PHP

Criar exceção customizada em PHP: quando estender Exception compensa

Se o seu controller tem um catch (Exception $e) que trata “pedido 4821 não existe” e “o gateway devolveu 504” com a mesma tela de “erro ao processar”, quem chama o código não tem como separar as duas coisas. E separar com strpos($e->getMessage(), 'não encontrado') é pior ainda.

Uma classe por motivo de falha resolve isso. Não por elegância: por causa do catch.

A classe carrega o dado, não só o texto

<?php
namespace AppPedidoException;

class PedidoNaoEncontradoException extends RuntimeException
{
    public function __construct(
        private readonly int $pedidoId,
        ?Throwable $previous = null
    ) {
        parent::__construct("Pedido {$pedidoId} não encontrado.", 0, $previous);
    }

    public function getPedidoId(): int
    {
        return $this->pedidoId;
    }
}

Estendi RuntimeException de propósito. A convenção do PHP é essa: LogicException é bug de programador, coisa que se corrige escrevendo o código direito; RuntimeException é o que só aparece com dado real em produção. Pedido que sumiu é a segunda. A promoção de propriedade com readonly no construtor vale do PHP 8.1 pra cima.

O $previous existe para não perder o rastro

public function porId(int $id): array
{
    try {
        $stmt = $this->pdo->prepare('SELECT * FROM pedido WHERE id = :id');
        $stmt->execute(['id' => $id]);
        $linha = $stmt->fetch(PDO::FETCH_ASSOC);
    } catch (PDOException $e) {
        // banco fora do ar não é "pedido inexistente"
        throw new PedidoIndisponivelException($id, $e);
    }

    if ($linha === false) {
        throw new PedidoNaoEncontradoException($id);
    }

    return $linha;
}
// na borda da aplicação
try {
    $pedido = $this->repo->porId($id);
} catch (PedidoNaoEncontradoException $e) {
    return new JsonResponse(['erro' => 'pedido inexistente'], 404);
} catch (PedidoIndisponivelException $e) {
    $this->logger->error($e->getMessage(), ['causa' => $e->getPrevious()]);
    return new JsonResponse(['erro' => 'tente de novo em instantes'], 503);
}

PedidoIndisponivelException estende RuntimeException do mesmo jeito e recebe o $previous. Esse terceiro parâmetro é o que mais gente esquece: sem ele o stack trace do PDO some do log e sobra uma mensagem bonitinha sem a query que estourou. Com ele, getPrevious() devolve a exceção original inteira.

Um freio: não crie quarenta classes. Uma para cada decisão que o chamador precisa tomar. Se dois erros caem no mesmo catch e fazem a mesma coisa, eram a mesma classe desde o começo.

Post Recentes

Erro HTTP no WordPress ao enviar imagem para a biblioteca de mídia

A barra de upload chega no fim e o WordPress devolve só "Erro HTTP". Separe as três causas: Imagick barrado…

7 horas atrás

style amp-custom acima de 75KB: como cortar o CSS inline do AMP

The author stylesheet specified in tag 'style amp-custom' is too long? Meça o bloco com curl e tire da fila…

13 horas atrás

Creation of dynamic property: deprecated no PHP 8.3 com WordPress 6.9

debug.log crescendo em gigabytes com avisos de propriedade dinâmica. Veja como isolar o plugin culpado, corrigir a classe e silenciar…

1 dia atrás

Plugin do Magento 2 não executa: método final, private ou objeto novo

O before ou around declarado no di.xml nunca dispara. Confira o Interceptor em generated e descarte as três causas que…

2 dias atrás

Criar swapfile no Ubuntu: parar o OOM killer de matar o MySQL

VPS de 2 GB onde o mysqld morre de madrugada e o site volta com erro de conexão. Como confirmar…

3 dias atrás

Grupos nomeados no preg_match: pare de contar índice de $matches

Regex com cinco parênteses e ninguém lembra se a data é $matches[2] ou $matches[3]. Nomeie os grupos e leia o…

4 dias atrás