# Laravel Framework — SunLint Agent Guide

> Priority directives for AI agents working on Laravel projects.
> Rule files: `.agent/skills/sunlint-code-quality/rules/`

---

## Critical Patterns — Apply Every Time

### Validation
- **Always** use `php artisan make:request` → `FormRequest` class, never `$request->validate()` in controller
- In the controller use `$request->validated()` — never `$request->all()` or `$request->input()` wholesale
- See: `LV001-form-request-validation.md`

### N+1 Queries
- **Always** check: does this controller/service access a relation in a loop or collection?
- If yes → add `->with(['relation'])` to the query **before** the loop
- Use `->withCount('relation')` instead of `->relation->count()` in loops  
- See: `LV002-eager-load-no-n-plus-1.md`

### Mass Assignment
- Every new Model must define explicit `$fillable = [...]`
- **Never**: `protected $guarded = []`
- **Never**: `Model::create($request->all())` — use `$request->validated()`
- See: `LV004-fillable-mass-assignment.md`

### Passwords
- `Hash::make($password)` to store, `Hash::check($plain, $hash)` to verify
- **Never** `md5()`, `sha1()`, or raw `password_hash()` for passwords
- See: `LV007-hash-passwords.md`

### Authorization
- **Never** `if ($user->role === 'admin')` scattered in controllers
- Generate a Policy: `php artisan make:policy ModelPolicy --model=Model`
- Use `$this->authorize('action', $model)` or Form Request `authorize()`
- See: `LV005-policies-gates-authorization.md`

---

## Architecture Patterns

### Controller responsibility
Controllers do exactly 3 things: validate input → call service → return response.
No business logic, no DB queries, no calculations in controllers.
```php
// Controller
public function store(StoreOrderRequest $request): JsonResponse
{
    $order = $this->orderService->placeOrder($request->user(), $request->validated());
    return OrderResource::make($order)->response()->setStatusCode(201);
}
```
See: `LV012-service-layer.md`

### API Responses
- Always use `php artisan make:resource` → never `->toArray()`, `->toJson()` or raw `response()->json($model)`
- Sensitive columns (`password`, `remember_token`) must be in `$hidden` on the model
- See: `LV009-api-resources.md`

### Configuration
- `env()` appears **only** in `config/*.php` files — never in app code (breaks after `config:cache`)
- App code always uses `config('key.sub_key')` with optional default
- See: `LV003-config-not-env.md`

### Heavy Operations (email, API calls, reports)
- Any external call or operation > ~200ms → `Job::dispatch()->onQueue('name')`
- Job class implements `ShouldQueue`, handles `failed()` method
- See: `LV006-queue-heavy-tasks.md`

### Large Data Sets
- No `Model::all()` or unbounded `->get()` in commands/jobs
- Use `->chunkById(500, fn($batch) => ...)` or `->cursor()` for iteration
- See: `LV010-chunk-large-datasets.md`

### Multi-step Writes
- Any sequence of 2+ DB writes that must be atomic → wrap in `DB::transaction(fn() => { ... })`
- dispatch jobs inside transactions: `Job::dispatch($model)->afterCommit()`
- See: `LV011-db-transactions.md`

### Route Model Binding
- Route params matching a Model name auto-resolve — no manual `findOrFail()`
- `Route::get('/posts/{post}', ...)` → controller receives `Post $post` directly
- See: `LV008-route-model-binding.md`

### Dependency Injection
- Register services in `AppServiceProvider::register()` as singletons/bindings
- Controllers receive via constructor — never `new Service()` inside a method
- See: `LV014-service-container.md`

---

## Run Commands

```bash
php artisan make:request  StoreXxxRequest   # validation
php artisan make:policy   XxxPolicy --model=Xxx
php artisan make:resource XxxResource
php artisan make:job      ProcessXxx
php artisan make:service  XxxService        # (if using stubs)

php artisan test --filter ClassName         # run specific test
php artisan test --coverage                 # coverage report
php artisan config:cache                    # validate no env() in app code
php artisan route:list --path=api           # audit routes
php artisan telescope:install               # install query/request debugger
```

---

## What NOT to do (quick reference)

| ❌ Wrong | ✅ Correct |
|---|---|
| `$request->validate([...])` in controller | `FormRequest` class |
| `$request->all()` | `$request->validated()` |
| `Post::all()` | `Post::with([...])->paginate(20)` |
| `$post->comments->count()` in loop | `Post::withCount('comments')` |
| `env('KEY')` in service | `config('prefix.key')` |
| `$guarded = []` | `$fillable = ['col1', 'col2']` |
| `if ($user->role === 'admin')` | `$this->authorize('action', $model)` |
| `md5($password)` | `Hash::make($password)` |
| `response()->json($model)` | `ModelResource::make($model)` |
| `Mail::send()` in controller | `SendMailJob::dispatch()->onQueue(...)` |
| `new OrderService()` in controller | Constructor DI auto-resolved by container |
