Exceptions no PHP5
por xDeCo

Controle de fluxo utilizando exceções (exceptions), try e catch no PHP5. Muitos já viram e não sabem como usar, outros nem sabem o que estão perdendo. O melhor controle de fluxo para programação orientada a objeto. Mais um dos grandes avanços no PHP5.



Exceptions são utilizadas para controlar o fluxo do código de maneira simples. Apesar do nome exceção, não significa que seja um controle de erro absurdo ou fatal e muito menos que o usuário do sistema saiba que ocorreu. Exception não é apenas um controle de erro, isso é apenas uma das funcionalidades. O principal é o controle de fluxo, inclusive, com código muito limpo e orientado.

Vamos compreender o assunto olhando uma maneira antiga:


<?php
/**
 * Retorna o usuario para login
 * @param string $user login do usuario
 * @param string $password senha do usuario
 * @return User
 */
function login($user$password)
{
    
// login valido
    
if ($user == 'usuario') {
        
// senha valida
        
if ($password == 'senha') {
            return new 
User($user);
        } else {
            
// senha invalida
            
return -2;
        }
    } else {
        
// login invalido
        
return -1;
    }
}

// usando a funcao acima
$user login('eu''senha');

// tratando o erro
if (!is_object($user)) {
    switch (
$user) {
        case -
1:
            print 
'usuario invalido';
            break;
            
        case -
2:
            print 
'senha invalida';
            break;
    }
} else {
    
// faz alguma coisa
}
?>


Esse tipo de tratamento é bem comum na programação sem exceptions em casos que é necessário retornar vários tipos de erros.

Exception é apenas uma classe, ela praticamente não faz nada. A parte que realmente faz todo o processo é a linguagem. Não é uma coisa que se possa criar, é nativa.

O nome correto para esse sistema é "Tratamento de exceções". Para disparar uma exceção, que no exemplo anterior seria o -1 e o -2 você deve utilizar um comando chamado "throw" (jogar) criando uma exceção.

Exemplo:

<?php
function qualquer() 
{
    
throw new Exception();
}
?>


É simples. Mas, além disso a classe de exceção possui alguns atributos métodos que você pode olhar melhor no manual do php.net. O principal é o parâmetro opcioanal $message no construtor da classe de exceção.

Vamos ver o exemplo anterior utilizando o disparo de exceções:


<?php
/**
 * Retorna o usuario para login
 * @param string $user login do usuario
 * @param string $password senha do usuario
 * @return User
 */
function login($user$password)
{
    
// login valido
    
if ($user != 'usuario') {
        
throw new Exception('login invalido');
    }
    
// senha invalida
    
if ($password != 'senha') {
        
throw new Exception('senha invalida');
    }
    return new 
User($user);
}
?>


Note acima que eu não precisei parar a estrutura de código. O comando "throw" interrompe a execução de todo o bloco e retorna para o ponto de chamada, quase como um "return", porém não retorna nada.

Inclusive, acontece efeito em cascata. A exceção não para só o método que jogou a ela, ela vai fazendo o caminho inverso e vai parando todos os métodos anteriores. Complicou? Não se preucupe.



Na página anterior você apenas viu como jogar exceções. Agora vamos ver como tratar elas utilizando os comandos "try" (tentar) e "catch" (pegar/prender).

Seguindo o exemplo anterior utilizando exceções.


<?php
// tentar fazer o login
try {
    
login'eu' 'senha' );
    print 
'se acima sair uma exceção, essa linha não será executada';
catchException $e ) {
    
// falhou...
    
print $e->getMessage();
}
?>


Note que ela interrompe o bloco de código dentro do "try" e pula para o "catch" caso houver erro.

Obvio que não é só isso. O tratamento de exceções é muito mais poderoso. No exemplo anterior você não diferenciou uma exceção da outra. Agora vamos ver isso mais detalhado. A exceção como é uma classe, você pode herdar e fazer o que quiser, incluindo fazer log, etc. Mas a parte que você mais vai utilizar é apenas o nome da nova classe para controlar o fluxo.

Um exemplo mais detalhado:

<?php
class InvalidLogin extends Exception { }
class 
InvalidPassword extends Exception { }

function 
login($user$password)
{
    
// login valido
    
if ($user != 'usuario') {
        
throw new InvalidLogin($login);
    }
    
// senha invalida
    
if ($password != 'senha') {
        
throw new InvalidPassword($password);
    }
    return new 
User($user);
}

// tentar fazer o login
try {
    
login('eu''senha');
    print 
'se acima sair uma exceção, essa linha não será executada';
catch (InvalidLogin $e) {
    
// faz alguma coisa
    
print $e->getMessage();
catch (InvalidPassword $e) {
    
// faz qualquer coisa
    
print $e->getMessage();
}
?>


Destrinchando o "catch". O primeiro parâmetro é o nome da classe de exceção ou super classe. Sim, super classe, você pode utilizar polimorfismo para pegar as exceções.


<?php
try 
{
    
login('eu''senha');
catch (Exception $e) {
    print 
$e->getMessage();
}
?>


Logo, o "catch" acima pega qualquer exceção, já que toda exceção deve ser herdada da classe Exception para ser jogada.

Porém não controla nada o seu fluxo, o correto é sempre criar um conjunto exclusivo de exceções para cada erro de uma classe. Não se espante, é comum você encontrar classes com mais de 10 exceções. Geralmente você declara todas as exceções em cima da classe que vai jogar para ficar mais fácil de ler o código.


Como eu disse anteriormente, um "throw" provoca um efeito em cascata e interrompe os códigos das chamadas anteriores até que a exceção seja tratada.

Vamos a mais um exemplo, uma classe:


<?php
class AlreadyExists extends Exception { }
class 
NotExists extends Exception { }
class 
InvalidDirName extends Exception { }

class 
Disk 
{
    
/**
     * @var string $path Nome da pasta atual
     */
    
protected $path;

    
/**
     * Construtor
     * @param string $path Nome da pasta do disco
     */
    
public function __construct($path null)
    {
        if (
$path !== null) {
            
$this->open($path);
        }
    }

    
/**
     * Abre uma pasta para manipulação
     * @param string $path Nome da pasta
     */
    
public function open($path)
    {
        if (
is_dir($path)) {
            
$this->path $path;
        } else {
            
throw NotExists($path);
        }
    }

    
/**
     * Cria uma nova pasta
     * @param string $path Nome da nova pasta
     */
    
public function make($path)
    {
        if (
file_exists($path)) {
            
throw new AlreadyExists($path);
        }
        if (!
ereg('[a-zA-Z]'$path)) {
            
throw new InvalidDirName($path);
        }
        
mkdir($path);
        
$this->open($path);
    }
}
?>


Uso:

<?php
try 
{
    
$disk = new Disk('lol');
catch (NotExists $e) {
    
/*
     * nesse caso o obj $disk nao existe
     * pois o throw do metodo open, fez um efeito em cascata
     * e nao deixou o construtor terminar
     */
    
print 'diretorio invalido';
}
?>


Outro exemplo, controlando o fluxo:


<?php
$path 
'lol';
$disk = new Disk();

try {
    
// tenta abrir
    
$disk->open($path);
catch (NotExists $e) {
    
/**
     * A pasta ainda nao existe
     * esse é um exemplo onde a exceção não é um erro
     */
    
try {
        
$disk->make($path);
    } 
catch (InvalidDirName $e) {
        
// isso é um erro
        
print 'Nome de diretorio invalido: ' $e->getMessage();
    }
}
?>



Tratamento de exceções é um meio muito bom de controlar o fluxo. Invista um pouco de tempo criando classes de teste até dominar esse assunto.

Lembre-se, você pode tratar a exceção em qualquer nível. Funciona como uma peneira, você pode jogar uma exceção na décima camada e pegar na terceira ou na primeira, como preferir, isso vai interromper todo os codigos do meio.

Um exemplo simples, jogar uma exceção do banco de dados e tratar só na camanda mais próxima da interface, já que não se pode fazer mais nada se uma query não for executada corretamente e então mostrar o erro no template colocando um texto pré-definido para aquela exceção.

O tratamento de exceção tem um jeito bem diferente de se programar, no começo parece chato por falta de costume, parece que o código as vezes fica maior. Depois você aprende a usar corretamente e não consegue mais largar.