Name: Controllers
Description: Thin HTTP entry points that validate input, delegate to Actions or Services, and return a response. Controllers contain no business logic.
Compatible Agents: general-purpose, backend
Tags: app/Http/Controllers/**/*.php, laravel, php, backend, controller, http, request-response
Rules
- Keep controllers thin — delegate business logic to Services, Actions, or Jobs
- Controllers handle: request validation, calling a service/job, returning a response
- No direct database queries or complex logic in controller methods
- Use dedicated
FormRequest classes for all validation — never call $request->validate() or Validator::make() inside a controller
- Use invokable controllers (
__invoke) for single-action endpoints
- Name invokable controllers after the action:
StoreInvoiceController, ProcessWebhookController
- Return JSON responses from API controllers
- Return views from web controllers
- Use appropriate HTTP status codes (200, 201, 422, 403, etc.)
- Enforce authorization at the controller level using
$this->authorize(), via Form Request authorize(), or route middleware
Examples
// Invokable single-action controller
class StoreInvoiceController extends Controller
{
public function __invoke(StoreInvoiceRequest $request, CreateInvoice $action): JsonResponse
{
$order = Order::findOrFail($request->validated('order_id'));
$invoice = $action->execute($order);
return new JsonResponse(new InvoiceResource($invoice), 201);
}
}
// Resource controller — thin, delegates to actions
class InvoiceController extends Controller
{
public function index(): JsonResponse
{
return new JsonResponse(InvoiceResource::collection(Invoice::paginate()));
}
public function store(StoreInvoiceRequest $request, CreateInvoice $action): JsonResponse
{
$this->authorize('create', Invoice::class);
$order = Order::findOrFail($request->validated('order_id'));
$invoice = $action->execute($order);
return new JsonResponse(new InvoiceResource($invoice), 201);
}
public function destroy(Invoice $invoice, DeleteInvoice $action): JsonResponse
{
$this->authorize('delete', $invoice);
$action->execute($invoice);
return new JsonResponse(null, 204);
}
}
Anti-Patterns
- Calling
$request->validate() directly in a controller (use a FormRequest)
- Writing database queries directly in a controller
- Putting business logic in a controller method
- Using resource controllers with 7 methods when only 1-2 are needed (use invokable controllers)
- Not using appropriate HTTP status codes in responses
- Performing authorization inside Actions instead of at the controller level
References
- Laravel Controllers
- Related:
FormRequests/SKILL.md — all input validation
- Related:
Actions/SKILL.md — the business logic controllers delegate to
- Related:
Policies/SKILL.md — authorization enforced in controllers
1---2name: controllers3description: Thin HTTP entry points that validate input, delegate to Actions or Services, and return a response. Controllers contain no business logic.4---5
6**Name:** Controllers
7**Description:** Thin HTTP entry points that validate input, delegate to Actions or Services, and return a response. Controllers contain no business logic.
8**Compatible Agents:** general-purpose, backend
9**Tags:** app/Http/Controllers/**/*.php, laravel, php, backend, controller, http, request-response
10
11## Rules
12
13- Keep controllers **thin** — delegate business logic to Services, Actions, or Jobs
14- Controllers handle: request validation, calling a service/job, returning a response
15- No direct database queries or complex logic in controller methods
16- Use dedicated `FormRequest` classes for all validation — never call `$request->validate()` or `Validator::make()` inside a controller
17- Use invokable controllers (`__invoke`) for single-action endpoints
18- Name invokable controllers after the action: `StoreInvoiceController`, `ProcessWebhookController`
19- Return JSON responses from API controllers
20- Return views from web controllers
21- Use appropriate HTTP status codes (200, 201, 422, 403, etc.)
22- Enforce authorization at the controller level using `$this->authorize()`, via Form Request `authorize()`, or route middleware
23
24## Examples
25
26```php
27// Invokable single-action controller
28class StoreInvoiceController extends Controller
29{
30 public function __invoke(StoreInvoiceRequest $request, CreateInvoice $action): JsonResponse
31 {
32 $order = Order::findOrFail($request->validated('order_id'));
33 $invoice = $action->execute($order);
34
35 return new JsonResponse(new InvoiceResource($invoice), 201);
36 }
37}
38```
39
40```php
41// Resource controller — thin, delegates to actions
42class InvoiceController extends Controller
43{
44 public function index(): JsonResponse
45 {
46 return new JsonResponse(InvoiceResource::collection(Invoice::paginate()));
47 }
48
49 public function store(StoreInvoiceRequest $request, CreateInvoice $action): JsonResponse
50 {
51 $this->authorize('create', Invoice::class);
52
53 $order = Order::findOrFail($request->validated('order_id'));
54 $invoice = $action->execute($order);
55
56 return new JsonResponse(new InvoiceResource($invoice), 201);
57 }
58
59 public function destroy(Invoice $invoice, DeleteInvoice $action): JsonResponse
60 {
61 $this->authorize('delete', $invoice);
62 $action->execute($invoice);
63
64 return new JsonResponse(null, 204);
65 }
66}
67```
68
69## Anti-Patterns
70
71- Calling `$request->validate()` directly in a controller (use a FormRequest)
72- Writing database queries directly in a controller
73- Putting business logic in a controller method
74- Using resource controllers with 7 methods when only 1-2 are needed (use invokable controllers)
75- Not using appropriate HTTP status codes in responses
76- Performing authorization inside Actions instead of at the controller level
77
78## References
79
80- [Laravel Controllers](https://laravel.com/docs/controllers)
81- Related: `FormRequests/SKILL.md` — all input validation
82- Related: `Actions/SKILL.md` — the business logic controllers delegate to
83- Related: `Policies/SKILL.md` — authorization enforced in controllers