Utilizando as exceptions da SPL do PHP

Artigo que apresenta a lista de exceptions disponíveis pela SPL - Standard PHP Library e os pontos em que podem ser usadas.

Introdução

No artigo Try Catch Finally, vimos sobre o conceito de exceptions e seu funcionamento no PHP (onde/como são emitidas e onde/como são capturadas). Neste artigo, veremos sobre a correta utilização das exceptions definidas pela extensão SPL, então, se você ainda não conhece bem o conceito, recomendo a leitura daquele outro post antes de continuar.

Infelizmente, a documentação destas exceptions não deixa muito claro os possíveis pontos em qua cada uma pode ser usada. Portanto, as recomendações que faço são com base naquilo que compreendi da documentação.

Lista de Exceptions disponíveis pela SPL

Primeiramente, vamos listar as exceptions disponíveis pela SPL, seguindo sua estrutura hierárquica de heranças:

  • LogicException
    • BadFunctionCallException
      • BadMethodCallException
    • DomainException
    • InvalidArgumentException
    • LengthException
    • OutOfRangeException
  • RuntimeException
    • OutOfBoundsException
    • OverflowException
    • RangeException
    • UnderflowException
    • UnexpectedValueException

Agora, vamos às explicações...

Como podemos notar, existem dois grandes grupos de exceptions, que são: LogicException e RuntimeException. Cada um destes grupos possuem características em comum, que veremos a seguir.


Erros de Lógica

O grupo LogicException representa os erros de lógica no código-fonte e, caso sejam emitidos em algum momento, significa que o código-fonte possui algum problema e precisa ser corrigido. Ou seja: "a culpa é do programador". No entanto, a exception LogicException só deve ser emitida por um código quando nenhuma de suas exceptions derivadas representem o problema encontrado. Afinal, todas as exceptions derivadas dela também são consideradas do tipo dela e, portanto, o que foi falado sobre ela se aplica às classes filhas.

A exception BadFunctionCallException e sua exception derivada BadMethodCallException representam problemas no código-fonte no que diz respeito à utilização de determinada função ou método, respectivamente. Por exemplo, se a função/método obrigatoriamente precisava de um parâmetro, que não foi passado. Ou se um método só poderia ser executado sob alguma condição como, por exemplo, um atributo tenha sido preenchido anteriormente, ou seja, o objeto deveria estar em determinado "estado" para se chamar o método. Portanto, o código-fonte precisa ser corrigido.

A exception DomainException é usada quando algum valor não respeita um domínio de valores válidos, a nível de código-fonte. Ou seja, quando o programador passa algum valor que não está dentro do conjunto de valores esperados. Por exemplo, quando se tem um campo enum, que precisa ter um valor definido por um conjunto limitado de constantes. Se um valor diferente é recebido, então quer dizer que o programador não usou uma constante apropriada e o código precisa ser corrigido.

A exception InvalidArgumentException é usada quando algum parâmetro de uma função ou método não possui um valor esperado. Diferente da DomainException, esta exception não diz respeito apenas se um valor não está em determinado conjunto de valores conhecido. Além disso, ela só deve ser usada quando algum parâmetro foi passado de forma incorreta. Por exemplo, se esperava que o valor passado fosse do tipo "int", ou então, o valor passado fosse um objeto de determinada classe com algum "estado" específico. Novamente: só utilize esta exception se o valor inválido foi causado porque o programador informou algo inesperado.

A exception LengthException é usada quando se espera um comprimento e o programador usa um comprimento/tamanho inválido. Ou seja, quando existe uma limitação previamente definida para um valor, mas o programador informou um valor fora deste limite.

A exception OutOfRangeException é usada quando se está manipulando uma coleção de dados e o programador tenta acessar um item da coleção através de um índice que está fora da faixa permitida. Normalmente, esta coleção é criada pelo próprio programador, então, se ele está acessando uma determinada posição, ela precisa ser válida, caso contrário o código-fonte possui um problema lógico e precisa ser corrigido.


Erros em tempo de execução

O grupo RuntimeException representa os erros que só se consegue detectar quando o script está em funcionamento. Por exemplo, quando um script tenta se conectar ou recuperar um recurso externo (como um banco de dados ou uma requisição com cURL) e, por alguma falha no meio de campo, não foi possível. Neste caso, a culpa não é do programador [ufa!] e não há nada que se possa fazer no código-fonte para corrigir. A única coisa que se pode fazer é tratar a exception para que seja exibida uma mensagem apropriada para o usuário final ou que seja feita uma nova tentativa, que também estará sujeita à exceções. Assim como foi dito sobre a LogicException, esta exception também só deve ser emitida quando nenhuma de suas exceptions derivadas represente o problema detectado.

A exception OutOfBoundsException é muito parecida com a OutOfRangeException, pois é emitida quando um valor índice está fora da faixa permitida. A diferença é que OutOfBoundsException só pode ser detectada quando o sistema está sendo executado. Por exemplo, se um usuário passa o ID de um registro do BD que ele pretende ver, mas este ID não existe no BD, então pode-se considerar que ocorreu um erro de limite, mas a culpa não é do programador, pois o usuário do sistema que informou um ID inválido.

A exception OverflowException e UnderflowException são usadas, respectivamente, quando se tenta adicionar um elemento em um container que já está na sua capacidade máxima e quando se tenta remover um elemento de um container que já está vazio. Porém, quando estas operações não estiverem relacionadas com o código-fonte gerado pelo programador, mas por algum fator externo, como uma solicitação inválida do usuário.

A exception RangeException é usada quando algum valor não respeita um domínio de valores válidos, detectável apenas em tempo de execução. Ou seja, é similar à DomainException, mas ela só pode ser detectada em tempo de execução. Também pode ser usada quando o usuário precisava informar um valor dentro de um conjunto definido, mas informou algo inválido.

A exception UnexpectedValueException é usada quando algum valor não tem as características esperadas. Por exemplo, em PHP existem várias funções que retornam um valor com determinado tipo em caso de sucesso, ou o booleano false em caso de falha (alguns exemplos são: curl_exec e PDOStatement::execute). Então, no caso de falha, poderia se emitir esta exception.


Por que usar Exceptions?

Bom, já sabemos quando usar cada tipo de exception, mas afinal, por que usá-las?

Primeiramente, as exception derivadas de LogicException são úteis para se capturar problemas no código-fonte e que precisam ser ajustadas. Se o seu sistema em produção capturar este tipo de exception, é importante implementar alguma forma de notificar os responsáveis pelo software (seja por e-mail ou através de um mecanismo de log), para que os programadores possam corrigir o problema.

Já as exceptions derivadas de RuntimeException devem ser capturadas para mostrar alguma mensagem apropriada para o usuário ou tentar contornar o problema de alguma forma. Por exemplo, se ocorreu uma falha ao se conectar com um determinado BD, mas você tem um cluster com vários BDs replicados, então pode tentar se conectar com outro espelho, etc. Algumas destas exceptions podem até serem úteis de serem monitoradas, pois podem representar algum ataque do usuário tentando realizar operações indevidas.

Sobre a utilização das exceptions derivadas de LogicException e RuntimeException, a justificativa é que se tem um maior refinamento sobre o tipo de problema ocorrido e você pode optar por fazer um tratamento diferente para algum tipo específico. Naturalmente, você também pode criar suas próprias exceptions derivadas das exceptions providas pela SPL. Por exemplo, uma exception relacionada à segurança, que é emitida quando algum usuário tenta acessar alguma página ou realizar alguma operação que ele não tem permissão.

2 comentários