exceptions
Cria exceções customizadas renderable e reportable para HTTP e logging em Laravel. Use quando precisar criar exception handlers, exceções de domínio, ou melhorar o tratamento de erros.
SKILL.mdskills/exceptions/SKILL.md
GitHubexceptions
Resumo
Cria exceções customizadas com interfaces renderable/reportable para HTTP responses e logging.
Skills Relacionadas
| Skill | Quando usar junto |
|---|---|
architecture |
Para exceções de domínio na arquitetura |
actions |
Para tratamento em Actions |
testing |
Para testar exceções |
qa |
Para verificar tratamento de erros |
Quando usar
Use esta skill sempre que:
- Criar regras de negócio que violam limites
- Criar validações de domínio específicas
- Implementar autorização customizada
- Implementar erros de pagamento/billing
- Criar exceções com contexto para logging/monitoramento
Princípios Fundamentais
CRÍTICO: Use exceções especializadas para erros de domínio específicos - NUNCA use Exception genérico.
- Domain Exceptions - Crie exceções customizadas para violações de regras de negócio
- HTTP Status Mapping - Mapeie exceções para códigos HTTP apropriados
- Renderable - Implemente método
render()para respostas HTTP customizadas - Reportable - Implemente método
report()para logging/monitoramento - Localized Messages - Use
__()para mensagens de exceção (EN, ES, PT-BR)
Estrutura de Exception
Ajuste os subdiretórios aos domínios do seu projeto. A árvore abaixo usa placeholders genéricos (
Domain1/,Domain2/); os exemplos de código nas seções seguintes usam um domínio ilustrativo (Business,Payment) apenas para demonstração.
app/
├── Exceptions/
│ ├── Domain1/
│ │ ├── ResourceLimitExceededException.php
│ │ ├── ResourceNotFoundException.php
│ │ └── InvalidResourceTypeException.php
│ └── Domain2/
│ ├── InsufficientCreditsException.php
│ ├── PlanLimitExceededException.php
│ └── PaymentFailedException.php
Criando Exceções Customizadas
php artisan make:exception Business/BusinessLimitExceededException
Padrão de Exception
<?php
declare(strict_types=1);
namespace App\Exceptions\Business;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
final class BusinessLimitExceededException extends Exception
{
/**
* Create a new exception instance.
*/
public function __construct(
public readonly int $currentCount,
public readonly int $maxAllowed,
public readonly string $planName,
) {
parent::__construct(
message: __('exceptions.business_limit_exceeded', [
'current' => $this->currentCount,
'max' => $this->maxAllowed,
'plan' => $this->planName,
]),
code: 403
);
}
/**
* Render exception as an HTTP response.
*/
public function render(Request $request): JsonResponse|RedirectResponse
{
if ($request->expectsJson()) {
return response()->json([
'message' => $this->getMessage(),
'current_count' => $this->currentCount,
'max_allowed' => $this->maxAllowed,
'plan_name' => $this->planName,
], 403);
}
return back()->with('error', $this->getMessage());
}
/**
* Report exception (logging/monitoramento).
*/
public function report(): void
{
// Log or send to monitoring service
Log::warning('Business limit exceeded', [
'current' => $this->currentCount,
'max' => $this->maxAllowed,
'plan' => $this->planName,
]);
}
}
Uso em Actions
<?php
declare(strict_types=1);
namespace App\Actions\Business;
use App\DataObjects\Business\CreateBusinessData;
use App\Exceptions\Business\BusinessLimitExceededException;
use App\Models\Business;
use App\Models\Tenant;
use Lorisleiva\Actions\Concerns\AsAction;
final class CreateBusinessAction
{
use AsAction;
public function handle(Tenant $tenant, CreateBusinessData $data): Business
{
// Check business limit
$currentCount = $tenant->businesses()->count();
$maxAllowed = $tenant->plan->limits['businesses'] ?? 0;
if ($currentCount >= $maxAllowed) {
throw new BusinessLimitExceededException(
currentCount: $currentCount,
maxAllowed: $maxAllowed,
planName: $tenant->plan->name,
);
}
// Create business
return $tenant->businesses()->create($data->toArray());
}
}
Tipos Comuns de Exception
Exceções de Domínio/Regra de Negócio
// When resource not found
throw new BusinessNotFoundException($id);
// When validation fails
throw new InvalidBusinessTypeException($type);
// When limit exceeded
throw new BusinessLimitExceededException($current, $max, $plan);
Exceções de Autorização
// When tenant access denied
throw new UnauthorizedTenantAccessException($tenantId);
// When tenant is inactive
throw new TenantInactiveException($tenant);
// When tenant is suspended
throw new TenantSuspendedException($tenant, $reason);
Exceções de Pagamento/Billing
// When credits insufficient
throw new InsufficientCreditsException($required, $available);
// When plan limit exceeded
throw new PlanLimitExceededException($resource, $limit);
// When payment fails
throw new PaymentFailedException($transactionId, $reason);
Convenção de Nomenclatura de Exceções
| Tipo | Convenção | Exemplo |
|---|---|---|
| Not Found | *NotFoundException |
BusinessNotFoundException |
| Invalid Input | Invalid*Exception |
InvalidBusinessTypeException |
| Limit Exceeded | *LimitExceededException |
BusinessLimitExceededException |
| Unauthorized | Unauthorized*Exception |
UnauthorizedTenantAccessException |
| Payment | Payment*Exception |
PaymentFailedException |
| Resource State | *InactiveException |
TenantInactiveException |
| Business Rule | Nome descritivo | MenuItemUnavailableException |
Arquivos de Tradução
// lang/en/exceptions.php
return [
'business_limit_exceeded' => 'Business limit exceeded. You have :current businesses, but your :plan plan allows only :max.',
'business_not_found' => 'Business not found.',
'invalid_business_type' => 'Invalid business type: :type',
'tenant_inactive' => 'Your account is inactive. Please contact support.',
'tenant_suspended' => 'Your account has been suspended. Reason: :reason',
'insufficient_credits' => 'Insufficient credits. Required: :required, Available: :available',
];
// lang/es/exceptions.php
return [
'business_limit_exceeded' => 'Límite de negocios excedido. Tienes :current negocios, pero tu plan :plan permite solo :max.',
// ...
];
// lang/pt_BR/exceptions.php
return [
'business_limit_exceeded' => 'Limite de negócios excedido. Você tem :current negócios, mas seu plano :plan permite apenas :max.',
// ...
];
Mapeamento de Código HTTP
| Exception Type | HTTP Status | Descrição |
|---|---|---|
| NotFoundException | 404 | Resource not found |
| Unauthorized | 403 | Access denied |
| ValidationException | 422 | Validation failed |
| LimitExceeded | 403 | Resource limit exceeded |
| PaymentFailed | 402 | Payment required |
| InactiveException | 403 | Account/resource inactive |
| SuspendedException | 403 | Account suspended |
Testando Exceções
<?php
use App\Actions\Business\CreateBusinessAction;
use App\Exceptions\Business\BusinessLimitExceededException;
it('throws exception when business limit exceeded', function () {
$tenant = Tenant::factory()->create(['plan_id' => 1]);
$tenant->plan->update(['limits' => ['businesses' => 2]]);
// Create 2 businesses (at limit)
Business::factory()->count(2)->create(['tenant_id' => $tenant->id]);
// Try to create 3rd business
$data = CreateBusinessData::fromArray([
'name' => 'New Business',
'type' => 'restaurant',
]);
expect(fn() => CreateBusinessAction::run($tenant, $data))
->toThrow(BusinessLimitExceededException::class, 'Business limit exceeded');
});
it('includes exception context in response', function () {
$tenant = Tenant::factory()->create(['plan_id' => 1]);
Business::factory()->count(2)->create(['tenant_id' => $tenant->id]);
try {
CreateBusinessAction::run($tenant, $data);
} catch (BusinessLimitExceededException $e) {
expect($e->currentCount)->toBe(2)
->and($e->maxAllowed)->toBe(2)
->and($e->planName)->toBe('Basic');
}
});
Global Exception Handler
No Laravel 11+ não existe mais o app/Exceptions/Handler.php. Toda a
configuração de exceções vive em bootstrap/app.php, no callback
->withExceptions(...).
<?php
use App\Exceptions\Business\BusinessLimitExceededException;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Http\Request;
use Psr\Log\LogLevel;
use Throwable;
return Application::configure(basePath: dirname(__DIR__))
->withExceptions(function (Exceptions $exceptions): void {
// Callback de report (substitui o antigo register()/reportable)
$exceptions->report(function (Throwable $e): void {
// Send to monitoring service (Sentry, Bugsnag, etc.)
});
// Resposta HTTP customizada por tipo de exceção
$exceptions->render(function (BusinessLimitExceededException $e, Request $request) {
if ($request->expectsJson()) {
return response()->json(['message' => $e->getMessage()], 403);
}
return back()->with('error', $e->getMessage());
});
// Exceções que não devem ser reportadas (substitui $dontReport)
$exceptions->dontReport([
BusinessLimitExceededException::class,
]);
// Nível de log customizado por tipo (substitui $levels)
$exceptions->level(BusinessLimitExceededException::class, LogLevel::WARNING);
})->create();
Melhores Práticas
✅ FAÇA
- Crie exceções de domínio específicas para cada violação de regra de negócio
- Inclua dados de contexto no construtor da exceção
- Implemente
render()para respostas HTTP customizadas - Implemente
report()para logging/monitoramento - Use chaves de tradução para mensagens
- Teste exceções sendo lançadas nas Actions
- Mapeie exceções para códigos HTTP apropriados
❌ NÃO FAÇA
- NÃO coloque lógica de negócio nas classes de exceção
- NÃO catch exceções apenas para relançar
- NÃO esqueça de traduzir mensagens de erro
- NÃO use
Exceptiongenérico sem motivo claro - NÃO crie exceções para cada regra de validação (use Form Requests)
- NÃO esqueça de testar comportamento de exceções
Quando Criar Exceções Customizadas
✅ Violações de regras de negócio (limits, constraints)
✅ Erros de domínio específicos (invalid state transitions)
✅ Falhas de autorização (tenant access, ownership)
✅ Erros de pagamento/billing
✅ Resource not found (quando 404 genérico não é suficiente)
❌ Erros de validação (use Form Requests)
❌ Erros de conexão de banco (framework handles)
❌ Erros genéricos (use built-in exceptions)
Referências Cruzadas
- Traduções: Veja
i18npara traduções de mensagens de erro em EN, ES, PT-BR - Actions: Usado em Actions (veja
architectureeactions) - Testes: Veja
testingpara testar exceções sendo lançadas
Referências
- Laravel Error Handling - Documentação oficial