Ir para conteúdo

POWERED BY:

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

Douglas Aguiar

Criando um sistema de catálogo em MVC

Recommended Posts

Há um tempo estou com a intenção de criar um site que funcionaria como um catálogo para comércios da minha região.

 

Pois bem, resolvi merter a mão na massa e resolvi que por ser algo muito simples iria tentar criar o sistema num padrão MVC, pra praticar o tanto que já li e "esbocei" sobre o assunto.

 

Estou bem, beeeem no início e não estou com problema nenhum quanto ao desenvolvimento. Então: pra que você está escrevendo no fórum?, você pergunta.

 

Gostaria da ajuda dos colegas só para analizar se estou indo na direção certa desde o começo, opinar sobre os erros e se quiserem podem dar idéias a vontade.

 

Obs.:nem todo o código é 100% meu, o SimpleUrl por exemplo, foi adaptado de uma video aula que vi há muito no youtube, e perdoem se não lembro a fonte.

 

.htaccess

<IfModule mod_rewrite.c>
	RewriteEngine On
	RewriteBase /CleanUrls/
	
	RewriteCond %{REQUEST_FILENAME} !-f
	RewriteCond %{REQUEST_FILENAME} !-d
	
	RewriteRule ^(.*)$ index.php/$1
</IfModule>

 

 

index.php

<?php 

include 'core/autoload.php';

$controller = new Controller();

 

Controller.php

<?php

class Controller{
	public function __construct(){
		$url = new SimpleUrl('/CleanUrls');

		/**
		*	Verifica se foi requisitado um controller específico
		*	True: instancia a classe referente ao controller e executa a ação passada ou a default que será index()
		*	False: instancia a classe home, que terá por enquanto a penas a função __construct()
		**/
		if(!$url->segment(1)){
			$controller = new HomeController();
		}else{
			$controller = ucfirst(strtolower($url->segment(1))).'Controller';
			$controller = new $controller;

			if(!$url->segment(2)){
				$controller->index();
			}else{
				$controller->{$url->segment(2)}();
			}
		}

		
	}
}

 

SimpleUrl.php

<?php

class SimpleUrl{
	public $sitePath;
	
	function __construct($sitePath){
		$this->sitePath = $this->removeSlashes($sitePath);
	}
	
	function __toString(){
		return $this->sitePath;
	}
	
	private function removeSlashes($string){
		if($string[strlen($string) - 1] == '/'){
			$string = rtrim($string, '/');
		}
		return $string;
	}
	
	function segment($segment){
		$url = str_replace($this->sitePath, '', $_SERVER['REQUEST_URI']);
		$url = explode('/', $url);
		
		if (isset($url[$segment])) {
			return $url[$segment];
		}else{
			return false;
		}
	}
}

?>

 

AutoLoad.php

<?php

function __autoload($class){
	try{
		if( file_exists('controller/'.$class.'.php' ) ){ 
		    set_include_path('controller/'); 
		    spl_autoload($class); 
		} 
		elseif( file_exists('model/'.$class.'.php' ) ){ 
		    set_include_path('model/'); 
		    spl_autoload($class); 
		}elseif( file_exists('view/'.$class.'.php' ) ){ 
		    set_include_path('view/'); 
		    spl_autoload($class); 
		}elseif( file_exists('core/'.$class.'.php' ) ){ 
		    set_include_path('core/'); 
		    spl_autoload($class); 
		}
	}catch(Exception $e){
		$e->getMessage();
	}
}

 

 

Compartilhar este post


Link para o post
Compartilhar em outros sites

Vamos lá.

 

function __autoload($class){


Eu começarei de baixo para cima, acho que faria mais sentido no caso.

O primeiro ponto é que você só pode definir uma função __autoload (ou qualquer outro nome que seja). A spl_autoload_register resolve isso melhor.

 

		if( file_exists('controller/'.$class.'.php' ) ){ 
		    set_include_path('controller/'); 
		    spl_autoload($class); 
		} 
		elseif( file_exists('model/'.$class.'.php' ) ){ 
		    set_include_path('model/'); 
		    spl_autoload($class); 
		}elseif( file_exists('view/'.$class.'.php' ) ){ 
		    set_include_path('view/'); 
		    spl_autoload($class); 
		}elseif( file_exists('core/'.$class.'.php' ) ){ 
		    set_include_path('core/'); 
		    spl_autoload($class); 
		}


If, elseif, elseif, elseif... um bad smell conhecido.

Pense bem: cada vez que você for criar uma nova pasta você precisará editar essa bagaça. Além disso, a performance fica comprometida com set_include_path sendo executado toda hora.

Há uns bons anos, a comunidade PHP se reuniu e definiu um padrão conhecido como PSR-0. Nele, você usa namespaces e mapeia automaticamente os arquivos, removendo a necessidade para esse tipo de gambiarra.

 

$e->getMessage();


Isso não faz absolutamente nada. Se der erro, nada acontecerá. O método getMessage só retorna uma string, ele não imprime nada na tela.

 

class SimpleUrl{


Não entendi a escolha de nome. Por que SimpleUrl? Existe, por acaso, um plano de ter uma ComplexUrl ou algo do tipo? Url já define o nome, adjetivos que não fazem sentido não deveriam ser usados.

 

public $sitePath;


Conte-me mais sobre como é ter um valor completamente renomeável causando um caos total no sistema....

Essa propriedade deveria ser privada, já que a classe representa uma URL (VO), sendo assim imutável.

 

	function __construct($sitePath){
		$this->sitePath = $this->removeSlashes($sitePath);
	}
	
// ...
	
	private function removeSlashes($string){
		if($string[strlen($string) - 1] == '/'){
			$string = rtrim($string, '/');
		}
		return $string;
	}


Pelo que entendi, sua intenção é remover a(s) última(s) barras da URL para não causar duplicação (/page/ e /page). Por que não fazer um router de URLs ao invés disso?

 

$url = str_replace($this->sitePath, '', $_SERVER['REQUEST_URI']);


Acesso direto a uma superglobal, aumentando o acoplamento.

 

		if (isset($url[$segment])) {
			return $url[$segment];
		}else{
			return false;
		}


Return false para tratar erros? O PHP tem exceptions.

 

?>


Não há necessidade dele aqui.

 

class Controller{


Essa classe deveria ser um FrontController. Infelizmente, ela parece uma classe de adivinhação.

 

$url = new SimpleUrl('/CleanUrls');


Essa classe deveria estar sendo injetada, não sendo criada dentro do construtor diretamente.

 

		/**
		*	Verifica se foi requisitado um controller específico
		*	True: instancia a classe referente ao controller e executa a ação passada ou a default que será index()
		*	False: instancia a classe home, que terá por enquanto a penas a função __construct()
		**/


Einstein dizia: "Se você não consegue explicar algo de modo simples é porque não entendeu bem a coisa".

Se é necessário comentários para explicar a lógica (principalmente algo simples), então há um problema de legibilidade e expressividade do código.

 

		if(!$url->segment(1)){
			$controller = new HomeController();
		}


Aqui começa a adivinhação. E se não existir uma classe HomeController? E se essa classe precisar de parâmetros? Começa a existir um acoplamento entre o FrontController e a aplicação.

 

$controller = ucfirst(strtolower($url->segment(1))).'Controller';
$controller = new $controller;


Aqui é pior ainda. Como você sabe que existe uma classe com o nome do primeiro segmento? E se eu quiser ter uma URL personalizada? A lógica de representar controllers em URLs é uma má ideia. O Rails, disseminador dessa prática, aprendeu isso com o tempo.

 

			if(!$url->segment(2)){
				$controller->index();
			}


Que interface define a presença do método index num controller?

 

$controller->{$url->segment(2)}();


Uma simples coisa: jamais, nunca, nunquinha, nem pelo prêmio da loteria acumulada durante seis meses chame um método e/ou classe usando uma string, principalmente oriunda de input de usuário.

Se você pensar que essa feature não existe, os problemas daqui vão embora e você passa a pensar numa lógica concisa e explícita, ao contrário de pura adivinhação baseado numa convenção que afeta até o design de URIs.

 

 

include 'core/autoload.php';

 

Você precisa de carregar esse arquivo. Um include não pararia o fluxo da aplicação. No caso seria correto o require_once.

 

 

$controller = new Controller();

 

A ideia de um construtor é construir um objeto. Nesse caso o construtor está servindo como operação, o que deveria estar em métodos com suas devidas responsabilidades.

 

---

 

Resumo da ópera:

- você pegou código não testado e foi adaptando, logo a gambiarra foi aumentando cada vez mais

- você está pensando em MVC como algo incrível e esquecendo OOP, quando MVC, principalmente em ambientes complexos como a web, não passa de uma sigla bacana para dar marketing, mas na prática, o que vale para organização e facilidade de manutenção é SoC

 

Resolvendo:

- implementar um FrontController com um Router, ao invés de gambiarrar com mágica

- implementar testes unitários

- implementar PSR-0 e modularização

- implementar S.O.L.I.D., GRASP, SoC

- implementar layer de HTTP

Compartilhar este post


Link para o post
Compartilhar em outros sites

Agradeço muito a avaliação minunciosa e trabalhosa Enrico, então vamos lá:

 

class SimpleUrl{

A nomenclatura foi o criador que fez, pois a classe é um simples "mapeador" ( nem sei se existe isso kk ) de url's, mas realmente é melhor simplificar os nomes.

 

Sobre o router de URL's, eu reconheço a expressão do alguns fóruns de CakePHP, mas n sei se é o mesmo.

 

$url = str_replace($this->sitePath, '', $_SERVER['REQUEST_URI']);

Se o acesso direto prejudica, o que você me sugere?

 

$url = new SimpleUrl('/CleanUrls');

Então não deveria haver um new para construção, ela poderia ser abstrata e o parâmetro passado para ela?

Tentando entender o que é a injeção...

 

		/**
		*	Verifica se foi requisitado um controller específico
		*	True: instancia a classe referente ao controller e executa a ação passada ou a default que será index()
		*	False: instancia a classe home, que terá por enquanto a penas a função __construct()
		**/

O comentário seria para quem lesse o post, pelo que você disse o problema seria eu precisar do comentário para saber do que se trata, me enganei?

 

if(!$url->segment(1)){
	$controller = new HomeController();
}

Acho que todo site necessita de uma home, claro que se fosse reutilizar o código para uma aplicação de outro tipo não poderia, então seria realmente um problema, mas ao invés de uma classe Home, o que seria mais correto?

 

if(!$url->segment(2)){
	$controller->index();
}

Isso eu resolvo fácil, "acho" que aprendi bem a criar interfaces no artigo do João Batista, mas pelo jeito não aprendi a reconhecer a necessidade de quando utilizá-las ainda, mas eu chego lá, rs

 

$controller->{$url->segment(2)}();

Aqui eu poderia tentar utilizar algo como uma abstract factory para criar a interface e escolher o método utilizado em tempo de execução?

 

$controller = new Controller();

Novamente, eu não saberia como carregar a classe sem um construtor...poderia exemplificar?

 

No mais. algumas couisas eu entendi perfeitamente e vou resolver fácil e algumas coisas vou precisar estudar, já que nunca ouvi falar como: PSR-0, S.O.L.I.D., GRASP, SoC...

 

Mas o código que peguei foi só o da classes SimpleUrl mesmo, as burradas e gambiarras foram todas minhas kkk

 

O engraçado é que há pouco tempo o php não tinha essa capacidade toda então o que eu sabia dava pra me virar legal, mas como não gosto de ficar pra trás o negócio é estudar.

 

Verdade isso de eu estar focando no Padrão MVC e esquecendo coisas básicas de OOP. O João disse que boa parte dos erros acontecem por focar tanto no problema que não vemos a solução que está na cara.

 

Mas como ler, pra mim não é nenhum problema, daqui a algumas semanas acho que consigo pegar o básico desses conceitos e implementar.

 

Agora fala pra mim, pra alguém que não tem faculdade, não estou tão fora do caminho. Claro, me falta bastante base, reconheço isso, vlw pelo tempo.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Querer avançar além de código spaghetti coloca você numa posição bem melhor. Já é um começo, agora é seguir em frente.

A nomenclatura foi o criador que fez, pois a classe é um simples "mapeador" ( nem sei se existe isso kk ) de url's, mas realmente é melhor simplificar os nomes.


O problema não é a classe, mas o nome dela, que não diz bem o que ela faz. É como se eu criasse uma loja chamada "Simples", e aí? o que a loja vende? qual é o público da loja? O nome tem que dizer algo.

 

$url = str_replace($this->sitePath, '', $_SERVER['REQUEST_URI']);
Se o acesso direto prejudica, o que você me sugere?
$url = new SimpleUrl('/CleanUrls');
Então não deveria haver um new para construção, ela poderia ser abstrata e o parâmetro passado para ela?
Tentando entender o que é a injeção...


Em ambos os casos eu estou falando de Inversão de Dependências e, mais especificamente, em Dependency Injection.

 

		/**
		*	Verifica se foi requisitado um controller específico
		*	True: instancia a classe referente ao controller e executa a ação passada ou a default que será index()
		*	False: instancia a classe home, que terá por enquanto a penas a função __construct()
		**/
O comentário seria para quem lesse o post, pelo que você disse o problema seria eu precisar do comentário para saber do que se trata, me enganei?


Não se enganou não :)

O problema de comentários é que eles ocultam a verdadeira intenção do código e dificultam a manutenção (mudou o código, mudou o comentário). Só se usa quando é necessário.

 

if(!$url->segment(1)){
	$controller = new HomeController();
}
Acho que todo site necessita de uma home, claro que se fosse reutilizar o código para uma aplicação de outro tipo não poderia, então seria realmente um problema, mas ao invés de uma classe Home, o que seria mais correto?


Ter um router de URLs configurável.

 

if(!$url->segment(2)){
	$controller->index();
}
Isso eu resolvo fácil, "acho" que aprendi bem a criar interfaces no artigo do João Batista, mas pelo jeito não aprendi a reconhecer a necessidade de quando utilizá-las ainda, mas eu chego lá, rs

 

Aqui eu poderia tentar utilizar algo como uma abstract factory para criar a interface e escolher o método utilizado em tempo de execução?


Não me refiro às interfaces desse tipo, mas sim à API dos objetos. Numa linguagem mais estrita, como Java, esse código nem compilaria.

No caso como você sabe que o objeto controller responde ao método index? E se ele não tiver? Por que todo controller tem que ter um index? O router resolveria isso, não vejo necessidade de Abstract Factory para algo desse tipo.

 

 

$controller = new Controller();

Novamente, eu não saberia como carregar a classe sem um construtor...poderia exemplificar?

 

O que eu disse é que você está usando um construtor para realizar operações. O ideal é que um construtor sirva apenas para definir propriedades e validar os dados enviados. A parte de executar o controller, por exemplo, poderia ficar num método dispatch.

 

O engraçado é que há pouco tempo o php não tinha essa capacidade toda então o que eu sabia dava pra me virar legal, mas como não gosto de ficar pra trás o negócio é estudar.

 

Verdade isso de eu estar focando no Padrão MVC e esquecendo coisas básicas de OOP. O João disse que boa parte dos erros acontecem por focar tanto no problema que não vemos a solução que está na cara.

 

Mas como ler, pra mim não é nenhum problema, daqui a algumas semanas acho que consigo pegar o básico desses conceitos e implementar.

 

:graduated:

Compartilhar este post


Link para o post
Compartilhar em outros sites

Então...

 

Comecei a estudar sobre a tal DI (Dependecy Injection) e achei um ótimo artigo aqui no site principal do Imasters escrito pelo Fabrício Lopes:

 

- Injeção de dependência: desacoplando sua aplicação

 

Pela descrição no título parece que foi exemplificado em C#, tentei adaptar pra PHP e fiz algumas alterações no exemplo sem DI pra mostrar algum resultado caso o código compilasse corretamente e gostaria que dessem uma olhada antes de eu analizar mais a fundo, pra saber se tem algum erro...

 

O exemplo ficou assim:

<?php

class Download{
	//Métodos e Propriedades de Download
}

class DownController{
	public function novoDownload(Download $objDown){
		$objDownBusiness = new DownBusiness();
		if($objDownBusiness->salvarDownloadDb($objDown)){
			echo "Download Salvo</br>";
		}else{
			echo "Erro ao Salvar";
		}
	}
}

class DownBusiness{
	public function salvarDownloadDb(Download $objDown){
		$objDownData = new DownData();
		
		if($objDownData->conexaoBanco()){
			echo "Banco Conectado</br>";
			return true;
		}else{
			echo "Erro ao Conectar";
		}

		//Salva no Db
	}
}

class DownData{
	public function conexaoBanco(){
		//Conecta ao Banco

		return true;
	}
}

$download = new Download();
$downController = new DownController();

$downController->novoDownload($download);

 

E modificado:

<?php

interface iDownBusiness{
	public function salvarDownloadDb(Download $objDown);
}

interface iDownData{
	public function conexaoBanco();
}

class DownController{
	private $iDownBusiness;

	public function __construct(iDownBusiness $iObjDownBusiness){
		$this->iDownBusiness = $iObjDownBusiness;
	}

	public function novoDownload(Download $objDown){
		$this->iDownBusiness->salvarDownloadDb($objDown);
	}
}

class DownBusiness implements iDownBusiness{
	private $iObjDownData;

	public function __construct(iDownData $iObjDownData){
		$this->iObjDownData = $iObjDownData;
	}

	public function salvarDownloadDb(Download $objDown){
		$this->iObjDownData->conexaoBanco();

		//Salva no Db
	}
}

class DownData implements iDownData{
	public function conexaoBanco(){
		//Conecta ao Banco
	}
}

class Download{
	//Definição Download
}

$downController = new DownController(new DownBusiness(new DownData()));

$downController->novoDownload(new Download);

 

Seria mais ou menos isso mesmo?

Compartilhar este post


Link para o post
Compartilhar em outros sites

Enrico, quanto ao elseif, elseif... fui ler sobre e cheguei á seguinte mudança:

<?php

function loadController($className) {
    $filename = "controller/" . $className . ".php";
    if (is_readable($filename)) {
        include_once $filename;
    }
}
 
function loadModel($className) {
    $filename = "model/" . $className . ".php";
    if (is_readable($filename)) {
        include_once $filename;
    }
}
 
function loadView($className) {
    $filename = "view/" . $className . ".php";
    if (is_readable($filename)) {
        include_once $filename;
    }
}
 
function loadCore($className) {
    $filename = "core/" . $className . ".php";
    if (is_readable($filename)) {
        include_once $filename;
    }
}
 
spl_autoload_register("loadController");
spl_autoload_register("loadModel");
spl_autoload_register("loadView");
spl_autoload_register("loadCore");

melhor? pior? mesma mer** ?

Não achei nada sobre UrlRouter, se puder recomendar alguma leitura, mesmo que em inglês agradeço.

 

Se não me engano o CakePHP faz algo semelhante a chamar métodos de acordo com a url...foi dele que tirei a idéia.

 

Se o construtor deve construir, seria mais correto assim:

private $url;

	public function __construct(Url $objUrl){
		$this->url = $objUrl;

 

Bom, essas foram algumas mudanças pra tentar consertar...

Compartilhar este post


Link para o post
Compartilhar em outros sites

De intrometido vou dar uma sugestão em relação ao autoloader, como o Enrico disse você poderia fazer o uso de namespaces, facilitaria bastante, porém está disponível apenas a partir da versão 5.3.

 

Então poderia ser resolvido com uma classe de carregamento de classes:

class ClassLoader {

    private $dirs = [];

    public functin addDir($dir) {
        $this->dirs[] = trim(str_replace('\\', '/', (string)$dir), '/');
        return $this;
    }
    
    public function register() {
        spl_autoload_register([$this, 'load'], true);
        return $this;
    }

    public function load($class) {
        foreach ($this->dirs as $dir) {
            $path = $dir . '/' . $class . '.php';

            if (file_exists($path)) {
                include $path;
                break;
            }
        }
    }
}

 

Para usar bastaria:

$cl = new ClassLoader();
$cl->addDir('/app')
   ->addDir('/lib')
   ->register();

 

Suponde que tivéssemos a seguinte estrutura de diretórios:

app/
     model/
          User.php
     controller/
          UserController.php

 

E no arquivo app/model/user.php temos a classe model\User, então para carregá-la registraríamos apenas o diretório app/ e não app/model, pois assim o ClassLoader cuidaria de resolver a localização do arquivo mapeando-o pelo namespace.

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.