Criando um FrameWork PHP do Zero Usando Padrões de Projeto (Parte 3)
nov 29, 2008 I Sem Categoria.Todo bom FrameWork tem que nos dar a possibilidade de personalizá-lo, e um dos requisitos mais necessários de personalização é a URL, e nesse artigo será explicado como obter essa funcionalidade utilizando o padrão de projeto Strategy, que tem por objetivo trocar dinamicamente o comportamento da aplicação;
Como um exemplo para melhor entendimento do Padrão Strategy, considere uma classe Guerreiro, que possui um método chamado lutar, onde nele ele utiliza de uma arma golpear os adversários; Porém existem vários tipos de armas (Espada, Machado, Lança, Arco/Flecha), e para ganhar uma guerra, deve-se ter vários tipos de armamentos distribuídos entre os guerreiros;
Como primeira idéia de implementação, vamos criar uma implementação Guerreiro padrão, e para cada tipo de armamento vamos estender a classe padrão sobrescrevendo o método lutar para utilizar outro armamento; Sendo assim temos:
Como primeira idéia de implementação,
<?php class Guerreiro { // Outros métodos do Guerreiro ... public function lutar() { // Implementação da luta echo 'lutando com os punhos'; } } class Arqueiro extends Guerreiro { public function lutar() { // Implementação da luta echo 'Atirando com o Arco'; } } class Cavaleiro extends Guerreiro { public function lutar() { // Implementação da luta echo 'Lutando com espada'; } } ?>
De inicio parece uma boa solução, porém a luta não consiste apenas em golpear o adversário, mas sim de vários outros comportamentos que não depende da arma, e sendo assim, esse código ficará repetido em cada classe que estender a classe Guerreiro, o que não é uma boa solução, tendo em vista que na hora da alteração desse código, terá que passar em cada classe alterando o mesmo código;
Para melhorar solucionar esse problema, vamos aplicar o conceito do padrão Strategy, onde será mantida uma unica implementação de Guerreiro que receberá em seu construtor qualquer classe que implemente a interface Arma, que contem um método para realizar o golpe; Sendo assim:
<?php //Implementação de Guerreiro class Guerreiro { private $arma; public function __construct(Arma $arma) { $this->arma = $arma; } // Outros métodos do Guerreiro ... public function lutar() { // Implementação da luta $this->arma->golpear(); } } interface Arma { public function golpear(); } class ArcoFlecha implements Arma { public function golpear() { echo 'Atirando com o arco'; } } class Punho implements Arma { public function golpear() { echo 'Cabra macho luta com os punhos'; } } class Espada implements Arma { public function golpear() { echo 'Lutando com a Espada'; } } // Exemplo $arqueiro = new Guerreiro(new ArcoFlecha()); $cavaleiro = new Guerreiro(new Espada()); $cabraMacho = new Guerreiro(new Punho()); ?>
O padrão Strategy segue o padrão de Orientação a Objetos de encapsular o que varia, mantendo o codigo comum em um unico só lugar, ficando fácil a manutenção que, no nosso exemplo, basta implementar a interface Arma caso precisar de um novo tipo de arma na guerra; Outra vantagem vem da amplitude onde o problema pode ser expandido, ou seja, caso existam outro tipo de classe que lute com armas (um Pirata, por exemplo), não é necessário reimplementar todo o comportamento de luta com armas:
<?php class Pirata { private $arma; public function __construct(Arma $arma) { $this->arma = $arma; } public function invadirNavioInimigo() { $this->arma->golpear(); } } $pirata = new Pirata(new Espada()); ?>
Aplicando esse conceito no framework, vamos alterar a forma como é lido os parâmetros que definem qual Module/Action que será acessada;
Utilizar variáveis GET para determinar qual action deve ser executada não é uma boa prática, e tornam “feias” as URLs do Sistema; Para isso, vamos aplicar o conceito de URLs amigáveis, no lugar das variáveis GET; Assim, os valores do module e da action que será acessada no sistema, serão lidos diretamente da URL;
No Framework atual, a leitura dos parâmetros que decidem qual Command executar esta sendo feita atualmente dentro do Command_Factory juntamente com o código de carregar o Command; Nesse caso em particular, teremos que separar as duas funcionalidades, pois vai variar a forma que será recuperado os parâmetros para carregar os Command, e seguindo a definição do Strategy, vamos criar uma interface que irá descrever como que as implementações de leitura dos parâmetros deve fornecer os valores, assim o factory não irá se importar de qual implementação ele esta recuperando os parâmetros;
Com essa alteração, cada classe passa a ter sua responsabilidade bem definida, e sempre que se deseja mudar a forma como que os parâmetros devem ser lidos, basta criar uma classe que implementa a interface que realiza essa ação e passa-la pro Factory sem precisar mudar uma linha de código do Factory;
Nossa interface se chamará Command_Info, pois ela guardara as informações da URL de acesso, e terá dois métodos principais, que irá retornar o Modulo e a Action desejada, ficando da seguinte forma:
arquivo: /lib/Command/Info.php
interface Command_Info { //Método que retorna o Module public function getModule(); //Método que retorna a Action public function getAction(); }
Vamos criar a nossa implementação padrão, que irá recuperar os parâmetros da mesma forma que estava sendo feita anteriormente:
arquivo: /lib/Command/Info/Default.php
class Command_Info_Default implements Command_Info { // Retorna o valor do Module contido no GET public function getModule() { return $_GET['module']; } // Retorna o valor da Action contigo no GET public function getAction() { return $_GET['action']; } }
O Command_Factory receberá uma instância de alguma implementação de Command_Info por parâmetro, para então realizar a chamada ao Command correto;
arquivo: /lib/Command/Factory.php
<?php class Command_Factory { public function createCommand(Command_Info $info) { //Se o module não for informado, ele passa a ser considerado o index; $module = ($info->getModule() != null) ? $info->getModule() : 'index'; //A mesma coisa para a action $action = ($info->getAction() != null) ? $info->getAction() : 'index'; //Deixa a primeira letra em Maiusculo e o resto da string em minusculo $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 não existir, gera erro if (!file_exists($fileName)) { throw new Exception_Pagenotfound('Arquivo do Command não encontrado: '.$fileName); } //Carrega o arquivo require_once $fileName; //O nome da classe é a Action seguida da String Command $className = $action.'Command'; //Depois que o arquivo for adicionado, a classe deve existir, caso não existir, o arquivo carregado //não contem o Command, então deve gerar outro erro; o parâmetro false no class_exists //é informado para não carregar a classe utilizando o __autoload if (!class_exists($className,false)) { throw new Exception_Pagenotfound('Command não foi encontrado: '.$className); } //Carrega e retorna a Classe return new $className(); } } ?>
Notem que não é obrigatório a classe Strategy ser informadam no construtor, podendo ser informada como parâmetro do método; Vale tambem ressaltar que foi definido que o Command_Factory deve receber uma instância de Command_Info como parâmetro, e esse é um dos poucos casos onde o PHP trata o tipo da variável, que no nosso caso é essencial essa verificação de tipo, pois caso seja passado uma variável qualquer, o nosso Command_Factory não irá conseguir trabalhar corretamente;
Agora que temos toda a estrutura separada e flexível a ponto de podemos personaliza-la, precisamos de uma maneira setar a configuração; Geralmente se utiliza arquivo texto (XML, YAML, etc) para essa configuração, mas no nosso caso vamos utilizar uma classe PHP, que na minha opinião é a melhor escolha, pois pula a etapa de abrir e ler um arquivo para armazenar as configurações em variáveis do PHP, tornando o sistema mais rápido;
Obs.: A forma de configuração utilizada nesse artigo não é apenas um exemplo, e sua implementação não faz parte do foco; Existem outras formas de fazer essa configuração, podendo você escolher a que mais lhe agrada
arquivo: lib/Config.php
<?php //Arquivo de configuração do FrameWork class Config { private $commandInfo; public function configure() { //Configura a Classe que irá ler os parâmetros da URL $this->commandInfo = new Command_Info_Default(); } // Retorna o CommandInfo public function getCommandInfo() { return $this->commandInfo; } } ?>
arquivo: index.php
try { //Inicia a configuração $config = new Config(); $config->configure(); //Instancia o Factory e Recupera o Command $factory = new Command_Factory(); $factory->createCommand($config->getCommandInfo())->execute(); } catch (Exception_Pagenotfound $ep) { echo '<h1>ERRO 404 - Página não encontrada</h1>'; } catch (Exception $e) { echo '<h1>ERRO 500 - Erro na execução</h1>'; } ?>
Vamos agora a partir dessa nova estrutura, alterar o comportamento do FrameWork para utilizar URL amigáveis para acessar os command; Ja existem vários artigos disponíveis na web explicando passo a passo como criar Url’s amigáveis, e a implementação apresentada aqui será totalmente baseada no artigo disponível no fórum do iMasters e escrito por Perfect Lion;
Antes de tudo, é necessário que o modulo rewrite do Apache esteja habilitado dentro do arquivo httpd.conf; Ele é o modulo responsável por redirecionar as requisições, sendo que, a forma de redirecionamento é definida através do arquivo .htaccess, que deve estar localizado na raiz do site;
A Url que vamos utilizar terá o seguinte formato:
http://www.endereco.qualquer/MODULE/ACTION sendo que, caso nenhum dos dois for informado, será utilizado index como padrão;
A seguir, o código do .htaccess adaptado para utilizar no framework:
# << URLs Amigaveis
# verifica se o modulo rewrite está habilitado se sim executa o bloco dentro
# habilita o modulo Rewrite
RewriteEngine On
# Foi comentada essa linha para que o rewrite funcione somente nos arquivos que estão no diretorio do .htaccess
# RewriteBase /
# Verifica se a url digitada é um arquivo caso seja para aqui
RewriteCond %{REQUEST_FILENAME} !-f
# Verifica se a url digitada é uma pasta caso seja para aqui
RewriteCond %{REQUEST_FILENAME} !-d
# Redireciona para o arquivo index.php caso nenhum desses comandos assim seja valido vai para ele.
RewriteRule .(/)?$ index.php
# >> URLs Amigaveis
Vamos agora adaptar a implementação do Perfect Lion para a nossa estrutura, criando assim a classe Command_Info_Rewrite;
arquivo: lib/Command/Info/Rewrite.php
<?php /** * Command_Info utilizando URL Amigável * * Código Original por Perfect Lion em http://forum.imasters.uol.com.br/index.php?showtopic=203965 * * A unica alteração foi para que o script funcione quando estiver dentro de um diretório no servido, pois o * script original funciona na raiz do servidor * * @author Everton Emilio Tavares */ class Command_Info_Rewrite implements Command_Info { private $gets; public function __construct() { // Recupera o diretorio em que o sistema se encontra echo $diretorio = substr($_SERVER['SCRIPT_FILENAME'],strlen($_SERVER['DOCUMENT_ROOT']),-10); // Recupera a URI $uri = $_SERVER["REQUEST_URI"]; // Recupera os dados da URI. esta sendo utilizada a mesma variável do artigo para melhor entendimento $gets = explode("/",str_replace(strrchr($uri, "?"), "", $uri)); // Primeiro elemento sempre será em branco, então faz um array_shift para remove-lo array_shift($gets); // Caso o sistema esteja em um diretório, é necessário remover o caminho de pastas do mesmo $this->gets = array_slice($gets,sizeof(explode('/',$diretorio))); } // Retorna a primeira parte da URL como o Module public function getModule() { return $this->gets[0]; } // Retorna a Segunda parte da URL como Action public function getAction() { return $this->gets[1]; } } ?>
Basta agora alterarmos a configuração do framework para utilizarmos nossa nova classe:
arquivo: /config.php
<?php /** * Classe de Configuração do FrameWork * @author Everton Emilio Tavares */ class Config { private $commandInfo; public function configure() { //Configura a Classe que irá ler os parâmetros $this->commandInfo = new Command_Info_Rewrite(); } // Retorna o CommandInfo public function getCommandInfo() { return $this->commandInfo; } } ?>
Assim temos nosso Framework configurável e adaptável para qualquer situação e a qualquer tipo de URL sem modificar as classes do coração do FrameWork; É possível ainda possibilitar a configuração de outras partes do FrameWork, como a Factory de Commands, por exemplo;
Igor de Paula Says:
Nossa, esses artigos estao superando minhas expectativas, to vendo q preciso estudar mais php….nao estou me aguentando pra chegar a parte com banco de dados….
otima serie
nov 29, 2008, 12:24 amCriando um framework PHP do zero usando padrões de projeto (parte 3) Says:
[...] por Everton Emilio Tavares (ezidiuΘgmail·com) - referência [...]
nov 29, 2008, 3:03 pmFernando H. Says:
Parabéns pelos artigos, a qualidade está ótima, mais vou acompanhar até o fim para poder avaliar melhor.
nov 29, 2008, 7:39 amDiogo Says:
Excelente série =)
nov 29, 2008, 6:44 pmLaerccius Says:
Kra to gostando muito de ler estes artigos
nov 29, 2008, 12:41 amgostei da linguagem do texto , está muito bem escrito e tb tenho aprendido muito sobre PHP .
Sou um desenvolvedor iniciante no php e quero saber cada vez mais sobre framework php .
acesso quase todos os dias pra ver a continuação ,
estou querendo q venha logo o próximo artigo .
Cássio Says:
Neste ano experimentei alguns frameworks, em aespecial o cakePHP, gostei do resultado mas eu senti uma grande nececidade de montar o meu própio com as classes que eu já estou acostumado a trabalhar dentro do padrão MVC, e esta série de artigos está sendo justamente o que eu preciso.
Parabéns e espero pelos próximos capitulos anciosamente…
Abraços.
nov 29, 2008, 4:30 pmbattisti Says:
Realmente o Everton está dando uma aula de programação orientada a objetos e padrões de projeto!
Vai ajudar os iniciantes e os macaco velho, pois estes também aprendem truques novos!
nov 29, 2008, 7:02 pmRibeiro Says:
Parabéns pela série de artigos, estão ótimos.
Sou mais um que aguarda anciosamente pela sequência.
Só uma pequena correção: no 8º bloco de código, o “->” está aparecendo como “->”. Pode ser que confunda algum novato.
Obrigado.
nov 29, 2008, 10:08 pmRibeiro Says:
Opa, e aqui dá certo =]
nov 29, 2008, 10:17 pmO “->” está aparecendo como “- & g t ;”.
Andre Says:
Tenho uma dúvida quanto à implementação deste FrameWork. Sou um iniciante ao desenvolvimento em PHP, e gostava de saber como ligar, neste caso, as noticias a um menu que implementei no Command/Index/Index.php.
obrigado pela ajuda até agora disponibilizada!
nov 29, 2008, 10:29 amMax Says:
facil de enteder simplesmente facil …
para bens
nov 29, 2008, 5:33 pmFernando Says:
Muito bom, bem fácil de entender.
nov 29, 2008, 2:15 amparabéns.
Claiton Says:
Excelente!! Acho que são poucos artigos desse gabarito que encontramos na net e em português! Parabéns!
nov 29, 2008, 11:03 am