laravel-project-patterns
Project conventions for Laravel projects, including application code, project-supporting PHP, Inertia Blade shells, React Email templates, schema-backed models, and Pest test routing. Use when creating or changing routes, config, localization, bootstrap/public entrypoints, seeders, root tooling PHP, models, migrations, relationships, casts, factories, resources, actions, console commands, middleware, listeners, policies, providers, support classes, resources/views Blade shells, resources/react-email mail templates/exports, or tests under Unit, Integration, Feature, or Architecture, plus Browser only when a real browser suite exists. Encodes strict typed PHP, unguarded Eloquent models, public NanoID route keys, indexed *_id columns without database foreign key constraints, migrations without down() methods, controller/API feature test ordering, and system-logic model integration tests.
NPX Install
npx skill4agent add hosmelq/skills laravel-project-patternsTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →Laravel Project Patterns
resources/viewsresources/react-emailreferences/tests/Feature/Http/Controllers/README.mdWorkspaceWorkspaceParentRecordChildRecordLeafRecordRelatedRecordActorExampleInputExampleResourceFirst Pass
- Read the nearest and application guidelines.
AGENTS.md - Search version-specific docs before code changes when Laravel Boost is available.
- Inspect the exact file being changed and sibling files in the same area before generating anything:
database/migrations/*routes/*.phpconfig/*.phplang/**/*.phpbootstrap/*.phppublic/*.phpdatabase/seeders/*.php- root tooling PHP and tracked auxiliary PHP guidance
app/**resources/views/**resources/react-email/**database/factories/**tests/Unit/**tests/Integration/**tests/Feature/**- if present
tests/Browser/**
- If adding or changing a controller feature test, load first, then the matching action, route, mode, and validation references under that directory.
references/tests/Feature/Http/Controllers/README.md - For nested controller tests, compare against the deepest sibling controller tests before deciding a case is unnecessary. Treat Inertia assertions as backend response contracts for page component names, props, redirects, and flashes/toasts.
Reference Skeleton
references/**references/README.mdNon-Negotiables
- Preserve concurrent changes. Re-read a file immediately before patching it.
- Keep new code aligned to the existing architecture. Do not add base folders, dependencies, or broad abstractions unless the task explicitly needs them.
- Use for Laravel-created files when practical, then rewrite generated output to match local patterns.
php artisan make:* --no-interaction - Do not add database foreign key constraints when the repository uses schema-planning tools or application-level relationships instead. Use indexed columns only.
foreignId - Do not add migration methods when existing migrations intentionally omit them.
down() - Do not add or
$fillableon models when the app globally calls$guarded.Model::unguard() - Because models are globally unguarded, use for normal persisted attribute mutations in app-owned code. Do not use
$model->update([...])as a mass-assignment workaround.forceFill(...)->save() - Every behavioral change needs a focused programmatic test.
- Every new test must prove changed behavior, an interface contract, a regression risk, or a changed owner surface. Do not add tests only because an action template contains a similar example.
- Name action integration tests after the observable behavior. For the primary create success case, use a direct name such as ; do not append the parent or owner merely because the persisted row includes its ID. Add a scope qualifier only when that scope is the behavior under test, such as cross-parent isolation or an active-parent guard.
creates a child record - Assert durable database effects with ,
assertDatabaseHas(),assertDatabaseMissing(), orassertSoftDeleted()according to the persistence contract. Do not assert fixture setup; if a test needs a soft-deleted fixture, create it with the factoryassertModelMissing()state instead of creating an active model and callingtrashed(). Keep the soft-deleted parent in a local variable for route/login arguments because normaldelete()queries may filter it out. If a child factory derives ownership through a normal parent query that cannot see trashed rows, pass only the minimum FK/owner IDs needed to create the child under that trashed parent. UsebelongsTowith$model->refresh()only when the test is proving reloaded Eloquent model behavior, such as casts, accessors, relationships, timestamps, or dirty/original state.expect() - Controller feature tests are the exception when the mutation delegates persistence to a Data input-backed action: mock the action and assert the HTTP boundary plus the request-to-input mapping needed by the scenario. For create/store primary success, a required-only payload may assert the required input mapping. For partial update, keep cases partial and assert submitted fields plus request-normalized fields only where relevant. Leave persistence, optional/default behavior, nullable clearing, and side effects to .
tests/Integration/Actions - Controller feature tests remain required for delegated mutations because the controller is an entry point. Do not remove controller tests as duplicate only because covers the internal guard. Keep the controller assertions for authentication, authorization, scoped binding, request validation, action invocation, request-to-input mapping, redirects/toasts, and exception-to-validation mapping.
tests/Integration/Actions - For delegated destroy or lifecycle actions, keep controller feature tests at the HTTP boundary: mock the action, assert the bound model is passed, and assert exception-to-validation mapping. Do not use one generic mock as a reason to delete concrete route-contract tests for distinct dependency families, such as configuration rows versus operational rows that make deletion invalid. When those cases remain in the controller suite, set up the minimum fixture that names the route scenario, mock the action to throw, and preserve historical active and soft-deleted dependency variants when they protect the route contract. Leave deletion state, transaction internals, and guard truth to .
tests/Integration/Actions - Actions must accept only the models, payloads, and independent values that are business inputs to the operation. Do not pass route hierarchy solely to repeat ownership checks already enforced by scoped bindings and policies at the entrypoint.
- Do not re-query a model passed to an action solely to prove ownership, existence, or soft-delete state. Query fresh state only when the action owns a transactional guard, lock, or required relationship read.
- A parent or owner belongs in the action signature only when the operation needs it as a business input, such as creating a row through that parent. Otherwise derive required business relationships from the target model instead of making callers reconstruct the route hierarchy.
- When a controller test mocks a create/store action and the controller needs the returned model for a redirect route, return a persisted factory model with . Do not set generated route keys such as
createOne(),public_id, or generated codes unless the literal value is asserted. Set only required relationships and the minimum non-conflicting domain attributes needed to let validation reach the mocked action.slug - Do not put Pest chains inside Mockery argument matchers. Mock callbacks should return booleans and check only the arguments needed for that controller contract.
expect() - Keep limited to project/system behavior. Use
tests/Integration/Models/**for the canonical boundary and avoid generic Laravel relationship, FK/ID equality, related-model type, or factory/count smoke tests.references/tests/Integration/Models/README.md - Do not create only the most obvious test file. Update related tests when a change alters a related model, resource, controller, action, middleware, console command, or support surface.
- Controller feature tests follow the action-first, failure-to-success matrix in . Nested routes must cover every scoped binding boundary, including redundant ownership mismatches when a child stores denormalized
references/tests/Feature/Http/Controllers/README.mdor ancestor IDs.Workspace - Store/update validation uses public IDs when the form contract exposes public IDs. Convert to internal integer IDs after validation in the controller only when persistence requires it; do not force solely to make public IDs fit integer columns.
prepareForValidation() - For Form Request rules that mention server-managed or unknown fields, inspect the application service-provider form-request bootstrap first. The app calls , so do not add
FormRequest::failOnUnknownFields()to silently drop submitted input; leave non-contract fields out ofexclude.rules() - Prefer database constraints for invariants that PostgreSQL can enforce. Add an explicit lock only for a documented cross-row invariant that cannot reasonably live in the database, and make every competing action lock the same parent row in the same order.
- Put web/resource request validation, normalization, and domain validation in Form Requests. Domain guards that require action-owned transactional state or dependent-record checks should live in the action and be mapped by the controller when a sibling action-delegation pattern does that; do not duplicate the same guard in the Form Request. Avoid in web controllers unless a live sibling has the same controller-owned domain failure pattern. Current API session controllers may throw validation exceptions for external-token or session-domain failures.
ValidationException::withMessages() - When a controller catches an action-owned domain exception and maps it to validation, the controller test that mocks the action only proves exception-to-validation mapping. Cover the action guard itself in .
tests/Integration/Actions - Name controller tests after the observable rejected behavior with verbs such as or
rejects; do not name them after the internal exception-to-validation mapping mechanism.prevents
Reference Map
- for migration layout, indexes, route keys, soft deletes, and database inspection.
references/database/migrations/README.md - for factory defaults, relationship factories, states, and after-creating hooks.
references/database/factories/README.md - for routes, config, localization, bootstrap/public entrypoints, seeders, root tooling PHP, and auxiliary PHP guidance.
references/project/README.md - for patterns from the application layer under
references/app/README.md.app/ - for action classes.
references/app/Actions/README.md - for Artisan commands.
references/app/Console/Commands/README.md - for enums.
references/app/Enums/README.md - for domain exceptions.
references/app/Exceptions/README.md - for controllers.
references/app/Http/Controllers/README.md - for CRUDdy/resourceful controller naming, activation/deactivation, enable/disable, confirm/unconfirm, session/login/logout, regeneration, and other lifecycle actions.
references/app/Http/Controllers/lifecycle-resources.md - for middleware.
references/app/Http/Middleware/README.md - for form requests.
references/app/Http/Requests/README.md - for JSON resources.
references/app/Http/Resources/README.md - for event listeners.
references/app/Listeners/README.md - for Eloquent model structure, casts, docblocks, route keys, traits, and relationships.
references/app/Models/README.md - for reusable model concerns.
references/app/Models/Concerns/README.md - for models backed by a non-default reference-data connection.
references/app/Models/World/README.md - for notifications.
references/app/Notifications/README.md - for policies.
references/app/Policies/README.md - for service providers.
references/app/Providers/README.md - for support classes.
references/app/Support/README.md - for global helpers.
references/app/functions.php.md - for Inertia root Blade shells and hand-authored app views.
references/resources/views/README.md - for React Email templates, Nub commands, export lifecycle, generated Blade views, and mail assets.
references/resources/react-email/README.md
- for suite routing and the complete path map.
references/tests/README.md - and
references/tests/Pest.mdfor global Pest helpers and base test behavior.references/tests/TestCase.md - for shard metadata expectations.
references/tests/.pest/shards.md - for architecture rules.
references/tests/ArchitectureTest.md - for
references/tests/Unit/Enums/README.md.tests/Unit/Enums/*Test.php - for
references/tests/Unit/Models/README.md.tests/Unit/Models/*Test.php - for
references/tests/Integration/Actions/README.md.tests/Integration/Actions/*Test.php - for
references/tests/Integration/Http/Resources/README.md.tests/Integration/Http/Resources/*ResourceTest.php - for
references/tests/Integration/Listeners/README.md.tests/Integration/Listeners/*Test.php - and
references/tests/Integration/Models/README.mdfor persisted model behavior.references/tests/Integration/Models/Concerns/README.md - for media support integration tests.
references/tests/Integration/Support/Media/README.md - for console command feature tests.
references/tests/Feature/Console/README.md - for web controller feature tests.
references/tests/Feature/Http/Controllers/README.md - for API controller feature tests.
references/tests/Feature/Http/Controllers/Api/README.md - ,
references/tests/Feature/Http/Controllers/actions/*.md,modes/api-json.md, androute-patterns.mdfor controller action matrices.validation/*.md - for middleware feature tests.
references/tests/Feature/Http/Middleware/README.md - for route-binding feature tests on model concerns.
references/tests/Feature/Models/Concerns/README.md - and
references/tests/migrations/README.mdfor test-only schema and binary/text fixtures.references/tests/testfiles/README.md - and
references/tests/Support/Models/README.mdfor test-only support utilities.references/tests/TestSupport/README.md
Completion Checklist
- New migration matches the local style and avoids unsupported rollback/FK patterns.
- New or changed model has typed relationships, casts, docblock properties, and the expected route-key/public-id behavior.
- Factory can create a valid row with realistic defaults and coherent relationship ownership.
- shell changes preserve Inertia head/app slots, Vite entrypoints, font directives, locale/html metadata, and production/authenticated third-party scripts.
resources/views - changes keep source templates under
resources/react-email, use Nub commands, and treat exported Blade views/assets as generated output.resources/react-email/mail - Tests are placed in the correct suite and cover every touched surface: unit-level configuration/pure logic, integration-level persisted behavior/resources/support, feature-level HTTP/console/middleware behavior, and browser coverage when real browser UX is touched.
- Related model/resource/controller tests are updated when system behavior or serialized contracts change; do not add paired model relationship tests just to prove Laravel relationship wiring.
- Controller coverage was checked against live nested siblings, not only action templates. If a nested child stores redundant /ancestor ownership, the controller tests include a same-parent mismatched-ownership
Workspacecase and list actions exclude those records.404 - Run the smallest relevant tests, for example:
php artisan test --compact tests/Unit/Models/<Model>Test.php tests/Integration/Models/<Model>Test.php
php artisan test --compact tests/Integration/Http/Resources/<Resource>Test.php
php artisan test --compact tests/Feature/Http/Controllers/<Controller>Test.php- If PHP files changed, run:
vendor/bin/pint --dirty --format agent