Tratamento de erros, você está fazendo isso errado…

Quando começamos a utilizar PHP aprendemos a tratar erros da seguinte forma: echo em mensagens e retorno boolean. Ex:

function divide($num1, $num2)
{
    if ($num2 == 0) {
        echo 'Você não pode dividir por zero!';
        return false;
    }

    return $num1 / $num2;
}

Muitas vezes paramos neste ponto, e levamos esta forma de tratar erros para as aplicações que desenvolvemos, resultado: CAOS!

Imaginem a seguinte situação (pra deixar ainda pior, vou usar a ext/mysql hahah):

class UserDao
{
     // todos os métodos aqui para conexão...

    public function save(User $user)
    {
        // monta a query na $query

        return mysql_query($query) ? true : false;
    }

    public function getByLogin($login)
    {
        // faz a pesquisa de usuários pelo login
    }
}

class User
{
    // todos atributos e métodos
}

class UserService
{
    public function create($name, $email, $login, $pass)
    {
        $dao = new UserDao();

        if ($dao->getByLogin($login)) {
            echo 'Já existe um usuário com este login';
            return false;
        }

        $user = new User();
        // preenche o objeto com os dados

        if (!$dao->save($user)) {
            echo 'Não foi possível cadastrar no banco de dados';
            return false;

        }

        return true;
    }
}

Agora a grande pergunta: como saber UTILIZANDO PHP se o motivo do erro foi banco de dados ou se foi por já existir um usuário com o login?
Um grande gênio vai dizer “fácil, usa o ob_start(), ob_get_contents() e pega o que foi jogado p/ o buffer de saída…”. Legal né? Fica bem prático de tratar os erros dessa forma, não?

Pois é… para todas as pessoas que tratam erros desse jeito eu apresento a classe Exception: http://www.php.net/manual/en/class.exception.php

Como vocês podem ver, num tem mistério algum em utilizá-la… vamos reescrever nosso exemplo (usando PDO dessa vez =P):

/*
A instância do PDO está configurada para que o tratamento de erros seja feito através de exceptions (PDO::ATTR_ERRMODE =  PDO::ERRMODE_EXCEPTION)
Veja mais em PDO::setAttribute() http://hu2.php.net/manual/en/class.pdo.php
*/
class UserDao
{
     // todos os métodos aqui para conexão...

    public function save(User $user)
    {
        // faz um prepared statement na var $stm - PDO::prepare()
        // a var $params são os dados passados no prepared statement

        $stm->execute($params);

        // Só retorna algo aqui se quiser, pois caso ocorram erros de query será lançada uma PDOException
    }

    public function getByLogin($login)
    {
        // faz a pesquisa de usuários pelo login
    }
}

class User
{
    // todos atributos e métodos
}

class UserAlreadyExistsException extends Exception
{
   // Não precisamos sobrecarregar nenhum método neste caso
}

class UserService
{
    public function create($name, $email, $login, $pass)
    {
        $dao = new UserDao();

        if ($dao->getByLogin($login)) {
            throw new UserAlreadyExistsException('Já existe um usuário cadastrado com o login informado!');
        }

        $user = new User();
        // preenche o objeto com os dados

        $dao->save($user);
    }
}

O código ficou um pouco mais simples, e agora dá pra gente tratar erros de uma forma mais simples:

try {
    $service = new UserService();
    $service->create('Teste', 'teste@teste.com', 'teste', 'teste');
} catch (PDOException $e) {
    // Entrará aqui caso ocorra algum erro de query
} catch (UserAlreadyExistsException $e) {
   // Entrará aqui caso já exista algum usuário criado com o login "teste"
} catch (Exception $e) {
   // Entrará aqui caso seja lançada qualquer outra Exception
}

Assim conseguimos tratar erros para cada situação.
Apesar de ter colocado ali, não indico de forma alguma capturar (catch) a classe Exception.
Na minha opinião cada erro tem um propósito, portanto deve ser estabelecido como cada possibilidade de erro deve ser gerenciada (algumas interrompem o fluxo, outras não).

Funcionalidades que você ganha ao utilizar exceptions:

  • Stack trace (lista de métodos/funções que foram chamados antes do lançamento da exception);
  • Linha e arquivo que foi lançada a exception;
  • Exceptions aninhadas PHP 5.3+ (utilizar uma exception como causa de outra)

É isso ae…

Anúncios

Tags:,

About lcobucci

Passionate PHP Developer

6 responses to “Tratamento de erros, você está fazendo isso errado…”

  1. gabrielfs7 says :

    Gostei. Tratamento com exceção, sempre a melhor opção! 😉

  2. Jeferson Perito says :

    Faço tratamento de erros com Exepctions desde o ano passado… graças a você 😛

    Agiliza muito os testes de unidade com erros lançados como Excessões e o código realmente fica mais robusto. Faça um tutorial sobre testes de aceitação com selenium ou testes de unidade utilizando mock/stub. Acho uma boa pedida. 🙂

  3. jean says :

    Brother.. muito bom mesmo!
    Novamente, vou compartilhar!

    Parabéns!

  4. Rubens Takiguti Ribeiro says :

    Olá, Luís. Parabéns pelo blog.

    Sobre o post, tenho um certo preconceito em relação à utilização de Exceptions em todos os níveis do sistema. Para o baixo nível, eu acho bastante razoável: um motivo ocasiona uma exceção.

    Porém, no alto nível, há situações em que é útil mostrar para o usuário vários motivos que causaram um erro em determinada ação.

    Por exemplo, o usuário pode preencher o login com caracteres inválidos E também não utilizar o número mínimo de caracteres. Se soltarmos uma exception ao detectar um dos motivos, não capturamos o segundo. Ou seja, o usuário precisa preencher duas vezes o login para perceber os dois erros.

    De fato, podemos criar um tipo de Exception com um pacote de erros, mas acho um pouco exagerada a solução. Você conhece algo que contorne esta situação?

    Até

    • lcobucci says :

      Olá Rubens, Obrigado!

      Na minha visão os erros devem ser tratados tanto no lado do servidor quando no lado do cliente. Do lado do servidor para termos segurança e robustez e no lado do cliente para termos respostas rápidas e com o cuidado com a interface da aplicação.

      No caso que você exemplificou eu colocaria as verificações COM exceptions no lado do servidor, e com Javascript (considerando ser uma aplicação HTML) faria as mesmas validações, porém de forma amigável. Ou seja, as exceptions apenas seriam lançadas caso o lado do cliente falhasse.

      Dessa forma você mantém um tratamento de erros simples na parte do PHP e faz com que o usuário saiba (antes mesmo de enviar a requisição ao servidor) dos problemas que impedem a ação dele.

      Em relação a uma exception para um pacote de erros eu sou um pouco relutante, eu gosto das coisas bem nomeadas e definidas… Neste caso eu prefiro usar a funcionalidade de exceptions aninhadas que o PHP 5.3 nos trouxe, onde podemos definir a causa da ocorrência de um erro (tipo, a exception \Lcobucci\Utils\Exception\AuthenticationError foi causada por uma \InvalidArgumentException isso ajuda bastante =D).

      Espero ter ajudado!

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: