|
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';
} catch( Exception $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.
|
|
|