# Pathrule Pattern: Laravel (1.0.0)
# ::pathrule:package:laravel

### [RULE] Eager-load every relation a view or resource reads  (path: /app)
<!-- scope: folder | priority: high | strict -->

Eloquent will happily run one query per row, and Blade makes it invisible: the loop reads `$order->customer->name` and the page fires 200 queries.

- Load relations on the query, not in the template: `Order::with(['customer', 'items.product'])`. If a Blade view, API resource, or export touches a relation, the query that feeds it declares it.
- Turn lazy loading into an error outside production with `Model::preventLazyLoading(! app()->isProduction())` in a service provider. This is the single highest-value line in a Laravel app.
- Use `withCount` for counts, `chunk`/`lazy`/`cursor` for large sets, and `select` the columns you need. Loading full models to count them is wasted memory.
- Never call a query inside an accessor or a `map` over a collection. Load what you need in one query and key it by id.
- Watch the inverse too: `whereHas` on a deep relation can be slower than a join with an index. Read the query log before optimising by feel.

---

### [RULE] Validate in a form request and authorize with a policy  (path: /app/Http)
<!-- scope: folder | priority: high | strict -->

Laravel gives you validation and authorization as first-class layers. Skipping them is what turns a mass-assignment convenience into a privilege escalation.

- Put validation in a `FormRequest` per endpoint. The controller receives validated data, never `$request->all()`. Rules live with the endpoint, not scattered across the action.
- Authorize with a policy or gate for every action on a model, and check ownership of the specific record. A route model binding proves the record exists, not that this user may touch it.
- Keep `$fillable` (or `$guarded`) accurate on every model and pass only validated data to `create`/`update`. `Model::unguard()` in application code is a bug.
- Return an API resource rather than the model, so a new database column never leaks into a public response by accident.
- Rate-limit authentication and other abuse-prone routes with the framework's throttle middleware.

See the web-security pattern for the app-level authorization and input rules this mirrors.

---

### [RULE] Make queued jobs idempotent and bounded  (path: /app/Jobs)
<!-- scope: folder | priority: high | strict -->

A queued job will run twice eventually: a worker is killed mid-run, a deploy restarts the pool, a transient failure triggers a retry. Design for the second run.

- Set `$tries` and `$backoff` (or `retryUntil`) on every job. A job with unlimited retries and a permanent failure is an infinite loop that burns the queue.
- Make the side effect idempotent: check state before acting, or key it on something stable so a repeat is a no-op. Charging a card, sending an email, or incrementing a counter twice must be impossible.
- Use `ShouldBeUnique` (or an explicit cache lock) for jobs that must not overlap for the same subject, and `ShouldBeUniqueUntilProcessing` where only the queued window matters.
- Dispatch after the transaction commits (`dispatch()->afterCommit()` or `after_commit` on the connection). A job that reads a row the transaction has not committed yet fails intermittently and is a nightmare to reproduce.
- Pass ids, not serialized models, and handle the row being gone. Implement `failed()` so a dead job leaves a trace instead of silence.

See the background-jobs-queues pattern for the queue-agnostic version of these constraints.

---

### [MEMORY] Where code goes in a Laravel app, and what stays out of the container  (path: /app)

Laravel's structure is loose enough that two developers produce two architectures. Pick the conventional one and stay in it.

- Controllers stay thin: resolve, delegate, respond. Multi-step workflows go in a single-purpose action or service class under `App\Actions` or `App\Services`, which is testable without HTTP and reusable from a command or job.
- Models own their query vocabulary: local scopes for reusable filters, casts for value conversion, accessors for derived display values. Business decisions that span models do not belong there.
- Avoid heavy work in a service provider's `boot()`. It runs on every request, including artisan commands and health checks. Bind lazily and let the container resolve on demand.
- Prefer explicit event listeners over model events for anything a reader needs to know about, and never rely on model events in bulk paths: `Model::query()->update()` bypasses them entirely, exactly like Eloquent's docs say.
- Keep Blade for presentation. A view that queries, formats money, or decides permissions is a view that cannot be tested.

See /app/Http for the request rules and /app/Jobs for the queue rules.

---

### [MEMORY] Migrations, seeders, and the production deploy sequence  (path: /database)

Most Laravel production surprises come from the deploy, not the code.

- Cache everything the framework can precompute on deploy: `config:cache`, `route:cache`, `view:cache`, `event:cache`. Without config caching, every request re-reads your config tree, and `env()` calls outside config files return null once caching is on. Read config, never `env()`, at runtime.
- Run `migrate --force` as part of the release, and treat migrations as forward-only in production. Rolling back a shipped migration is a data decision, not a command.
- Keep destructive schema changes multi-step: add nullable, deploy writers, backfill, then constrain or drop. On MySQL, an added index or a changed column type can lock a large table.
- Seeders are for reference data and must be idempotent (`upsert`/`firstOrCreate`), so re-running one does not duplicate rows. Test data belongs in factories.
- Restart the queue workers after every deploy (`queue:restart`); long-lived workers keep the old code in memory until they cycle.

See /app/Jobs for the job design that makes a worker restart safe.

---

### [SKILL] laravel-pre-merge-review  (path: /)

---
name: laravel-pre-merge-review
description: Laravel review checklist covering Eloquent query efficiency, request validation and authorization, queued job safety, and deploy-time caching. Run before merging.
---

# Laravel review

## Eloquent
- [ ] Every relation the view, resource, or export reads is eager-loaded on the query.
- [ ] No query inside a loop, accessor, or collection callback.
- [ ] `preventLazyLoading` is on outside production and the change does not trip it.
- [ ] Large reads use `chunk`, `lazy`, or `cursor`; counts use `withCount`.

## Request handling
- [ ] Writes take a `FormRequest`; the controller never reads `$request->all()`.
- [ ] A policy or gate authorizes the action on the specific record.
- [ ] Responses go through an API resource; `$fillable` is accurate.

## Jobs and events
- [ ] `$tries` and `$backoff` are set; the side effect is safe to repeat.
- [ ] Overlap-sensitive jobs use `ShouldBeUnique` or an explicit lock.
- [ ] Dispatch happens after commit; the job takes ids, not models, and handles a missing row.

## Migrations and deploy
- [ ] Schema change is additive, or split into add, backfill, constrain.
- [ ] No runtime `env()` call outside a config file.
- [ ] Seeder changes are idempotent.
