Try Catch Finally em PHP

Artigo que apresenta o funcionamento de exceções no PHP, os blocos try catch, além do recurso finally, adicionado no PHP 5.5.

Introdução

Dando continuidade aos artigos relacionados às novidades do PHP 5.5, este artigo irá abordar sobre o suporte ao bloco "finally" nas estruturas try/catch/finally do PHP.

Porém, antes de apresentar o novo recurso ("finally"), vou contextualizar o assunto de Exceptions para os leitores que ainda não o conhecem.

Como funcionam as Exceptions

Exception é um mecanismo das linguagens de programação que permite especificar condições para que uma determinada funcionalidade seja executada, e permite detectar quando uma destas condições não foi satisfeita, ou seja, detectar quando uma exceção foi lançada.

Por exemplo, podemos fazer uma função que divide um número por outro. A função precisa garantir que o dividendo e o divisor sejam números (int ou float) e que o divisor seja diferente de zero, para que a divisão seja possível. Veja o exemplo:

function dividir($dividendo, $divisor) {
    if (!is_int($dividendo) && !is_float($dividendo)) {
        throw new InvalidArgumentException('O dividendo não é um número', 0);
    }
    if (!is_int($divisor) && !is_float($divisor)) {
        throw new InvalidArgumentException('O divisor não é um número', 1);
    }
    if ($divisor == 0) {
        throw new RangeException('Não é possível realizar uma divisão por zero', 0);
    }
    $resultado = $dividendo / $divisor;
    return $resultado;
}

Em PHP, a notação para se emitir a exception é com a palavra reservada throw seguida de um objeto derivado da classe Exception, que é nativa da linguagem. Este objeto pode ser construído durante a sua emissão, ou então pode ser construído, atribuído a uma variável, (opcionalmente) manipulado e depois emitido. No exemplo acima, utilizamos as exceptions InvalidArgumentException e RangeException, que são exceções nativas do PHP, definidas pela extensão SPL, e que são derivadas da classe Exception.

Sabendo que a função dividir pode emitir exception, o código que utiliza esta função pode optar por realizar algum tratamento, caso uma exceção ocorra. Isso é feito através de um bloco try/catch:

try {
    $resultado = dividir(6, 0);
} catch (InvalidArgumentException $e) {
    ...
} catch (RangeException $e) {
    ...
} catch (Exception $e) {
    ...
}

Neste caso o bloco try é uma região do código que conseguirá recuperar alguma exceção que ocorra em alguma de suas instruções. O bloco catch especifica o tipo de exceção que será capturada e a variável que vai receber o objeto com a exceção. O que será feito no bloco catch depende do que o programador deseja. Não é obrigatório capturar todos os tipos de exceção. Se você captura o tipo Exception, automaticamente captura qualquer tipo de exceção, pois a "captura" é feita de acordo com a herança da exceção. Então, se um tipo de exceção não é definido em nenhum catch, mas este tipo de exceção deriva de um outro tipo mais abrangente, que é capturado em algum catch, então este catch vai capturar a exceção mais específica.

Além disso, as exceções são escaladas através de diferentes níveis de chamadas de funções. Ou seja, se uma função "a" chama uma função "b" que chama uma função "c", e "c" emite uma exceção, então se "b" não capturar a exceção, a função "a" tem condições de capturar. Caso ninguém capture a exceção, até chegar ao escopo global de execução do PHP, então o script é abortado, como se fosse um erro fatal do PHP. Porém, existe uma forma de capturar exceções que chegaram ao escopo global: definindo um "tratador de exceções global" definido pela função set_exception_handler.

Como usar Exceptions

Sabendo como funcionam as exceptions, abrimos margem para diferentes formas de utilização. É importante que um projeto tenha bem definido qual será a abordagem sobre como as exceções serão capturadas, para que mantenha um padrão.

Uma forma conveniente é criar uma classe de exceção para cada possível exceção da aplicação e a captura da exceção ser específica. Assim, ao capturar a exceção, ela pode devolver informações úteis para que o problema seja "contornado" de alguma outra forma, quando possível. Ou então, pode retornar os dados necessários para que seja gerado um log de erro que ajude a depurar o problema (por exemplo, o backtrace da execução). Criando uma classe para cada exceção, esta classe ainda pode ter métodos específicos para contornar a exceção.

Uma outra abordagem é utilizar uma única classe de exceção para toda a aplicação e definir códigos numéricos para cada possível erro de uma função. Note, no exemplo mostrado anteriormente, que ao emitirmos a exceção, passamos uma mensagem e um código numérico. Assim, ao capturar a exceção podemos checar qual foi o código retornado e tratar a exceção. Embora a abordagem seja mais prática, não é possível criar nada específico na classe de exceção, já que ela é global. Além disso, a legibilidade do código fica pior.

Observação: não é recomendado utilizar a mensagem lançada pela exceção para ser exibida para o usuário final da aplicação, pois algumas delas podem ser lançadas pelo próprio PHP, em inglês. Além disso, para prover a internacionalização do seu sistema, as mensagens precisam estar adequadas à localidade do usuário.

Para conhecer a estrutura da classe Exception, seus atributos e métodos, consulte a documentação oficial do PHP: Exceções no PHP 5.

Como funciona o finally

O finally é um outro bloco de instruções que é colocado após um bloco try ou após o último bloco catch. Ele especifica quais instruções devem ser executadas após as instruções do try ou do catch serem executadas. Por exemplo, se o bloco try criou um arquivo temporário e ocorreu alguma exceção durante a sua manipulação (antes dele ser apagado), o bloco finally pode, por exemplo, forçar a remoção do arquivo temporário.

Caso existam blocos catch e uma exceção seja capturada, as instruções do finnaly serão executadas apenas após as instruções do catch que capturou a exceção. Caso seja um try sem nenhum catch ou um try que não lançou nenhuma exceção, o finally será executado após a execução do try. Veja um exemplo da ordem que as coisas funcionam:

// O codigo abaixo vai imprimir: 12345
echo '1';
try {
    echo '2';
    throw new Exception();
} catch (Exception $e) {
    echo '3';
} finally {
    echo '4';
}
echo '5';

Caso não ocorra a exceção, o bloco finally é executado após o try:

// O codigo abaixo vai imprimir: 1245
echo '1';
try {
    echo '2';
} catch (Exception $e) {
    echo '3';
} finally {
    echo '4';
}
echo '5';

Esta regra só muda quando o bloco try ou catch retorna algum valor (quando dentro de uma função, por exemplo). Neste caso, as intruções do finally serão executadas antes de ocorrer o retorno.

Portanto, o bloco finally é útil para agrupar instruções que precisam ser executadas antes de sair do try. Ele torna o código mais legível e evita duplicidade de código.

12 comentários

Anônimo disse...

Amigo vc conhece alguma IDE que já suporte o PHP 5.5 semelhante ao netbeans?

Anônimo disse...

Rubens, poderia responder uma mensagem que enviei a você no about.me com nick de TRXPLZ0

Luiz Danin disse...

Olá Rubens,
Para quem programa em PHP mas não entende praticamente nada do Framework Zend e como utiliza-lo, contudo gostaria de aprender ou pelo menos dar seus primeiros passos você recomenda algum material de estudo para iniciantes?

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Luiz
Sinceramente, acho que o Zend Framework não é o melhor framework para quem ainda é iniciante na área de frameworks. Talvez seja melhor começar pelo Yii, Synfony, ou mesmo o CodeIgniter ou Cake (embora estes 2 últimos sejam muito simples e um pouco mais restritivos, dá pra ter uma noção sobre como estruturar a aplicação com MVC, e onde um framework pode ajudar de fato).

O melhor lugar para estudá-los é com os tutoriais oferecidos nas próprias páginas destes frameworks. Acho que é uma boa ver pelo menos como é uma aplicação simples em cada um deles.

Luiz Danin disse...

Tudo bem entendo,
Obrigado pela dica, também tive informações que o Zend é "carne de cabeça" para quem tá começando no universo dos frameworks. Gostaria de saber se me aconselhas o Phalcon, pois pretendo aprender a utilizar algum framework PHP e este pelo que pesquisei parece ser bem interessante.
Outra coisa, vlw por ser prestativo, poucos autores de blog respondem com tanta rapidez e são solicitos.

Rubens Takiguti Ribeiro (autor do blog) disse...

Oi, Luiz
Sobre o Phalcon, eu cheguei a vê-lo ano passado, quando ainda estava para lançar a versão 1.0. Hoje em dia ele já está prestes a lançar a versão 2.0. Ele está evoluindo rápido e é um framework promissor, pois tem muitos recursos bem estruturados e também tem ótima performance.

Porém, é preciso estar atendo à sua característica fundamental: diferente da maioria dos frameworks PHP, que são escritos em PHP, o Phalcon é um framework escrito em C, e disponibilizado na forma de módulo do PHP. Ou seja, não é um bom framework para se utilizar em projetos que serão hospedados em locais em que não se tem acesso total ao servidor (para instalar módulos do PHP, por exemplo).

Outro framework similar ao Phalcon, mas com muito menos recursos é o YAF-PHP. Ele também é feito em C, disponibilizado como módulo do PHP. Embora tenha poucos recursos e, teoricamente seria fácil aprendê-lo, ele tem um defeito que é a pouca documentação estilo "tutorial".

gilson.edfisica disse...

Olhá Rubens!
Após fazer uma conexão mysql com o BD, realizar a instrução select numa tabela, retorna 0 registros. Mesmo colocando o bloco abaixo, ele continua retornando a exceção em tela.
try{
$n = mysql_num_rows($result); //zero registros exception
}catch{
$n = 0;
}

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Gilson
Inicialmente, o PHP não tinha suporte a exceptions. Por isso, as funções mais antigas não tem este tipo de controle sobre erros. Ao invés disso, elas controlam via disparo de notificações, que podem ser NOTICE, WARNING ou ERROR (entre outros). Sugiro que leia o artigo a seguir para entender melhor:
http://rubsphp.blogspot.com.br/2010/10/controle-de-erros.html

Neste seu caso, nem se trata de um erro, nem exception. Você fez uma consulta com sucesso o obteve zero resultados com sucesso. Não é um erro uma consulta não devolver nada. Mas você pode fazer uma função que obrigatoriamente precisa retornar algo e, por isso, pode optar por emitir uma exception caso algo não seja encontrado. Mas aí, é você que tem que soltar esta exception.

Aliás, sugiro que utilize PDO ao invés das funções do mysql, pois estas já estão depreciadas.
http://rubsphp.blogspot.com.br/2010/09/pdo.html

Anônimo disse...

Você é feio demais. Tire sua foto daí. Para poder ler este artigo tive que usar o F12 e remover a tag div#cartao_pessoal