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-&gt;createCommand($config->getCommandInfo())-&gt;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>';
}
?&gt;

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;

Códigos desse Artigo

13 responses so far, say something?

  1. 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

  2. Criando um framework PHP do zero usando padrões de projeto (parte 3) Says:

    [...] por Everton Emilio Tavares (ezidiuΘgmail·com) - referência [...]

  3. Fernando H. Says:

    Parabéns pelos artigos, a qualidade está ótima, mais vou acompanhar até o fim para poder avaliar melhor.

  4. Diogo Says:

    Excelente série =)

  5. Laerccius Says:

    Kra to gostando muito de ler estes artigos
    gostei 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 .

  6. 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.

  7. battisti 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!

  8. Ribeiro 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.

  9. Ribeiro Says:

    Opa, e aqui dá certo =]
    O “->” está aparecendo como “- & g t ;”.

  10. 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!

  11. Max Says:

    facil de enteder simplesmente facil …

    para bens

  12. Fernando Says:

    Muito bom, bem fácil de entender.
    parabéns.

  13. Claiton Says:

    Excelente!! Acho que são poucos artigos desse gabarito que encontramos na net e em português! Parabéns!

Deixe um comentário