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;

Faça o download do resultado final do artigo clicando aqui.

6 responses so far, say something?

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

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

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

  3. Kalé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) [...]

  4. ian Says:

    Você vai abordar nos próximos artigos View e Model?
    (uso de templates seria interessante)

    Parabéns!!!

  5. lucas 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

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

Deixe um comentário