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;
[...] 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 [...]
ola amigo
deu erro para baixar o zip aqui
Erro ja corrigido, peço desculpas…
Obrigado Everton pelo material , vou dar uma estudada , depois
adicione no seu blog , o google friend connect ai eu acompanho o
seu novos material
um abraço e tenha uma boa semana
Ja dicionei o widget no site… Obrigado pela dica!!
Opa bom dia Everton
adicione embaixo do friend connect o ‘twitter tambem”
tem um plugin no wordpress que tudo que vc adicionar aqui
ja vai direito la para o twitter
ai eu pego tambem la qualquer novidade do blog
um abração e bom final de semana para ti
@kakarotodev
[...] Artigo muito interessante: http://kaizenweb.com.br/2010/12/criando-um-framework-php-do-zero-usando-padroes-de-projeto-parte-1/ [...]
BOa noite,baixei o tuto e rodei mas nãosai da index.Como q seria a URL para noticia por exeplo. Tente de varias maneiras
http://localhost/frame/artigo_framework_parte_1/command/Noticia_Listar
http://localhost/frame/artigo_framework_parte_1/Noticia_Listar
http://localhost/frame/artigo_framework_parte_1/command/Noticia/Listar
http://localhost/frame/artigo_framework_parte_1/Noticia/Listar
Mas nenhuma entrou na pagina de noticia, so deu erro de ..was not found on this server.
Consegui, a partir da parte 3, deve ser pq os outros 2 naum tinha o htacess.
Uma pergunta, como eu faria em caso de um cadastro de cliente, por exemplo
Não entendi muito bem sua pergunta, mas acredito que em cadastro de cliente você deve ter um command de listagem, um para o formulário de edição, outro para receber os dados e gravar no banco, e assim por diante.
=)
[...] Criando um FrameWork PHP do Zero Usando Padrões de Projeto (Parte 1) [...]
Cara, muito bom o artigo, to lendo e está tudo muito claro!
Parabéns.