Criando um FrameWork PHP do Zero Usando Padrões de Projeto (Parte 2)
out 25, 2008 I PHP.Nessa parte da construção do FrameWork, não vamos utilizar padrões de projetos, e sim, vamos analisar as estruturas de tratamento de erros fornecidas pelo PHP, e escolher 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 esse foi tratado utlilizando a função die, que interrompe a execução exibindo uma mensagem; esse tratamento é o tratamento básico que encontramos em diversos tutoriais de PHP espalhados pela net, e é eficaz para quando o erro ocorrido impede a continuação da execução;
A desvantagem de utilizar esse método em um sistema “real”, é a falta de controle que temos sobre ele, pois a chamada a esta função finaliza a execução, exibe a mensagem para o usuário e só! não existe uma forma de mudar o comportamento para que possamos tratar o erro, gerar log, enviar a mensagem por email, etc… e sendo assim, o desenvolvedor vai ficar sabendo do erro através do usuário do sistema, o que não é uma idéia muito interessante;
Para tentar melhorar o tratamento de erros, vamos encapsular esse erro em uma função. Vamos chamá-la de erro(), e dentro dela vamos tratar a mensagem e vamos chamar o die para finalizar a execução; Assim teremos, mais ou menos, o seguinte formato:
function erro($mensagem) { echo '<h1>Erro na execução do sistema</h1>'; echo 'Descrição do erro: '.$mensagem; die(); }
Vamos aplicar ao nosso factory, para que um erro seja gerado caso o Command não for encontrado;
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 $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 { //Chama nossa função personalizada para tratamentos de erro erro('Command não encontrado!'); } } } ?>
Agora já temos um tratamento único para os erros. E dentro dessa função, pode-se executar uma função para mandar a mensagem de erro para o desenvolvedor por email, ou grava-la em um log; Porem para o desenvolvedor, esse tipo de informação não é algo muito útil. Se houver um erro que não acontece com uma certa freqüência, fica complicado para o desenvolvedor conseguir identificar e reproduzir o erro, pois não sabe o arquivo e o ponto do código que gerou o erro.
Portanto vamos adotar outra política de erros, que é a utilização da função trigger_error, que vem nativo no PHP desde a versão 4.0.1, e tem como objetivo gerar uma mensagem de erro com os niveis E_USER_ERROR,E_USER_WARNING, E_USER_NOTICE, sendo que desses, apenas o E_USER_ERROR termina a execução do sistema; Outro recurso que essa função oferece é a de omitir a notificação de determinados tipos de erros através da função error_reporting, que recebe por parâmetro o nivel do erro que será exibido. Não vamos entrar em detalhes no funcionamento dessa função, mas caso você tenha interesse, pode-se ler o manual dela clicando aqui;
Para capturar os erros gerados por essa função, deve-se criar uma outra função e configurá-la utilizando a função set_error_handler, como a seguir:
function trata_erro($n_erro , $mensagem , $arquivo , $linha , $contexto ) { echo '<h1>Erro na execução do sistema</h1>'; echo 'Descrição do erro: '.$mensagem.'<br />'; echo 'Local do erro: '.$arquivo.' na linha '.$linha.''; die(); } set_error_handler("trata_erro");
Os parametros recebidos pela função são:
- $n_erro - Tipo do Erro (E_USER_ERROR,E_USER_WARNING, E_USER_NOTICE)
- $mensagem - Mensagem do Erro
- $arquivo - Arquivo onde ocorreu o erro
- $linha - Linha onde ocorreu o erro
- $contexto - Variáveis que existiam no momento em que ocorreu o erro
Com esse mecanismo se torna muito mais fácil para o desenvolvedor poder tratar e identificar os erros, pois temos agora informações do arquivo que gerou o erro, a linha e até as variáveis que existiam no momento que o erro foi lançado, podendo enviar essas informações por email, e/ou grava-las em um log para que possa ser identificada pelo desenvolvedor; Vamos portanto atualizar o nosso Factory;
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 $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 { //Lança erro como E_USER_ERROR para poder finalizar a execução trigger_error('Command não encontrado',E_USER_ERROR); } } } ?>
Com esse mecanismo o tratamento de erros parece ter ficado mais fácil, porém ainda podemos identificar algumas dificuldades; A primeira é que o erro lançado no Factory não é nada mais que um erro de página não encontrada (erro 404) e não deveria ser exibido para o usuário como um erro fatal;
Também não é possível descobrir os passos feitos pelo usuário até a ocorrência do erro, ou seja, imagine a seguinte função que tem por responsabilidade dividir dois números:
function dividir($n1, $n2) { if ($n2 == 0) trigger_error('Não é possivel dividir por zero'); return $n1 / $n2; }
Essa função pode ser usada em vários lugares no sistema, e apenas pelo numero da linha e o nome do arquivo é impossível saber em qual das utilizações foi gerado o erro; Outra dificuldade existente é que quando o erro ocorre, a execução do sistema é interrompida imediatamente, e dependendo do erro ocorrido, não é desejável que isso ocorra, como é o caso do exemplo a seguir, onde a mensagem de boas vindas não será exibida caso o erro em dividir ocorra:
$n1 = $_GET['n1']; $n2 = $_GET['n2']; $valor = dividir($n1,$n2); echo 'Resultado: ',$valor,'<br />'; echo 'Obrigado por visitar nosso site!';
Sendo assim, vamos abordar um novo mecanismo de tratamento de erros, que são as Exceptions, que consiste em uma forma Orientada a Objetos para tratamento de exceções no sistema que já existia em diversas linguagens de programação, e que foi adicionado ao PHP somente a partir da versão 5; Se baseia em uma classe chamada Exception que deve ser lançada quando ocorrer um erro ou uma situação inesperada, e possui a possibilidade de existir um ponto de tratamento da exceção para que, quando uma exceção for lançada, a execução do sistema possa parar imediatamente e direcionar a execução para o ponto de tratamento da exceção, onde o sistema possa se recuperar do erro e continuar executando normalmente; Caso a exceção não for tratada, será executado um tratamento interno do PHP; Para entender melhor, veremos um exemplo:
//Alterando a função dividir para a utilização de exception function dividir($n1,$n2) { if ($n2 == 0) { //Código do erro (numero inteiro - opcional) $codigoDoErro = 10; //Lançando a exceção, (throw = lançar) throw new Exception("Não é possivel dividir por zero",$codigoDoErro); } return $n1/$n2; } //Inicia o bloco que terá as exceções tratadas try { $n1 = $_GET['n1']; $n2 = $_GET['n2']; $valor = dividir($n1,$n2); echo 'Resultado: '.$valor; //Qualquer exceção lançada dentro do bloco irá executar o bloco de código dentro do catch, onde $e é a exceção lançada no erro } catch (Exception $e) { echo 'Não foi possivel calcular a divisão; Motivo: '.$e->getMessage().'<br />'; } //Ocorrendo ou não o erro, a mensagem de agradecimento será exibida echo 'Obrigado por visitar nosso site!';
Qualquer exceção que for lançada dentro do bloco try vai direcionar a execução para o catch presente no final do bloco, que vai tratar a exceção e continuar a execução do sistema normalmente; Dentro da variável $e vem todas as informações do erro gerado, como a mensagem, o código, o arquivo/linha que gerou o erro; Também possui o “trace” da execução, que é um array contendo todos os métodos que foram executados juntamente com seus parâmetros, até chegar no erro lançado, que é uma informação muito útil para o desenvolvedor pois da a possibilidade de ter todos os passos do usuário até o acontecimento do erro; Para maior detalhes, veja a documentação da classe exception aqui;
Outra vantagem do exception é poder criar tipos de erro, e tratá-los de forma diferente; Para isso basta criar uma nova classe de exceção que estende a classe Exception padrão, e adicionar um catch que recebe a nova classe que você criou; Veja como fazer no seguinte exemplo:
//Exceção personalizada para tratamento de erros aritméticos class ExcecaoAritmetica extends Exception { //Valores que serão armazenados no momento de lançar a exceção, e que serão recuperados no tratamento da mesma private $valor1,$valor2; //Construtor personalizado recebendo os valores da exceção public function __construct($mensagem,$codigo,$valor1,$valor2) { parent::__construct($mensagem,$codigo); $this->valor1 = $valor1; $this->valor2 = $valor2; } //Funções personalizadas para a exceção public function getValor1() { return $this->valor1; } public function getValor2() { return $this->valor2; } } //Função dividir lançando exceção especifica function dividir($n1,$n2) { if ($n2 == 0) { $codigoDoErro = 10; //Lançando a exceção throw new ExcecaoAritmetica("Não é possivel dividir por zero",$codigoDoErro,$n1,$n2); } return $n1/$n2; } try { $n1 = $_GET['n1']; $n2 = $_GET['n2']; $valor = dividir($n1,$n2); echo 'Resultado: '.$valor; //Exceções Aritméticas será tratada aqui } catch (ExcecaoMatematica $em) { echo 'Ocorreu um erro aritmético: ',$em->getMessage(),'<br />'; //Utilizando as funções personalizadas echo 'valores informados: ',$em->getValor1(),' e ',$em->getValor2(),'<br />'; //Demais tratamentos pertinentes //As outras exceções serão tratadas aqui } catch (Exception $e) { echo 'Ocorreu um erro desconhecido; Motivo: '.$e->getMessage().'<br />'; } //Ocorrendo ou não o erro, a mensagem de agradecimento será exibida echo 'Obrigado por visitar nosso site!';
Podemos notar que essa é a melhor opção para o nosso tratamento de erros; então vamos aplicá-la no nosso framework; Primeiramente vamos criar uma nova exceção chamada Exception_Pagenotfound, que será utilizada no Factory de Command e tem por objetivo informar que uma página não foi encontrada;
arquivo: lib/Exception/Pagenotfound.php
//Classe apenas para identificar o tipo do erro class Exception_Pagenotfound extends Exception { //Recebe obrigatoriamente a mensagem, e o codigo pode ser omitido public function __construct($mensagem,$codigo = 0) { parent::__construct($mensagem,$codigo); } }
O nosso command ficará da seguinte forma:
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 $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 parametro 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(); } } ?>
O tratamento de erros ficará dentro do index.php, onde toda a exceção ocorrida no processo de carregar o Command e excecutá-lo será tratado utilizando um catch para cada tipo de exceção. Note que a classe Exception padrão do PHP deve vir por ultimo, porque senão o tratamento para as classes específicas não serão executados;
arquivo: index.php
<?php function __autoload($nomeClasse) { require_once 'lib/'.implode('/',explode('_',$nomeClasse)).'.php'; } try { $factory = new Command_Factory(); $factory->createCommand()->execute(); } catch (Exception_Pagenotfound $ep) { echo '<h1>ERRO 404 - Página não encontrada</h1>'; //O tratamento do Erro não vai ser abordado neste artigo } catch (Exception $e) { echo '<h1>ERRO 500 - Erro na execução</h1>'; //O tratamento do Erro não vai ser abordado neste artigo } ?>
A seguir veremos um exemplo de Exception funcionando dentro de um Command:
LerNoticia.php
<?php class LerNoticiaCommand implements Command { public function execute ( ) { //Se não conseguir conectar, lança exceção if (!mysql_connect('localhost','usuario','senha')) { throw new Exception('Não foi possivel conectar com o banco de dados'); } //Se não conseguir selecionar o banco, lança exceção tambem if (!mysql_select_db('banco')) { throw new Exception('Não foi possivel selecionar o banco de dados'); } $id_da_noticia = $_GET['id']; //Verifica se o id passado é um numero if (!is_numeric($id_da_noticia)) { throw new Exception('Parâmetro Inválido: '.$id_da_noticia); } //Realiza a busca da Noticia $noticia = mysql_query('Select * From Noticia Where id = '.$id_da_noticia); //Se não existe a noticia, lança exceção de pagina não encontrada if (mysql_num_rows($noticia) == 0) { throw new Exception_Pagenotfound('Noticia com id '.$id_da_noticia.' não existe'; } //Continua com a exibição da noticia } } ?>
Portando, podemos perceber que as exceções contem uma gama de recursos que facilitam bastante a vida do programador tanto na correção do erro (informações sobre o erro gerado), no tratamento da ocultação do erro (realizar algum caminho alternativo dentro do catch caso o erro ocorra) e na exibição do erro (exibir de forma diferente determinados tipos de erro), agregando muito mais segurança e praticidade no tratamento de erros no Framework;
Igor de paula Says:
Poxa…nao ficou claro em qual pasta e arquivo vai ficar a classe Exception_Pagenotfound…poderia deixar um source para vermos como ficou agora com a classe para tratamentos de erros….
out 25, 2008, 9:13 amCriando um framework PHP do zero usando padrões de projeto Says:
[...] por Everton Emilio Tavares (ezidiuΘgmail·com) - referência [...]
out 25, 2008, 8:03 amKaléu P. D. Caminha » Blog Archive » Padrões de Projeto em PHP Says:
[...] Mais links: Padrão Adapter (exemplo 1 PHP); Construindo um framework PHP com Padrões de Projeto (parte 1, parte 2) [...]
out 25, 2008, 4:19 pmian Says:
Você vai abordar nos próximos artigos View e Model?
(uso de templates seria interessante)
Parabéns!!!
out 25, 2008, 4:45 pmlucas Says:
Muito bom, espero que tenhamos continuações. Ajudam bastante. Quem sabe, umas aulas para um framework de geração de páginas web, seria ótimo. =D
out 25, 2008, 11:12 amJefferson Says:
Ótimo, seria interessante se tratasse sobre os padrões em si em algum capítulo deste desenvolvimento, onde se encontra muitas dúvidas, talvez não seria tão trabalhoso entender a class ‘Exception’ quanto enteder o padrão ao qual ela esta sendo utilizado.
Parabéns, Jefferson.
Ps: Tentando aprender OOP.
out 25, 2008, 9:47 pm