Skip to content

Capítulo 2.7: Tratamento de Erros

Tempo de estudo: 40 minutos


1. Por que erros padrão são ruins?

Se ocorrer um erro em sua aplicação Laravel (por exemplo, um registro não encontrado no banco de dados) e você não o tratar de nenhuma forma, o usuário verá uma enorme página HTML com informações de depuração ou uma mensagem não informativa de "Server Error".

Para APIs, isso é uma catástrofe. Seu aplicativo frontend espera receber JSON, não HTML. Nossa tarefa é interceptar qualquer erro e transformá-lo em uma resposta JSON estruturada.


2. Dispatcher Central de Erros: bootstrap/app.php

Em versões antigas do Laravel, havia um arquivo volumoso App\Exceptions\Handler.php. No Laravel 11/12, tudo se tornou muito mais simples e elegante. O centro de gerenciamento de erros agora está localizado diretamente no arquivo de configuração do seu aplicativo — bootstrap/app.php.

Abra bootstrap/app.php. No final, você verá o bloco .withExceptions(...). Este é o nosso "dispatcher central".

<?php
// bootstrap/app.php

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        // ...
    })
    ->withExceptions(function (Exceptions $exceptions) {
        // <-- É AQUI QUE VAMOS TRABALHAR
    })->create();

3. Lidando com o erro mais comum: "Não Encontrado" (404)

O erro mais comum em uma API é quando o usuário solicita um recurso que não existe (por exemplo, GET /api/planets/999). O Laravel, neste caso, gera uma exceção ModelNotFoundException ou NotFoundHttpException. Vamos interceptá-las.

Adicione o seguinte código dentro de .withExceptions(...):

<?php
// bootstrap/app.php

->withExceptions(function (Exceptions $exceptions) {

    // Interceptamos a exceção quando o modelo não é encontrado no banco de dados
    $exceptions->render(function (ModelNotFoundException $e, Request $request) {
        // Verificamos se a requisição veio especificamente para nossa API
        if ($request->is('api/*')) {
            return response()->json([
                'message' => 'O recurso solicitado não foi encontrado em nossa galáxia.'
            ], 404);
        }
    });

    // Interceptamos a exceção quando a própria rota não é encontrada
    $exceptions->render(function (NotFoundHttpException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json([
                'message' => 'Essa rota espacial não existe.'
            ], 404);
        }
    });

})->create();
O que fizemos?

  1. $exceptions->render(...) — registramos um "handler" (manipulador). Ele diz: "Se ocorrer uma exceção do tipo ModelNotFoundException, execute este código".
  2. if ($request->is('api/*')) — esta é uma verificação importante. Ela garante que nossa bela resposta JSON será enviada apenas para requisições de API, sem afetar páginas web comuns.
  3. return response()->json(...) — criamos e retornamos uma resposta JSON padronizada com código 404.

Agora, se você solicitar um planeta inexistente, em vez de uma página HTML feia, você receberá um JSON limpo.


4. Exceções Personalizadas: Criando nossos próprios "sinais de alarme"

Às vezes, as exceções padrão não são suficientes. Imagine que temos uma regra de negócio: "não é possível deletar o planeta 'Terra'". Se alguém tentar fazer isso, devemos retornar um erro significativo.

Passo 1: Criando nossa classe de exceção Executaremos no terminal:

php artisan make:exception CannotDeleteEarthException

Passo 2: Usando-o no controller Abriremos PlanetController.php e modificaremos o método destroy:

<?php
// app/Http/Controllers/PlanetController.php
use App\Exceptions\CannotDeleteEarthException; // <-- Importamos nossa exceção
use App\Models\Planet;

public function destroy(Planet $planet)
{
    // Nossa nova regra de negócio
    if (strtolower($planet->name) === 'земля') {
        throw new CannotDeleteEarthException('A exclusão do planeta Terra é proibida pelo Código Galáctico.');
    }

    $planet->delete();
    return response()->json(null, 204);
}
Agora, se alguém tentar executar DELETE /api/planets/1 (onde 1 é o ID da Terra), nosso código lançará uma exceção CannotDeleteEarthException.

Passo 3: Ensinando o Laravel a tratar nossa "emergência" de forma elegante Voltaremos a bootstrap/app.php e adicionaremos um novo handler para nossa exceção.

<?php
// bootstrap/app.php

->withExceptions(function (Exceptions $exceptions) {

    // Nosso novo handler
    $exceptions->render(function (CannotDeleteEarthException $e, Request $request) {
        return response()->json([
            'message' => 'Operação proibida.',
            'details' => $e->getMessage() // Obtemos a mensagem que passamos no throw
        ], 403); // 403 Forbidden - "Acesso Proibido"
    });

    // ... (outros handlers para 404)

})->create();
Pronto! Criamos nossa própria exceção nomeada, que torna o código do controller mais limpo, e ensinamos o Laravel a transformá-la em uma resposta JSON bonita e significativa com o status HTTP correto.


5. Tratamento de todas as outras falhas (500 Internal Server Error)

O que fazer com todas as outras falhas inesperadas? Por exemplo, se o banco de dados cair ou houver um erro de sintaxe no código. Para isso, podemos registrar um handler "universal" para o tipo de erro mais geral — Throwable.

Importante: Este handler deve ser o último, para não interceptar exceções mais específicas que definimos acima.

<?php
// bootstrap/app.php

->withExceptions(function (Exceptions $exceptions) {

    // ... (handlers para CannotDeleteEarthException e 404)

    // HANDLER UNIVERSAL (no final)
    $exceptions->render(function (Throwable $e, Request $request) {
        if ($request->is('api/*')) {
            // Em modo de depuração, a mensagem de erro real pode ser exibida
            $message = config('app.debug')
                ? 'Ocorreu um erro: ' . $e->getMessage()
                : 'Uma falha inesperada ocorreu a bordo. Os engenheiros já foram chamados.';

            return response()->json(['message' => $message], 500);
        }
    });

})->create();

Agora, qualquer exceção "desconhecida" será cuidadosamente interceptada e transformada em JSON com código 500, sem quebrar sua API ou mostrar informações desnecessárias ao usuário.


6. Log de Erros: Caixa preta da nave espacial

Configurações de log em config/logging.php:

<?php
'channels' => [
    'space_api' => [
        'driver' => 'daily',
        'path' => storage_path('logs/space_api.log'),
        'level' => 'error',
        'days' => 14,
    ],
],

Adicionando um registro ao log:

<?php
try {
    // Código com risco de erro
} catch (Exception $e) {
    Log::channel('space_api')->error('Erro de acesso aos planetas', [
        'exception' => $e,
        'request' => request()->all(),
        'user_id' => auth()->id()
    ]);
    throw $e;
}


Quiz para fixação

1. Status HTTP para "Planeta não encontrado":

2. Classe para tratamento global de erros:

3. Método para criar uma exceção personalizada:

4. Canal para log separado de erros da API:

5. Principal vantagem de criar exceções personalizadas:


🚀 Resumo do capítulo:

Você equipou sua API com um sistema de recuperação robusto:

  • 🛟 Interceptação global de erros padrão
  • 🪐 Exceções personalizadas com códigos claros
  • 📝 Formato JSON unificado para todos os erros
  • 🔍 Log com detalhes do incidente
  • 📡 Integração com sistemas de monitoramento

A nave espacial está pronta para emergências! No capítulo final da seção, testaremos todos os sistemas.

📌 Verificação:

  1. Crie uma exceção PlanetNotFoundException
  2. Adicione o tratamento de erros 404 em ->withExceptions
  3. Teste uma requisição para um planeta inexistente

⚠️ Se os erros não forem interceptados:

  • Certifique-se de que is('api/*') corresponde às suas rotas
  • Verifique a ordem dos manipuladores em register()
  • Para exceções personalizadas, use throw new