Laravel
Pathrule3 Rules • 2 Memories • 1 Skill
A bundle for teams on Laravel 12 and 13 that keeps agents on the framework's own idioms instead of generic PHP. It covers the four places Laravel apps rot: Eloquent relations that lazy-load inside a Blade loop, controllers that read straight from the request, jobs that are not idempotent and duplicate work on retry, and a production deploy that never caches config or routes. Written so each piece lands on the directory it governs rather than in one README nobody scopes.
Suggested path map
Pathrule places each piece on the matching path, so your assistant only sees it where it belongs. This is the scoping you get on import; you can adjust it in your workspace.
Rules
3Eager-load every relation a view or resource reads/apphighstrictDeclare `with()` on the query that feeds a view, resource, or export, and fail loudly on lazy loading in local and CI.
| 1 | 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. |
| 2 | |
| 3 | - 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. |
| 4 | - 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. |
| 5 | - 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. |
| 6 | - 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. |
| 7 | - 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. |
Make queued jobs idempotent and bounded/app/JobshighstrictQueues deliver at least once: give every job a retry budget, a unique lock where it must not overlap, and a side effect that is safe to repeat.
| 1 | 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. |
| 2 | |
| 3 | - 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. |
| 4 | - 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. |
| 5 | - 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. |
| 6 | - 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. |
| 7 | - Pass ids, not serialized models, and handle the row being gone. Implement `failed()` so a dead job leaves a trace instead of silence. |
| 8 | |
| 9 | See the background-jobs-queues pattern for the queue-agnostic version of these constraints. |
Memories
2Where code goes in a Laravel app, and what stays out of the container/appActions and services hold workflows, models hold scopes and casts, and anything expensive is resolved lazily rather than in a provider boot.
| 1 | Laravel's structure is loose enough that two developers produce two architectures. Pick the conventional one and stay in it. |
| 2 | |
| 3 | - 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. |
| 4 | - 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. |
| 5 | - 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. |
| 6 | - 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. |
| 7 | - Keep Blade for presentation. A view that queries, formats money, or decides permissions is a view that cannot be tested. |
| 8 | |
| 9 | See /app/Http for the request rules and /app/Jobs for the queue rules. |
Migrations, seeders, and the production deploy sequence/databaseMigrations are forward-only in production, seeders stay idempotent, and every deploy caches config, routes, views, and events.
| 1 | Most Laravel production surprises come from the deploy, not the code. |
| 2 | |
| 3 | - 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. |
| 4 | - 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. |
| 5 | - 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. |
| 6 | - Seeders are for reference data and must be idempotent (`upsert`/`firstOrCreate`), so re-running one does not duplicate rows. Test data belongs in factories. |
| 7 | - Restart the queue workers after every deploy (`queue:restart`); long-lived workers keep the old code in memory until they cycle. |
| 8 | |
| 9 | See /app/Jobs for the job design that makes a worker restart safe. |
Skills
1laravel-pre-merge-review/rootChecklist for any Laravel change that adds a controller, model query, job, or migration.
| 1 | --- |
| 2 | name: laravel-pre-merge-review |
| 3 | description: Laravel review checklist covering Eloquent query efficiency, request validation and authorization, queued job safety, and deploy-time caching. Run before merging. |
| 4 | --- |
| 5 | |
| 6 | # Laravel review |
| 7 | |
| 8 | ## Eloquent |
| 9 | - [ ] Every relation the view, resource, or export reads is eager-loaded on the query. |
| 10 | - [ ] No query inside a loop, accessor, or collection callback. |
| 11 | - [ ] `preventLazyLoading` is on outside production and the change does not trip it. |
| 12 | - [ ] Large reads use `chunk`, `lazy`, or `cursor`; counts use `withCount`. |
| 13 | |
| 14 | ## Request handling |
| 15 | - [ ] Writes take a `FormRequest`; the controller never reads `$request->all()`. |
| 16 | - [ ] A policy or gate authorizes the action on the specific record. |
| 17 | - [ ] Responses go through an API resource; `$fillable` is accurate. |
| 18 | |
| 19 | ## Jobs and events |
| 20 | - [ ] `$tries` and `$backoff` are set; the side effect is safe to repeat. |
| 21 | - [ ] Overlap-sensitive jobs use `ShouldBeUnique` or an explicit lock. |
| 22 | - [ ] Dispatch happens after commit; the job takes ids, not models, and handles a missing row. |
| 23 | |
| 24 | ## Migrations and deploy |
| 25 | - [ ] Schema change is additive, or split into add, backfill, constrain. |
| 26 | - [ ] No runtime `env()` call outside a config file. |
| 27 | - [ ] Seeder changes are idempotent. |
Why this pattern
AI agents write Laravel code that lazy-loads relations inside Blade loops, validates inline in controllers, authorizes nowhere, and queues jobs that duplicate work when they retry.
Built for PHP teams shipping Laravel 12 or 13 applications.
Keeps your assistant from:
- Lazy-loading a relation inside a Blade or collection loop and firing N queries
- Reading request input directly in a controller with no validation or authorization
- Queueing a job that repeats its side effect every time the queue retries it
- Deploying without caching config, routes, views, and events
- License
- Apache-2.0
- Version
- 1.0.0
- Updated
- 2026-08-24