Você lança uma exceção no PHP com throw new Exception('message');. A execução para naquela linha e pula para o bloco catch mais próximo que aceita o tipo da exceção, onde $e->getMessage() retorna a mensagem. Use uma classe específica, como InvalidArgumentException, para que quem chama consiga distinguir os problemas.
Uma exceção é um objeto. O throw a passa para cima na pilha de chamadas até o primeiro chamador com um catch correspondente, e esse chamador decide o que fazer; a função que encontrou o problema não precisa saber. A mecânica da captura, do finally e da captura de vários tipos está na página sobre try catch.
O que um objeto de exceção contém
Toda exceção carrega uma mensagem, um código inteiro, o arquivo e a linha em que foi criada e um stack trace. O construtor recebe ($message, $code, $previous), todos opcionais:
getLine() é a linha do throw new, não a linha do catch. O trace lista as chamadas que levaram até ali (#0 /home/index.php(8): findOrder()), que costuma ser a primeira coisa a ler quando um erro aparece num log. Converter a exceção em string (echo $e; ou (string) $e) imprime tudo isso no formato padrão do PHP.
Classes de exceção nativas
O PHP traz uma família de classes de exceção na sua Standard PHP Library (SPL). Lançar a que dá nome ao problema deixa o código mais legível e permite que quem chama capture de forma precisa. Todas estas estendem Exception:
| Classe | Lance quando |
|---|---|
InvalidArgumentException | um argumento tem a forma errada: um nome vazio, uma opção desconhecida |
DomainException | um valor está fora do conjunto que faz sentido: um preço negativo |
OutOfRangeException | o código pediu um índice que nunca pode existir (um bug de quem chama) |
OutOfBoundsException | falta uma chave ou um índice em dados só conhecidos em tempo de execução |
RangeException | um resultado calculado cai fora do intervalo válido durante a execução |
LengthException | algo é longo ou curto demais |
RuntimeException | um problema só detectável durante a execução: um disco cheio, um timeout |
UnexpectedValueException | uma função retornou ou recebeu um valor de um tipo inesperado |
LogicException | o próprio código está errado, um bug e não uma entrada ruim |
JsonException | lançada por json_encode()/json_decode() com JSON_THROW_ON_ERROR |
InvalidArgumentException, DomainException, LengthException e OutOfRangeException estendem LogicException; OutOfBoundsException, RangeException e UnexpectedValueException estendem RuntimeException. Você mesmo pode verificar as classes pai de qualquer classe:
TypeError tem Error como classe pai, não Exception: ela pertence à segunda família, abaixo.
Erros que o próprio PHP lança
Desde o PHP 7, e muito mais desde o PHP 8, as funções e os operadores do próprio PHP lançam subclasses de Error para problemas que antes eram warnings. Você as captura do mesmo jeito, mas catch (Exception $e) não bate com elas:
DivisionByZeroError, TypeError, ValueError, ArgumentCountError, UnhandledMatchError e o Error simples estendem Error. Cada uma aponta mais vezes para um bug no código que chama do que para dados ruins, e é por isso que ficam fora da árvore de Exception. É o declare(strict_types=1) que faz str_repeat(5, 2) ser um TypeError; sem ele o PHP converteria 5 em "5".
Escrever uma classe de exceção própria
Uma subclasse de uma linha muitas vezes é tudo de que você precisa: o próprio nome da classe carrega o significado, e quem chama pode capturar exatamente esse tipo. Quando quem captura precisa de dados (qual pedido, qual valor), adicione propriedades e repasse a mensagem com parent::__construct():
Escolha a classe pai com cuidado: estender RuntimeException (em vez de Exception simples) significa que o código que captura RuntimeException também trata a sua. Uma pequena hierarquia, uma exceção base por biblioteca ou módulo com subclasses específicas abaixo dela, deixa quem chama escolher entre "capturar tudo de pagamentos" e "capturar só este caso".
Encadear exceções com previous
Quando você captura uma exceção de baixo nível e lança uma de nível mais alto, passe a original como terceiro argumento. Nada se perde: getPrevious() volta até a causa raiz.
O usuário vê "customers.csv could not be imported"; o log, percorrendo a cadeia, também recebe "row 2 has 2 columns". Se uma exceção encadeada nunca for capturada, o erro fatal do PHP imprime a cadeia inteira: primeiro a causa original, depois cada camada numa linha Next.
throw como expressão
Desde o PHP 8.0, throw é uma expressão, então pode ficar onde quer que um valor seja esperado: depois de ??, num ternário, numa arrow function. Isso transforma "pegue isto ou falhe" numa única linha:
Quando não lançar
Exceções são para situações que a função atual não consegue tratar e que o fluxo normal não espera. Uma busca que não encontra nada, um campo opcional vazio ou um usuário que não está logado são resultados comuns: retorne null, false ou um array vazio e deixe quem chama verificar, e guarde as exceções para os casos em que continuar seria errado.
Mais dois hábitos mantêm o código com exceções legível: nunca capture uma exceção só para ignorá-la (um catch (Exception $e) {} vazio esconde o próximo bug de verdade), e lance classes específicas em vez de Exception simples, para que quem chama nunca seja obrigado a capturar tudo para tratar um caso.
Perguntas frequentes
Como lanço uma exceção no PHP?
Crie um objeto de exceção e lance-o: throw new InvalidArgumentException('Quantity must be positive');. A execução para naquela linha e pula para o bloco catch correspondente mais próximo; se não houver nenhum, o script termina com um erro fatal "Uncaught".
Como crio uma exceção personalizada no PHP?
Estenda Exception ou uma das subclasses dela: class PaymentFailedException extends RuntimeException {}. Essa linha basta para lançá-la e capturá-la pelo próprio tipo. Adicione propriedades e um construtor quando quem captura precisar de dados extras, e chame parent::__construct($message, $code, $previous).
Qual a diferença entre getMessage e getCode?
getMessage() retorna o texto passado como primeiro argumento do construtor, feito para pessoas e logs. getCode() retorna o inteiro passado como segundo argumento (0 por padrão), útil para verificações automáticas, como mapear erros para códigos de status HTTP.
Qual exceção devo lançar no PHP?
Use uma classe SPL nativa que corresponda ao problema: InvalidArgumentException para um argumento inválido, DomainException para um valor fora do permitido, RuntimeException para falhas só visíveis em tempo de execução (um disco cheio, um timeout), LogicException para erros de programação. Para erros que quem chama precisa distinguir, crie uma subclasse própria.
O que é encadeamento de exceções no PHP?
Passar a exceção original como terceiro argumento do construtor quando você lança uma nova: throw new ImportException('Import failed', 0, $e);. A nova exceção dá contexto, e $e->getPrevious() nela retorna a causa original, então nenhum detalhe se perde.