Define a arquitetura limpa de projetos Laravel com Actions, DTOs, Policies e Service Layer. Use quando precisar organizar estrutura de diretórios, definir padrões arquiteturais, ou planejar a organização de um projeto Laravel.
From laravel-toolkitnpx claudepluginhub aronpc/ai --plugin laravel-toolkitThis skill is limited to using the following tools:
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
Executes pre-written implementation plans: critically reviews, follows bite-sized steps exactly, runs verifications, tracks progress with checkpoints, uses git worktrees, stops on blockers.
Guides idea refinement into designs: explores context, asks questions one-by-one, proposes approaches, presents sections for approval, writes/review specs before coding.
Define e mantém arquitetura limpa em projetos Laravel usando Actions, DTOs e Policies.
| Skill | Quando usar junto |
|---|---|
coder | Para implementar seguindo a arquitetura |
actions | Para Actions, Events e Jobs |
models | Para Models e relações |
exceptions | Para exceções de domínio |
testing | Para testar Actions e Policies |
planner | Para planejar arquitetura de features |
Use esta skill sempre que:
Stack: Laravel 12 + React + Inertia.js + Filament 5 + Tailwind 4
declare(strict_types=1);DB::, use Model::query()env() fora de arquivos de configuração.gitkeep ao adicionar arquivosapp/Console/Commands/app/Console/Commands/config('app.name') não env('APP_NAME')app/
├── Actions/ # Lógica de negócio (NÃO Services!)
│ ├── Business/
│ ├── Tenant/
│ └── Billing/
├── DataObjects/ # Value Objects (DTOs)
│ ├── Business/
│ ├── Menu/
│ └── Order/
├── Enums/ # Todos os enums (nomes descritivos, sem sufixo)
├── Events/ # Eventos de domínio (past tense)
├── Listeners/ # Event listeners (imperative)
├── Models/ # Eloquent models (thin, sem lógica de negócio)
├── Observers/ # Model lifecycle observers
├── Http/
│ ├── Controllers/ # Thin orchestrators
│ └── Requests/ # Form Requests (validation)
└── Policies/ # Authorization logic
Este projeto segue clean architecture com clara separação de responsabilidades:
┌─────────────────────────────────────────────────────────────┐
│ HTTP REQUEST │
└──────────────────────┬──────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ CONTROLLER (Thin Orchestrator) │
│ ├─ Validate (FormRequest) │
│ ├─ Authorize (Policy) │
│ ├─ Build Value Object │
│ └─ Call Action │
└──────────────────────┬──────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ VALUE OBJECT (Data Transfer) │
│ ├─ Type-safe data structure │
│ ├─ Immutable (readonly) │
│ └─ Factory methods (fromRequest, toArray) │
└──────────────────────┬──────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ ACTION (Business Logic) │
│ ├─ Single responsibility │
│ ├─ Database operations │
│ ├─ Business rules & calculations │
│ ├─ Call other Actions if needed │
│ └─ Dispatch Events │
└──────────────────────┬──────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ MODEL (Data + Relationships) │
│ ├─ Eloquent relationships │
│ ├─ Accessors & Mutators │
│ ├─ Scopes │
│ └─ NO business logic │
└──────────────────────┬──────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ OBSERVER / EVENT (Side Effects) │
│ ├─ Model lifecycle hooks (Observer) │
│ ├─ Send notifications │
│ ├─ Update related records │
│ └─ Log activities │
└─────────────────────────────────────────────────────────────┘
| Camada | Responsabilidade | ✅ Deve | ❌ Não Deve |
|---|---|---|---|
| Controller | Orquestração HTTP | Validate, Authorize, Delegate, Respond | Lógica de negócio, Queries de database |
| Value Object | Transferência de dados | Type safety, Imutabilidade | Validação, Lógica de negócio |
| Action | Lógica de negócio | CRUD, Cálculos, Regras, Eventos | Preocupações HTTP, Validação |
| Model | Dados + Relações | Relações, Casts, Scopes | Lógica de negócio, Cálculos complexos |
| Observer | Side effects | Lifecycle hooks, Events | Lógica de negócio core |
| Event/Listener | Side effects desacoplados | Notifications, Logging, Async tasks | Fluxo de negócio principal |
| Tipo | Convenção | Exemplo |
|---|---|---|
| Enum | Sem sufixo (Spatie) | BusinessType, UserRole |
| Action | Sufixo Action | CreateBusinessAction, UpdateOrderAction |
| Value Object | Sufixo Data | CreateBusinessData, UpdateOrderData |
| Controller | Plural + Controller | BusinessesController, OrdersController |
| Event | Past tense, sem sufixo | BusinessCreated, OrderPlaced |
| Listener | Imperativo, sem sufixo | SendWelcomeEmail, NotifyAdmin |
| Observer | Sufixo Observer | BusinessObserver, OrderObserver |
| Policy | Sufixo Policy | BusinessPolicy, OrderPolicy |
| Form Request | Sufixo Request | StoreBusinessRequest, UpdateOrderRequest |
NOTA: NUNCA use sufixo Service - use Actions!
php artisan make:model Product -mf
php artisan make:action CreateProduct
php artisan make:class DataObjects/Product/CreateProductData
php artisan make:request StoreProductRequest
php artisan make:policy ProductPolicy --model=Product
php artisan make:observer ProductObserver --model=Product
php artisan make:test --pest ProductTest
Antes de finalizar QUALQUER feature:
composer test)composer fix)final class BusinessController
{
public function store(StoreBusinessRequest $request): RedirectResponse
{
// ✅ GOOD: Thin controller - delega para Action
$business = CreateBusinessAction::run(
tenant: auth()->user()->tenant,
data: $request->validated()
);
return redirect()
->route('owner.businesses.index')
->with('success', __('messages.business_created'));
}
}
final class CreateBusinessAction
{
use AsAction;
public function handle(Tenant $tenant, CreateBusinessData $data): Business
{
// Check limits
if (!$tenant->isWithinLimit('businesses')) {
throw new BusinessLimitExceededException();
}
// Create business
$business = $tenant->businesses()->create($data->toArray());
// Increment usage
$tenant->incrementUsage('businesses');
// Dispatch event
event(new BusinessCreated($business));
return $business;
}
}
final readonly class CreateBusinessData
{
public function __construct(
public string $name,
public string $type,
public ?string $email = null,
public ?string $phone = null,
) {}
public static function fromRequest(array $data): self
{
return new self(
name: $data['name'],
type: $data['type'],
email: $data['email'] ?? null,
phone: $data['phone'] ?? null,
);
}
public function toArray(): array
{
return [
'name' => $this->name,
'type' => $this->type,
'email' => $this->email,
'phone' => $this->phone,
];
}
}
final class BusinessPolicy
{
public function view(User $user, Business $business): bool
{
return $user->tenant_id === $business->tenant_id;
}
public function create(User $user): bool
{
return $user->tenant->isWithinLimit('businesses');
}
public function update(User $user, Business $business): bool
{
return $user->tenant_id === $business->tenant_id;
}
public function delete(User $user, Business $business): bool
{
return $user->tenant_id === $business->tenant_id;
}
}
laravel-i18n para traduções de Enums, mensagens e interfaceslaravel-exceptions para exceções de domínio e regras de negóciolaravel-testing-pest para testes de Actions e Policieslaravel-actions-events para patterns avançados de eventos