Criando um FrameWork PHP do Zero Usando Padrões de Projeto (Parte 1)
out 01, 2008 I PHP.Hoje em dia, para um bom desenvolvimento de softwares em PHP é imprescindível o uso de um Framework. Existe vários disponíveis, dentre eles podemos citar: Symfony; Prado; Zend; Cake, etc. Mas mesmo com tantos frameworks disponíveis atualmente, será que vale a pena o desenvolvimento de mais um framework? Realmente não compensa, já que quase todos os frameworks já possui uma quantidade grande de usuários e de sistemas desenvolvidos, fazendo com que esses frameworks sejam confiáveis e robustos;
Mas o interessante de se desenvolver seu próprio framework é conhecer o funcionamento do mesmo, e aprender os mecanismos por trás dele para que o nivel de programação do seu código possa ser elevado; Portanto o objetivo principal deste artigo é desenvolver um Framework, para que possa, através da codificação do mesmo compreender os princípios básicos dos frameworks em geral, e conhecer um pouco mais de padrões de Projeto;
Como a maioria dos Frameworks da atualidade, vamos o implementar através do padrão MVC, (Model-View-Controller), que visa separar as camadas de Dados, de Visão e de Controle de forma intercambiável. Na primeira parte vamos desenvolver a parte inicial do Controller que é o responsável pelo fluxo das informações dentro do sistema.
Para muitas pessoas que programam em PHP, a codificação se da em um único arquivo, onde a ação a ser executada era definida a partir de uma variável GET e de IF, que direciona a execução do código dependendo do valor dessa variável; Se for analisar esse código, podemos considerar isso como um pequeno controller, sendo que é a partir dessa idéia iremos desenvolver o Framework;
Sendo assim, a seguir temos nossa primeira versão do nosso Controller, que está “controlando” as actions de um mini-sistema de noticia:
Arquivo: noticias.php
<?php $acao = $_ GET['action']; if ($acao == 'ler') { $id_da_noticia = $_GET['id']; echo 'Lendo a noticia de id '.$id_da_noticia; } else if ($acao == 'formulario') { echo 'Formulário de Cadastro de Noticia'; } else { echo 'Home das Noticias'; } ?>
A idéia aqui é intuitiva: Para cadastrar uma nova noticia, invoca-se a URL: noticia.php?action=formulario. Caso o parâmetro action for omitido, ou tiver um valor inválido será exibida uma página listando todas as Noticias cadastradas, que é sera Home do sistema de noticias;
O código é funcional, mantém tudo em um único lugar, porém durante as manutenções, o caos aparece. Com o aumento de tamanho ficará complicado se localizar dentro do arquivo, tanto para encontrar o local para uma correção como para adicionar uma nova action. A fim de conseguir uma melhor organização, vamos começar separando as ações em funções, e em seguida, um switch é utilizado para direcionar o fluxo da aplicação para a função desejada:
Arquivo: noticias.php
<?php function home() { echo 'Home das Noticias'; } function ler() { $id_da_noticia = $_GET['id']; echo 'Lendo a noticia de id '.$id_da_noticia; } function formulario() { echo 'Formulário de Cadastro de Noticia'; } switch($_GET['action']) { case 'formulario': formulario();break; case 'ler': ler(); break; default: home(); break; } ?>
Aparentemente nosso código está significativamente mais legível. Porém o problema da manutenção ainda permanece, o arquivo ficará maior que o da versão anterior, devido a todas as functions que deverão ser criadas; Para melhorar essa organização, vamos separar as funções em arquivos, e incluí-los conforme a action requisitada:
<?php switch($_ GET['action']) { case 'formulario': include('formulario.php'); formulario(); break; case 'ler': include('ler.php'); ler(); break; default: include('home.php'); home(); break; } ?>
O código acima aumentou a ainda mais a legibilidade porém a manutenção ainda é trabalhosa pois necessitamos realizar manualmente os includes dos arquivos das funções, o que não é nada empolgante, e caso o nome do arquivo incluído esteja errado, aparecerá um erro, para melhorar esse cenário, vamos adicionar orientação a objetos no nosso código, e também utilizar o padrão de projeto Command;
Esse padrão de projeto, como o próprio nome diz, trata-se de criar classes de comandos, que podem ser executados independentemente ou agrupadas, fornecendo a flexibilidade de serem alternadas em tempo de execução sem muitas mudanças; Cada classe Command implementa uma única interface, que contem um único método, como descrito a seguir:
arquivo: Command.php
<?php interface Command { public function execute ( ); } ?>
Para não nos preocuparmos com os includes, utilizaremos a função __autoload, que tem por responsabilidade, carregar todas as classes que ainda não foram carregadas dentro do script atual; Para maiores informações, veja no site do PHP; Para podermos utiliza-la, o nome do arquivo deve ser padronizado com o nome da Classe. Sendo assim, definimos que o nome da classe sera o nome do arquivo, seguido da string ‘Command’;
Assim teremos:
Formulario.php = FormularioCommand
LerNoticia.php = LerNoticiaCommand
HomeNoticia.php = HomeNoticiaCommand
Como exemplo, vamos criar o Command para a ação de leitura:
LerNoticia.php
<?php class LerNoticiaCommand implements Command { public function execute ( ) { $id_da_noticia = $_GET['id']; echo 'Lendo a noticia de id '.$id_da_noticia; } } ?>
Simples não? Outra vantagem dessa abordagem é que, se algum curioso tentar acessar o arquivo LerNoticia.php diretamente, ele não irá ver nada, ja que a classe LerNoticiaCommand necessita ser invocada para ser executada;
Assim nosso Controller fica mais simplificado:
arquivo: noticia.php
<?php //Incluindo a interface command, necessária para os command das Action require_once 'command.php'; function __autoload($nomeClasse) { $nomeArquivo = substr($nomeClasse,0,-7).'.php'; require_once $nomeArquivo; } switch($_ GET['action']) { case 'formulario': $command = new FormularioCommand();break; case 'ler' : $command = new LerNoticiaCommand();break; default : $command = new HomeNoticiaCommand();break; } $command->execute(); ?>
O controller ja está bastante prático no momento. Para criarmos uma nova action, basta:
- Criar um arquivo com uma classe que implemente a interface Command;
- Adicioná-lo no switch para que ele seja encontrado.
Mas é necessário ainda umas mudanças na organização interna para adaptá-lo nos moldes da orientação a objetos, e um dos principios básico desta abordagem é que devemos separar o que muda do que é estático, e no nosso caso, a parte do código que está sempre em alteração, no período de desenvolvimento, é o switch que diz qual command vai ser executado em qual action; e para separar essa funcionalidade, vamos utilizar mais um padrão de projeto, chamado Factory, que é uma classe cujo papel é instanciar e retornar um Command para a execução, dependendo dos parâmetros informados pela URL. Mais detalhes sobre o padrão factory você pode obter aqui;
Vamos tambem padronizar o nome da factory para que o __autoload possa encontrá-lo, fazendo com que o nome da classe seja o caminho de diretórios dela, separados pelo caractere underline (’_'), além disso vamos separá-las em um diretório específico do nosso framework, que chamaremos de lib; e tambem separaremos as classes de Controller, adicionando-as em um diretório específo, chamado ‘command’;
Nossa Factory terá o seguinte formato:
arquivo: lib/Command/Factory.php
<?php class Command_Factory { public function createCommand() { switch($_ GET['action']) { case 'formulario': return new FormularioCommand(); case 'ler' : return new LerNoticiaCommand(); default : return new HomeNoticiaCommand(); } } } ?>
e o nosso novo __autoload, juntamente com o novo controller, ficará assim
arquivo: noticia.php
<?php function __autoload($nomeClasse) { //Se a classe terminar com Command, carrega o arquivo da maneira 'antiga'; if (substr($nomeClasse,-7) === 'Command') { $nomeArquivo = 'command/'.substr($nomeClasse,0,-7).'.php'; } else { $nomeArquivo = 'lib/'.implode('/',explode('_',$nomeClasse)).'.php'; } require_once $nomeArquivo; } $factory = new Command_Factory(); $factory->createCommand()->execute(); ?>
E se quisermos ter, alem das noticias, um sistema para exibição de galeria de fotos no site? teremos que criar outro Factory? outro Controller?
R.: Não! podemos mudar nossa factory para que possa adicionar mais tipos de ’subsistemas’ no framework;
Para tanto necessitaremos de parametro adicional na URL que será chamado de ‘module’; Nossa Factory terá que ler esses dois parâmetros para direcionar o fluxo de informações ao Command correto; Sendo assim:
arquivo: lib/Command/Factory.php
<?php class Command_Factory { public function createCommand() { switch($_GET['module']) { case 'noticia': switch($_ GET['action']) { case 'formulario': return new FormularioCommand(); case 'ler' : return new LerNoticiaCommand(); default : return new HomeNoticiaCommand(); } case 'galeria': switch($_ GET['action']) { case 'vergaleria': return new VerGaleriaCommand(); case 'verfoto' : return new VerFotoCommand(); default : return new HomeGaleriaCommand(); } default: 'index': return new IndexCommand(); } } } ?>
Agora que o nosso controller não é mais específico para noticias, podemos renomea-lo para index.php, fazendo com que seja a pagina principal do sistema;
Poderiamos finalizar nosso controller por aqui, porém ele pode ser melhorado na parte da configuração dos Command, substituindo o switch por um arquivo de configuração (XML, YML, properties), ou podemos simplismente suprimir o switch, fazendo com que a factory encontre a classe Command conforme a estrutura de diretórios. Assim, vamos fazer com que a classe do Command esteja armazenada em um diretório com o nome do modulo a qual ela pertence;
Esse tipo de configuração é chamado de configuração por convenção, que é a que será utilizada no Controller que estamos desenvolvendo; e é preferível, pois a configuração das actions da-se na existência de um único arquivo, que é o próprio arquivo do Command; Vejamos como fica o Command_Factory com esse comportamento:
arquivo: /lib/Command/Factory.php
<?php class Command_Factory { public function createCommand() { //Se o module não for informado, ele passa a ser considerado o index; $module = (isset($_GET['module'])) ? $_GET['module'] : 'index'; //A mesma coisa para a action $action = (isset($_GET['action'])) ? $_GET['action'] : 'index'; //Deixa a primeira letra em Maiusculo e o resto da string em minusculo //Conforme dica de André Caldas, foi adicionado o comando htmlspecialchars para evitar //SQL Injection $module = htmlspecialchars(ucfirst(strtolower($module))); $action = htmlspecialchars(ucfirst(strtolower($action))); //Cria o nome do arquivo da Action $fileName = 'command/'.$module.'/'.$action.'.php'; //Se o arquivo existir, carrega-o, caso contrário gera erro if (file_exists($fileName)) { require_once $fileName; //O nome da classe é a Action seguida da String Command $className = $action.'Command'; //Carrega e retorna a Classe return new $className(); } else { //Não vou me preocupar com o tratamento de erros agora, isso será abordado nos proximos artigos die('Erro 404 - Página não encontrada'); } } } ?>
Tambem é necessário mudar a função __autoload, ja que o carregamento da classe do Command fica agora por responsabilidade da Factory.
arquivo: index.php
<?php
function __autoload($nomeClasse) {
require_once 'lib/'.implode('/',explode('_',$nomeClasse)).'.php';
}
$factory = new Command_Factory();
$factory->createCommand()->execute();
?>Arvore de diretório final:
- /
- index.php
- lib/
- Command.php
- Command/
- Factory.php
- Command/
- Noticia/
- Lernoticia.php
- Index.php
- Listar.php
- Galeria/
- Index.php
- Vergaleria.php
- Verfoto.php
- Noticia/
Com esse recurso, a manutenção do código fica somente por conta dos Commands. Para adicionar um novo, basta apenas criar o arquivo com a classe implementando a interface Command e adicionar no diretório do módulo específico. Muito prático, e rápido; Com esses artifícios ja da para começar a usar o Controller, mas ainda temos muito a fazer;
Editado: Conforme dica de André Caldas, foi adicionado o comando htmlspecialchars no Factory, afim de evitar códigos maliciosos vindos via URL
Clique aqui para fazer o Download do código gerado nessa primeira parte;
Igor de Paula Says:
Adorei o artigo, pois sempre fiz sites em dreamweaver, e to com um projeto que resolvi fazer em classes, mas depois resolvi fazer em mvc, e me chegou por um amigo esse seu blog….abraços
out 01, 2008, 4:03 pmskoolfobia Says:
Muito bom. Esse tipo de artigo realmente vale a pena. Cheers
out 01, 2008, 11:05 amCarlos Torre Says:
Muito bom post, gostei muito dessa iniciativa, acredito que em breve o php irá evoluir e ser uma linguagem comercial, equiparando-se com Java e .Net, pra isso nos programadores devemos deixar e sermos preguiçosos e programar php de forma correta, como demostra esse post.
out 01, 2008, 11:54 amParabéns
Lucas Fernando Amorim Says:
MVC e Smarty estão sendo supervalorizados.
out 01, 2008, 12:34 pmWryel Says:
Excelente aritgo, não o li por completo, mas chegando em casa eu vo dar uma lida, e do meu feedveck por completo
out 01, 2008, 1:49 pmezidio Says:
Valew galera! Aguarde a próxima parte, onde será abordado o tratamento de erros encima dessa arquitetura.
out 01, 2008, 2:55 pmAbraço!
André Gustavo Says:
Pow cara, exatamente na semana que comecei a criar um framework para estudo fiquei sabendo deste artigo pelo BR-LINUX, só vou esperar o CONAPHP que vai ter uma palestra exatamente sobre isso pra por a mão na massa
muito bom
out 01, 2008, 2:59 pmparabéns
jfr Says:
um otimo exemplo de boas praticas e programação

out 01, 2008, 3:03 pmcom tecnicas que tornam o negocio mais funcional
Anderson Says:
Muito bom o artigo, explicou muito bem sobre o padrão Command.
out 01, 2008, 3:51 pmRibamar FS Says:
Rapaz, realmente importante.
out 01, 2008, 3:57 pmComo você disse, já temos muitos e bons frameworks, mas saber como fazer um deles e usando os bons padrões aí é outra coisa.
Sempre procurei e tive vontade de criar o meu mas nunca encontrei um artigo assim como o seu.
Tem mais, se eu conseguir fazer um, posso criar um pequeno e funcional que resolva muitos dos meus problemas sem as complicações dos grandes frameworks existentes.
No mínimo eu saberei como funciona um bichinho destes.
Muito obrigado e concordo com o colega que é importante melhorar o nível técnico dos programadores de PHP, para valorizar a linguagem.
Ribamar FS Says:
O link dos fontes está quebrado.
out 01, 2008, 7:51 pmezidio Says:
Link dos fontes corrigido…
out 01, 2008, 8:31 pmAndré Caldas Says:
Sei que o objetivo do artigo é educacional… mas tá cheio de falhas de segurança.
Nenhuma informação passada pelo usuário em uma URL é confiável. Não se deve fazer echo( “$id_da_noticia” ). Deve-se usar htmlspecialchars.
Pior ainda é:
$fileName = ‘command/’.$module.’/’.$action.’.php’;
André Caldas.
out 01, 2008, 10:57 pmThiago A. Says:
Parabéns pelo artigo. Eu uso um framework parecido, que eu mesmo fui desenvolvendo, inclusive com um esquema de include automático para os modules, e condensando as rotinas de inserir/alterar/excluir, funcionalidades do banco de dados em geral e comandos SELECT nas classes, retornando-os em um array pra ficar legal a manipulação.
A diferença é que condenso o controle de exibição da aplicação (as actions do module) em apenas um arquivo (que é incluido automaticamente, ele e a classe correspondente), e pra ir à ação desejada, no caso de manutenção, utilizo um simples CTRL + F… e claro, abuso da identação, então fica tudo bem legível e fácil de achar.
Também gosto de serializar as classes correspondentes dos modules na Session, assim a aplicação lembra onde o usuário estava dentro de um module, ou qual pesquisa ele fez por último. E cadastro também os modules no banco de dados, assim limito a exibição das áreas válidas e implemento um sistema de permissões. Mas achei bem legal esse esquema, e como leva o padrão de orientação a objetos mais à risca e faz uma separação mais rigorosa, penso em adotá-lo.
Abraços
out 01, 2008, 11:14 pmJoão F. Melo Says:
Muito show mesmo, como “iniciante” em php ainda não conhecia como fazer frameworks, usa muitos arquivos a acabava ficando com um “site” enorme e tendo o trabalho de lembrar onde fica isso ou aquilo.
Estarei acompanhando o artigo e se precisar de ajuda na tradução de algum doc pode contar comigo.
out 01, 2008, 7:58 pmGuilherme Garnier Says:
Ótimo artigo, parabéns. Muito bom para aprender na prática como aplicar padrões de projeto.
out 01, 2008, 11:50 amIgor de Paula Says:
uma duvida nesse caso, aqui nessa funcao, eu poderia colocar todo o script da minha pagina? incluindo css, consulta ao banco…
eis a funcao…
public function execute ( ) {
out 01, 2008, 1:18 pm$id_da_noticia = $_GET['id'];
echo ‘Lendo a noticia de id ‘.$id_da_noticia;
}
Everton Tavares Says:
Igor… poder pode… mas não é a melhor solução para o problema; O ideal é separar a parte de banco de dados, a parte de codificação e a parte visual; Nos próximos artigos vamos estar abordando esse assunto;
out 01, 2008, 1:49 pm=)
Igor Says:
Estou ansioso para terminar a serie….
out 01, 2008, 11:16 pmKaizenWeb » Criando um FrameWork PHP do Zero Usando Padrões de Projeto (Parte 2) Says:
[...] a abordagem que melhor se encaixa no FrameWork; Se você perdeu a primeira parte, pode ler ela clicando aqui; Como foi observado, a nossa factory gera um erro quando não encontra o command solicitado, e [...]
out 01, 2008, 1:24 amRibamar FS Says:
Olá!
Quando executo a primeira parte aparece o erro:
Erro 404 - Página náo encontrada
O que será que está havendo?
out 01, 2008, 7:31 amEverton Tavares Says:
Provavelmente porque não existe o Command Index/IndexCommand.php, que é o command que é executado caso não seja informado o module e a action;
out 01, 2008, 9:58 amKaléu P. D. Caminha » Blog Archive » Padrões de Projeto em PHP Says:
[...] links: Padrão Adapter (exemplo 1 PHP); Construindo um framework PHP com Padrões de Projeto (parte 1, parte [...]
out 01, 2008, 4:17 pmHelio Says:
Senhor, muito boa a série. Vou ler todas!!!!
- Só 2 comentários e 2 dúvidas:
1º - Tem 2 parágrafos duplicados no texto. “Poderíamos finalizar nosso come…”
2º - Faltou o link para o Padrão Factory? Você está querendo dizer “Abstract Factory” ou “Factory Method” ou é algum outro padrão novo que não tá no GoF?
3º - Cuidado com o que você chama de “padrões de projeto” e “padrões de design”. MVC é um padrão de projetos que utiliza vários “padrões de design” como o Command.
Aprender padrões é MUITO bom… hehehe
out 01, 2008, 10:19 pmEverton Tavares Says:
Realmente tinha um paragrafo duplicado… ja foi removido, obrigado!!
=)
O Padrão Factory a qual me refiro é o Factory Method; Em vários artigos o Factory Method é chamado apenas de factory, como vc pode conferir nesse link: http://java.sys-con.com/node/46239.
Vale a pena ressaltar que não existe apenas os padrões catalogados pelo GoF, a Sun por exemplo tem seu próprio catálogo de Padrões que tem por objetivo complementar os padrões do GoF; Veja mais aqui: http://www.corej2eepatterns.com/Patterns2ndEd/index.htm
Quanto ao terceiro comentário, o termo Design Patterns tem duas traduções: Padrões de Design e Padrões de Projeto; Portanto, os dois termos significam a mesma coisa! =)
out 01, 2008, 11:13 pmEverton Tavares Says:
Tambem para constar, alguns autores também chamam o Factory Method como Simple Factory (Fábrica Simples);
out 01, 2008, 8:14 amian Says:
Muito bom!
out 01, 2008, 4:30 pmPrático, Simples e Funcional.
Leonardo Says:
Everton, primeiramente parabens pelo artigo! Muito explicativo e prático!
Agora vou levantar um probleminha que estou enfrentando e uma possível solução:
Estou usando o seu exemplo, e a IDE Eclipse + DW para fazer as paginas.
No Eclipse, tudo perfeito! Ele mapeia as classes, disponibiliza a documentação, etc, etc.
Agora no DW que está o problema: Um colega que faz a parte visual do site (ele não programa) usando o DW e eu temos enfrentado problemas quanto ao ‘endereçamento’ dos elementos. Usamos endereçamento relativo para imagens, links, etc, e na hora de publicar o problema ocorre, pois as páginas pensam que estão na pasta command/*module*, mas na verdade estão no root, devido ao include do Factory no index.php.
Bom, eu estava pensando em usar header(location:xxx) no lugar do include. O problema do design estaria resolvido, mas as páginas não seriam mais classes.
O que você acha disso?
Agradeço a atenção!
out 01, 2008, 10:00 amroberto Says:
Olá parabens pelo artigo, estou tendo um problema na parte de acessar os Commands, qual seria a sintaxe para acessa-los?
out 01, 2008, 12:57 pmja tem tentei localhost/[site]/[controller]/[action] e só da pagina nao encontrada. só funciona quando localhost/[site] q vai para a pagina Inicial.
Leonardo Says:
Roberto, verifique o nome das pastas.
out 01, 2008, 4:03 pmA pasta ‘command’ é diferente da pasta ‘Command’.
Pelo que me lembro, tive que fazer algumas correções desse tipo no projeto, mas no mais está funcionando.
Ricardo Says:
Amigo so um problema no arquivo Factory.php a variavel $module = index, mas nao se tem a pasta index isso não levaria a um erro? pois ele vai tentar acessar algo que nao existe.
out 01, 2008, 11:54 pm