Para ver os artigos anteriores desta série, clique aqui.
Olá meus caros. De volta. "Que você viva em tempos interessantes". Esta antiga maldição chinesa (!?) explica muito bem a sensação que toma conta deste seu humilde autor. Muito a fazer, muito a compreender, pouco tempo. Desculpas à parte, retorno à esta série para falar mais um pouco sobre Exceptions, um conceito fundamental que talvez ajude alguns de vocês a poupar um tanto desse tão precioso tempo; afinal, como bem disse Benjamin Franklin, o tempo perdido nunca mais será encontrado.
Muito bem, senhores, o que faz nossas Exceptions tão especiais? Para entender, precisamos pensar na forma como gerenciamos erros em nossas aplicações. Ou melhor, precisamos pensar na forma como lidamos com erros em nossas vidas. Talvez isso complique as coisas, mas falar sobre a vida sempre é mais interessante, não?
Joãozinho trabalha numa fábrica de automóveis. (é um exemplo, ok? Não quero entrar em discussões morais sobre trabalho infantil, capitalismo, revolução industrial, etc e tal. Tudo bem, é um exemplo estúpido. Podemos ir em frente?) Joãozinho aperta parafusos na linha de montagem, e sua supervisora é a Mariazinha. O chefe da Mariazinha é o Aristides, que fica lá em cima no escritório.
Bom, você sabe que numa fábrica todo tipo de problema pode acontecer. Alguém pode esquecer de mandar o parafuso para o Joãozinho, o parafuso que foi mandado pode estar com defeito, o próprio Joãozinho pode vacilar e não apertar o parafuso... Sabe lá o que pode acontecer. Diante desse cenário, como você faz para gerenciar todos os erros que podem acontecer? Dá uma lista de IFs infinita para o Joãozinho para que ele mesmo resolva tudo? "Se o parafuso estiver com defeito, corra até a sala do Aristides e aperte o botão para parar as máquinas". "Se você dormir no ponto e não apertar o parafuso, corra até a sala do Aristides e aperte o botão para parar as máquinas", e assim por diante? Hmm... não sei se vocês estão entendendo, mas já está dando preguiça só de pensar em fazer essa lista. E a preguiça, amigos, é o nosso sentido-aranha, é nosso superpoder psiônico que avisa quando alguma coisa está esquisita no ar. Imagina se a Mariazinha tiver 50 funcionários sob sua supervisão, vai ter gente correndo pra tudo o que é lado, a sala do Aristides vai ficar totalmente destruída, e produção que é bom nada.
Apresento-lhes, então, as Exceptions, e seus companheiros inseparáveis Try, Catch e Throw. Vamos olhar para o problema acima de um outro ângulo. Imagine se a gente pudesse dizer o seguinte pro pessoal da fábrica:
Olha, Joãozinho, você vai tentar apertar o parafuso. Qualquer problema que você tiver, escreve num papel, amassa até virar uma bola e arremessa pra Mariazinha. A Mariazinha pega a bola, e se não for um problema que ela saiba resolver, amassa de novo e arremessa pro Aristides. Se ele também não souber resolver, a gente para a linha de produção na hora.
Tentar... Pegar... Arremessar... Vocês... sacaram? (se você viu Warriors, na versão legendada, está rolando no chão de rir nesse momento). [Agora relendo esse artigo me parece claro que o criador deste conceito gostava muito de baseball, ou cricket] Se vocês ainda não sacaram, vamos escrever um pouco de (pseudo)código:
/*
* Created on 02/05/2007
*
*/
class Apertador {
function apertarParafuso(){
echo "parafuso apertado!";
}
function oopa($descricao){
echo "Apertador: oopa! Deu pau! Segura aí supervisor!";
throw new Exception($descricao); // amassa e joga a bolinha pra cima!
}
}
class Supervisor{
function mandaApertar(){
try {
$joaozinho = new Apertador();
$joaozinho->apertarParafuso();
$joaozinho->oopa("Parafuso com defeito");
}
catch (Exception $e){
echo "Supervisor: xi! Exception! Não sei o que fazer! Toma aí chefe!";
throw $e; // peguei a bolinha, mas não sei o que fazer com ela... manda pra cima!
}
}
}
class Chefe{
function vamosTrabalhar(){
try {
$mariazinha = new Supervisor();
$mariazinha->mandaApertar();
} catch (Exception $e) {
echo "Chefe: Para tudo! Quero saber o que aconteceu aqui!"
echo $e;
die("Parando as máquinas!");
}
}
}
// Muito bem, agora a gente contrata o Aristides:
$aristides = new Chefe();
$aristides->vamosTrabalhar();
?>
Rode esse script e veja o resultado. Que interessante! Basta o Joãozinho mandar o erro pra cima que todos na cadeia (as classes que chamam a classe Apertador, neste caso) recebam aquela Exception, graças ao uso do comando Try. Tudo o que roda dentro de um bloco Try é monitorado, e caso uma Exception seja "jogada" ou "arremessada" pra cima o controle vai para o bloco Catch imediatamente abaixo.
Perceba também que ao gerarmos uma Exception, ela já é criada com um monte de informações importantes automaticamente: em qual arquivo e linha que o erro aconteceu, um Trace completo passando por todo o código até o Catch que "pegou" aquela Exception e muito mais. Para ver um bom resumo, basta mandar imprimir a variável que contém a Exception; mas você também pode acessar as propriedades do objeto separadamente usando métodos como $e->getMessage(), $e->getLine(), $e->getFile() entre outras. Não é bom isso?
Com as exceptions nosso trabalho passa a ser somente colocar os Throws nos locais onde erros acontecem, deixando o tratamento desses erros para classes mais acima na hierarquia. Mesmo que você não trate estas Exceptions com blocos Try/Catch a coisa funciona: você pode deixar a exception subir na hierarquia até chegar ao topo, pois toda exception que chega ao topo sem ser "pega" gera um erro fatal. Então, caros amigos, vocês podem trabalhar inicialmente só colocando throws no código, e, ao final, refinar o tratamento distribuindo blocos try/catch pelas classes da hierarquia. Muito bonito.
Mas tem uma coisa ainda mais bonita. Sim, amigos, a classe Exception pode ser estendida. É uma classe como outra qualquer. Isso quer dizer que no nosso caso você poderia criar a exception DefeitoParafusoException, que já conteria a mensagem de erro e mais outras informações que você necessita sem precisar nunca mais digitar. Exemplo:
class DefeitoParafusoException extends Exception {
function __construct(){
parent::__construct();
$this->message = "Defeito no parafuso!";
}
}
Sacaram? Agora toda vez que você criar uma nova Exception do tipo DefeitoParafusoException a mensagem já será colocada por default na variável $this->message da Exception em questão! Imagine o que você pode fazer com isso. Mas ainda tem mais. Imagine agora que você não quer parar a produção simplesmente por causa de um parafuso com defeito. Muito bem, então vamos fazer com que os Supervisores comecem a "Pegar" este tipo de Exception para não incomodar o chefe:
class Supervisor{
function mandaApertar(){
try {
$joaozinho = new Apertador();
$joaozinho->apertarParafuso();
$joaozinho->oopa("Parafuso com defeito");
}
catch (DefeitoParafusoException $e){
echo "Supervisor: é só um maldito parafusinho, diabos! Continuem!";
}
catch (Exception $e){
echo "Supervisor: xi! Exception! Não sei o que fazer! Toma aí chefe!";
throw $e; // peguei a bolinha, mas não sei o que fazer com ela... manda pra cima!
}
}
}
pra ver isso funcionando, precisamos alterar a função Apertador::oopa() para mandar uma DefeitoParafusoException:
function oopa(){
echo "Apertador: oopa! Deu pau! Segura aí supervisor!";
throw new DefeitoParafusoException(); // não precisa mais da descrição...
}
E pronto. Vocês perceberam que podemos utilizar vários blocos de catch um embaixo do outro? Essa é a beleza desta solução. Podemos começar com um tratamento de erros bem genérico, e depois ir especializando com novas subclasses, sempre lembrando de colocar as subclasses mais específicas, neste caso a DefeitoParafusoException nos blocos anteriores, deixando o catch genérico (Exception $e) por último.
Por hoje é só, pessoal, dúvidas, reclamações, elogios e doações são bem-vindas. Grande abraço a todos.