output:
Wall time: 3.2393 seconds
Output:
[{"type":"text","text":"# Search Results\n\n## Validation Validation Quickstart Displaying the Validation Errors XHR Requests and Validation\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### XHR Requests and Validation\n\nIn this example, we used a traditional form to send data to the application. However, many applications receive XHR requests from a JavaScript powered frontend. When using the `validate` method during an XHR request, Laravel will not generate a redirect response. Instead, Laravel generates a [JSON response containing all of the validation errors](#validation-error-response-format). This JSON response will be sent with a 422 HTTP status code.\n\n---\n\n## Expectations `json()`\nExpectations | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/expectations.md\n\n\n### `json()`\n\nThe `json()` modifier decodes the current expectation `$value` from JSON to an array.\n\n```php\nexpect('{\"name\":\"Nuno\",\"credit\":1000.00}')\n    ->json()\n    ->toHaveCount(2)\n    ->name->toBe('Nuno')\n    ->credit->toBeFloat();\n\nexpect('not-a-json')->json(); //Fails\n```\n\n---\n\n## Vite Working With Blade and Routes Aliases\nVite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/vite.md\n\n\n### Aliases\n\nIt is common in JavaScript applications to [create aliases](#aliases) to regularly referenced directories. But, you may also create aliases to use in Blade by using the `macro` method on the `Illuminate\\Support\\Facades\\Vite` class. Typically, \"macros\" should be defined within the `boot` method of a [service provider](/docs/{{version}}/providers):\n\n```php\n/**\n * Bootstrap any application services.\n */\npublic function boot(): void\n{\n    Vite::macro('image', fn (string $asset) => $this->asset(\"resources/images/{$asset}\"));\n}\n```\n\nOnce a macro has been defined, it can be invoked within your templates. For example, we can use the `image` macro defined above to reference an asset located at `resources/images/logo.png`:\n\n```blade\n<img src=\"{{ Vite::image('logo.png') }}\" alt=\"Laravel Logo\">\n```\n\n---\n\n## Validation Available Validation Rules json\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### json\n\nThe field under validation must be a valid JSON string.\n\n---\n\n## Expectations `toBeJson()`\nExpectations | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/expectations.md\n\n\n### `toBeJson()`\n\nThis expectation ensures that `$value` is a JSON string.\n\n```php\nexpect('{\"hello\":\"world\"}')->toBeJson();\n```\n\n---\n\n## Validation Form Request Validation\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n## Form Request Validation\n\n---\n\n## Vite Working With Blade and Routes Refreshing on Save\nVite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/vite.md\n\n\n### Refreshing on Save\n\nWhen your application is built using traditional server-side rendering with Blade, Vite can improve your development workflow by automatically refreshing the browser when you make changes to view files in your application. To get started, you can simply specify the `refresh` option as `true`.\n\n```js\nimport { defineConfig } from 'vite';\nimport laravel from 'laravel-vite-plugin';\n\nexport default defineConfig({\n    plugins: [\n        laravel({\n            // ...\n            refresh: true,\n        }),\n    ],\n});\n```\n\nWhen the `refresh` option is `true`, saving files in the following directories will trigger the browser to perform a full page refresh while you are running `npm run dev`:\n\n- `app/Livewire/**`\n- `app/View/Components/**`\n- `lang/**`\n- `resources/lang/**`\n- `resources/views/**`\n- `routes/**`\n\nWatching the `routes/**` directory is useful if you are utilizing [Ziggy](https://github.com/tighten/ziggy) to generate route links within your application's frontend.\n\nIf these default paths do not suit your needs, you can specify your own list of paths to watch:\n\n```js\nimport { defineConfig } from 'vite';\nimport laravel from 'laravel-vite-plugin';\n\nexport default defineConfig({\n    plugins: [\n        laravel({\n            // ...\n            refresh: ['resources/views/**'],\n        }),\n    ],\n});\n```\n\nUnder the hood, the Laravel Vite plugin uses the [vite-plugin-full-reload](https://github.com/ElMassimo/vite-plugin-full-reload) package, which offers some advanced configuration options to fine-tune this feature's behavior. If you need this level of customization, you may provide a `config` definition:\n\n```js\nimport { defineConfig } from 'vite';\nimport laravel from 'laravel-vite-plugin';\n\nexport default defineConfig({\n    plugins: [\n        laravel({\n            // ...\n            refresh: [{\n                paths: ['path/to/watch/**'],\n                config: { delay: 300 }\n            }],\n        }),\n    ],\n});\n```\n\n---\n\n## Http Tests Testing JSON APIs\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n## Testing JSON APIs\n\nLaravel also provides several helpers for testing JSON APIs and their responses. For example, the `json`, `getJson`, `postJson`, `putJson`, `patchJson`, `deleteJson`, and `optionsJson` methods may be used to issue JSON requests with various HTTP verbs. You may also easily pass data and headers to these methods. To get started, let's write a test to make a `POST` request to `/api/user` and assert that the expected JSON data was returned:\n\n```php tab=Pest\n<?php\n\ntest('making an api request', function () {\n    $response = $this->postJson('/api/user', ['name' => 'Sally']);\n\n    $response\n        ->assertStatus(201)\n        ->assertJson([\n            'created' => true,\n        ]);\n});\n```\n\n```php tab=PHPUnit\n<?php\n\nnamespace Tests\\Feature;\n\nuse Tests\\TestCase;\n\nclass ExampleTest extends TestCase\n{\n    /**\n     * A basic functional test example.\n     */\n    public function test_making_an_api_request(): void\n    {\n        $response = $this->postJson('/api/user', ['name' => 'Sally']);\n\n        $response\n            ->assertStatus(201)\n            ->assertJson([\n                'created' => true,\n            ]);\n    }\n}\n```\n\nIn addition, JSON response data may be accessed as array variables on the response, making it convenient for you to inspect the individual values returned within a JSON response:\n\n```php tab=Pest\nexpect($response['created'])->toBeTrue();\n```\n\n```php tab=PHPUnit\n$this->assertTrue($response['created']);\n```\n\n> [!NOTE]\n> The `assertJson` method converts the response to an array to verify that the given array exists within the JSON response returned by the application. So, if there are other properties in the JSON response, this test will still pass as long as the given fragment is present.\n\n---\n\n## Routing Route Model Binding\nRouting | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/routing.md\n\n\n## Route Model Binding\n\nWhen injecting a model ID to a route or controller action, you will often query the database to retrieve the model that corresponds to that ID. Laravel route model binding provides a convenient way to automatically inject the model instances directly into your routes. For example, instead of injecting a user's ID, you can inject the entire `User` model instance that matches the given ID.\n\n---\n\n## Validation Available Validation Rules date_format:_format_,...\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### date_format:_format_,...\n\nThe field under validation must match one of the given _formats_. You should use **either** `date` or `date_format` when validating a field, not both. This validation rule supports all formats supported by PHP's [DateTime](https://www.php.net/manual/en/class.datetime.php) class.\n\nFor convenience, date-based rules may be constructed using the fluent `date` rule builder:\n\n```php\nuse Illuminate\\Validation\\Rule;\n\n'start_date' => [\n    'required',\n    Rule::date()->format('Y-m-d'),\n],\n```\n\n---\n\n## Vite Working With Blade and Routes Processing Static Assets With Vite\nVite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/vite.md\n\n\n### Processing Static Assets With Vite\n\nWhen referencing assets in your JavaScript or CSS, Vite automatically processes and versions them. In addition, when building Blade based applications, Vite can also process and version static assets that you reference solely in Blade templates.\n\nHowever, in order to accomplish this, you need to make Vite aware of your assets by importing the static assets into the application's entry point. For example, if you want to process and version all images stored in `resources/images` and all fonts stored in `resources/fonts`, you should add the following in your application's `resources/js/app.js` entry point:\n\n```js\nimport.meta.glob([\n  '../images/**',\n  '../fonts/**',\n]);\n```\n\nThese assets will now be processed by Vite when running `npm run build`. You can then reference these assets in Blade templates using the `Vite::asset` method, which will return the versioned URL for a given asset:\n\n```blade\n<img src=\"{{ Vite::asset('resources/images/logo.png') }}\">\n```\n\n---\n\n## Validation Form Request Validation Preparing Input for Validation\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n### Preparing Input for Validation\n\nIf you need to prepare or sanitize any data from the request before you apply your validation rules, you may use the `prepareForValidation` method:\n\n```php\nuse Illuminate\\Support\\Str;\n\n/**\n * Prepare the data for validation.\n */\nprotected function prepareForValidation(): void\n{\n    $this->merge([\n        'slug' => Str::slug($this->slug),\n    ]);\n}\n```\n\nLikewise, if you need to normalize any request data after validation is complete, you may use the `passedValidation` method:\n\n```php\n/**\n * Handle a passed validation attempt.\n */\nprotected function passedValidation(): void\n{\n    $this->replace(['name' => 'Taylor']);\n}\n```\n\n---\n\n## Validation Manually Creating Validators Performing Additional Validation\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n### Performing Additional Validation\n\nSometimes you need to perform additional validation after your initial validation is complete. You can accomplish this using the validator's `after` method. The `after` method accepts a closure or an array of callables which will be invoked after validation is complete. The given callables will receive an `Illuminate\\Validation\\Validator` instance, allowing you to raise additional error messages if necessary:\n\n```php\nuse Illuminate\\Support\\Facades\\Validator;\n\n$validator = Validator::make(/* ... */);\n\n$validator->after(function ($validator) {\n    if ($this->somethingElseIsInvalid()) {\n        $validator->errors()->add(\n            'field', 'Something is wrong with this field!'\n        );\n    }\n});\n\nif ($validator->fails()) {\n    // ...\n}\n```\n\nAs noted, the `after` method also accepts an array of callables, which is particularly convenient if your \"after validation\" logic is encapsulated in invokable classes, which will receive an `Illuminate\\Validation\\Validator` instance via their `__invoke` method:\n\n```php\nuse App\\Validation\\ValidateShippingTime;\nuse App\\Validation\\ValidateUserStatus;\n\n$validator->after([\n    new ValidateUserStatus,\n    new ValidateShippingTime,\n    function ($validator) {\n        // ...\n    },\n]);\n```\n\n---\n\n## Vite Working With Blade and Routes\nVite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/vite.md\n\n\n## Working With Blade and Routes\n\n---\n\n## Strings Fluent Strings `isJson` {.collection-method}\nStrings | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/strings.md\n\n\n#### `isJson` {.collection-method}\n\nThe `isJson` method determines if a given string is valid JSON:\n\n```php\nuse Illuminate\\Support\\Str;\n\n$result = Str::of('[1,2,3]')->isJson();\n\n// true\n\n$result = Str::of('{\"first\": \"John\", \"last\": \"Doe\"}')->isJson();\n\n// true\n\n$result = Str::of('{first: \"John\", last: \"Doe\"}')->isJson();\n\n// false\n```\n\n---\n\n## Routing Route Model Binding Explicit Binding\nRouting | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/routing.md\n\n\n### Explicit Binding\n\nYou are not required to use Laravel's implicit, convention based model resolution in order to use model binding. You can also explicitly define how route parameters correspond to models. To register an explicit binding, use the router's `model` method to specify the class for a given parameter. You should define your explicit model bindings at the beginning of the `boot` method of your `AppServiceProvider` class:\n\n```php\nuse App\\Models\\User;\nuse Illuminate\\Support\\Facades\\Route;\n\n/**\n * Bootstrap any application services.\n */\npublic function boot(): void\n{\n    Route::model('user', User::class);\n}\n```\n\nNext, define a route that contains a `{user}` parameter:\n\n```php\nuse App\\Models\\User;\n\nRoute::get('/users/{user}', function (User $user) {\n    // ...\n});\n```\n\nSince we have bound all `{user}` parameters to the `App\\Models\\User` model, an instance of that class will be injected into the route. So, for example, a request to `users/1` will inject the `User` instance from the database which has an ID of `1`.\n\nIf a matching model instance is not found in the database, a 404 HTTP response will be automatically generated.\n\n---\n\n## Http Tests Testing JSON APIs Asserting on JSON Paths\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n#### Asserting on JSON Paths\n\nIf you would like to verify that the JSON response contains the given data at a specified path, you should use the `assertJsonPath` method:\n\n```php tab=Pest\n<?php\n\ntest('asserting a json path value', function () {\n    $response = $this->postJson('/user', ['name' => 'Sally']);\n\n    $response\n        ->assertStatus(201)\n        ->assertJsonPath('team.owner.name', 'Darian');\n});\n```\n\n```php tab=PHPUnit\n<?php\n\nnamespace Tests\\Feature;\n\nuse Tests\\TestCase;\n\nclass ExampleTest extends TestCase\n{\n    /**\n     * A basic functional test example.\n     */\n    public function test_asserting_a_json_paths_value(): void\n    {\n        $response = $this->postJson('/user', ['name' => 'Sally']);\n\n        $response\n            ->assertStatus(201)\n            ->assertJsonPath('team.owner.name', 'Darian');\n    }\n}\n```\n\nThe `assertJsonPath` method also accepts a closure, which may be used to dynamically determine if the assertion should pass:\n\n```php\n$response->assertJsonPath('team.owner.name', fn (string $name) => strlen($name) >= 3);\n```\n\n---\n\n## Strings Strings `Str::isJson()` {.collection-method}\nStrings | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/strings.md\n\n\n#### `Str::isJson()` {.collection-method}\n\nThe `Str::isJson` method determines if the given string is valid JSON:\n\n```php\nuse Illuminate\\Support\\Str;\n\n$result = Str::isJson('[1,2,3]');\n\n// true\n\n$result = Str::isJson('{\"first\": \"John\", \"last\": \"Doe\"}');\n\n// true\n\n$result = Str::isJson('{first: \"John\", last: \"Doe\"}');\n\n// false\n```\n\n---\n\n## Verification Routing Protecting Routes\nVerification | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/verification.md\n\n\n### Protecting Routes\n\n[Route middleware](/docs/{{version}}/middleware) may be used to only allow verified users to access a given route. Laravel includes a `verified` [middleware alias](/docs/{{version}}/middleware#middleware-aliases), which is an alias for the `Illuminate\\Auth\\Middleware\\EnsureEmailIsVerified` middleware class. Since this alias is already automatically registered by Laravel, all you need to do is attach the `verified` middleware to a route definition. Typically, this middleware is paired with the `auth` middleware:\n\n```php\nRoute::get('/profile', function () {\n    // Only verified users may access this route...\n})->middleware(['auth', 'verified']);\n```\n\nIf an unverified user attempts to access a route that has been assigned this middleware, they will automatically be redirected to the `verification.notice` [named route](/docs/{{version}}/routing#named-routes).\n\n---\n\n## Validation Form Request Validation Customizing the Error Messages Customizing the Validation Attributes\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### Customizing the Validation Attributes\n\nMany of Laravel's built-in validation rule error messages contain an `:attribute` placeholder. If you would like the `:attribute` placeholder of your validation message to be replaced with a custom attribute name, you may specify the custom names by overriding the `attributes` method. This method should return an array of attribute / name pairs:\n\n```php\n/**\n * Get custom attributes for validator errors.\n *\n * @return array<string, string>\n */\npublic function attributes(): array\n{\n    return [\n        'email' => 'email address',\n    ];\n}\n```\n\n---\n\n## Validation Form Request Validation Customizing the Error Messages\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n### Customizing the Error Messages\n\nYou may customize the error messages used by the form request by overriding the `messages` method. This method should return an array of attribute / rule pairs and their corresponding error messages:\n\n```php\n/**\n * Get the error messages for the defined validation rules.\n *\n * @return array<string, string>\n */\npublic function messages(): array\n{\n    return [\n        'title.required' => 'A title is required',\n        'body.required' => 'A message is required',\n    ];\n}\n```\n\n---\n\n## Validation Form Request Validation Creating Form Requests\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n### Creating Form Requests\n\nFor more complex validation scenarios, you may wish to create a \"form request\". Form requests are custom request classes that encapsulate their own validation and authorization logic. To create a form request class, you may use the `make:request` Artisan CLI command:\n\n```shell\nphp artisan make:request StorePostRequest\n```\n\nThe generated form request class will be placed in the `app/Http/Requests` directory. If this directory does not exist, it will be created when you run the `make:request` command. Each form request generated by Laravel has two methods: `authorize` and `rules`.\n\nAs you might have guessed, the `authorize` method is responsible for determining if the currently authenticated user can perform the action represented by the request, while the `rules` method returns the validation rules that should apply to the request's data:\n\n```php\n/**\n * Get the validation rules that apply to the request.\n *\n * @return array<string, \\Illuminate\\Contracts\\Validation\\ValidationRule|array<mixed>|string>\n */\npublic function rules(): array\n{\n    return [\n        'title' => 'required|unique:posts|max:255',\n        'body' => 'required',\n    ];\n}\n```\n\n> [!NOTE]\n> You may type-hint any dependencies you require within the `rules` method's signature. They will automatically be resolved via the Laravel [service container](/docs/{{version}}/container).\n\nSo, how are the validation rules evaluated? All you need to do is type-hint the request on your controller method. The incoming form request is validated before the controller method is called, meaning you do not need to clutter your controller with any validation logic:\n\n```php\n/**\n * Store a new blog post.\n */\npublic function store(StorePostRequest $request): RedirectResponse\n{\n    // The incoming request is valid...\n\n    // Retrieve the validated input data...\n    $validated = $request->validated();\n\n    // Retrieve a portion of the validated input data...\n    $validated = $request->safe()->only(['name', 'email']);\n    $validated = $request->safe()->except(['name', 'email']);\n\n    // Store the blog post...\n\n    return redirect('/posts');\n}\n```\n\nIf validation fails, a redirect response will be generated to send the user back to their previous location. The errors will also be flashed to the session so they are available for display. If the request was an XHR request, an HTTP response with a 422 status code will be returned to the user including a [JSON representation of the validation errors](#validation-error-response-format).\n\n> [!NOTE]\n> Need to add real-time form request validation to your Inertia powered Laravel frontend? Check out [Laravel Precognition](/docs/{{version}}/precognition).\n\n_[Truncated]_"}]


command: sed -n '1,220p' /Users/fabianwesner/Herd/shop/.agents/skills/laravel-best-practices/SKILL.md

SKILL.md

type: read

aggregated_output:
---
name: laravel-best-practices
description: "Apply this skill whenever writing, reviewing, or refactoring Laravel PHP code. This includes creating or modifying controllers, models, migrations, form requests, policies, jobs, scheduled commands, service classes, and Eloquent queries. Triggers for N+1 and query performance issues, caching strategies, authorization and security patterns, validation, error handling, queue and job configuration, route definitions, and architectural decisions. Also use for Laravel code reviews and refactoring existing Laravel code to follow best practices. Covers any task involving Laravel backend PHP code patterns."
license: MIT
metadata:
  author: laravel
---

# Laravel Best Practices

Best practices for Laravel, prioritized by impact. Each rule teaches what to do and why. For exact API syntax, verify with `search-docs`.

## Consistency First

Before applying any rule, check what the application already does. Laravel offers multiple valid approaches — the best choice is the one the codebase already uses, even if another pattern would be theoretically better. Inconsistency is worse than a suboptimal pattern.

Check sibling files, related controllers, models, or tests for established patterns. If one exists, follow it — don't introduce a second way. These rules are defaults for when no pattern exists yet, not overrides.

## Quick Reference

### 1. Database Performance → `rules/db-performance.md`

- Eager load with `with()` to prevent N+1 queries
- Enable `Model::preventLazyLoading()` in development
- Select only needed columns, avoid `SELECT *`
- `chunk()` / `chunkById()` for large datasets
- Index columns used in `WHERE`, `ORDER BY`, `JOIN`
- `withCount()` instead of loading relations to count
- `cursor()` for memory-efficient read-only iteration
- Never query in Blade templates

### 2. Advanced Query Patterns → `rules/advanced-queries.md`

- `addSelect()` subqueries over eager-loading entire has-many for a single value
- Dynamic relationships via subquery FK + `belongsTo`
- Conditional aggregates (`CASE WHEN` in `selectRaw`) over multiple count queries
- `setRelation()` to prevent circular N+1 queries
- `whereIn` + `pluck()` over `whereHas` for better index usage
- Two simple queries can beat one complex query
- Compound indexes matching `orderBy` column order
- Correlated subqueries in `orderBy` for has-many sorting (avoid joins)

### 3. Security → `rules/security.md`

- Define `$fillable` or `$guarded` on every model, authorize every action via policies or gates
- No raw SQL with user input — use Eloquent or query builder
- `{{ }}` for output escaping, `@csrf` on all POST/PUT/DELETE forms, `throttle` on auth and API routes
- Validate MIME type, extension, and size for file uploads
- Never commit `.env`, use `config()` for secrets, `encrypted` cast for sensitive DB fields

### 4. Caching → `rules/caching.md`

- `Cache::remember()` over manual get/put
- `Cache::flexible()` for stale-while-revalidate on high-traffic data
- `Cache::memo()` to avoid redundant cache hits within a request
- Cache tags to invalidate related groups
- `Cache::add()` for atomic conditional writes
- `once()` to memoize per-request or per-object lifetime
- `Cache::lock()` / `lockForUpdate()` for race conditions
- Failover cache stores in production

### 5. Eloquent Patterns → `rules/eloquent.md`

- Correct relationship types with return type hints
- Local scopes for reusable query constraints
- Global scopes sparingly — document their existence
- Attribute casts in the `casts()` method
- Cast date columns, use Carbon instances in templates
- `whereBelongsTo($model)` for cleaner queries
- Never hardcode table names — use `(new Model)->getTable()` or Eloquent queries

### 6. Validation & Forms → `rules/validation.md`

- Form Request classes, not inline validation
- Array notation `['required', 'email']` for new code; follow existing convention
- `$request->validated()` only — never `$request->all()`
- `Rule::when()` for conditional validation
- `after()` instead of `withValidator()`

### 7. Configuration → `rules/config.md`

- `env()` only inside config files
- `App::environment()` or `app()->isProduction()`
- Config, lang files, and constants over hardcoded text

### 8. Testing Patterns → `rules/testing.md`

- `LazilyRefreshDatabase` over `RefreshDatabase` for speed
- `assertModelExists()` over raw `assertDatabaseHas()`
- Factory states and sequences over manual overrides
- Use fakes (`Event::fake()`, `Exceptions::fake()`, etc.) — but always after factory setup, not before
- `recycle()` to share relationship instances across factories

### 9. Queue & Job Patterns → `rules/queue-jobs.md`

- `retry_after` must exceed job `timeout`; use exponential backoff `[1, 5, 10]`
- `ShouldBeUnique` to prevent duplicates; `ShouldBeUniqueUntilProcessing` for early lock release
- Always implement `failed()`; with `retryUntil()`, set `$tries = 0`
- `RateLimited` middleware for external API calls; `Bus::batch()` for related jobs
- Horizon for complex multi-queue scenarios

### 10. Routing & Controllers → `rules/routing.md`

- Implicit route model binding
- Scoped bindings for nested resources
- `Route::resource()` or `apiResource()`
- Methods under 10 lines — extract to actions/services
- Type-hint Form Requests for auto-validation

### 11. HTTP Client → `rules/http-client.md`

- Explicit `timeout` and `connectTimeout` on every request
- `retry()` with exponential backoff for external APIs
- Check response status or use `throw()`
- `Http::pool()` for concurrent independent requests
- `Http::fake()` and `preventStrayRequests()` in tests

### 12. Events, Notifications & Mail → `rules/events-notifications.md`, `rules/mail.md`

- Event discovery over manual registration; `event:cache` in production
- `ShouldDispatchAfterCommit` / `afterCommit()` inside transactions
- Queue notifications and mailables with `ShouldQueue`
- On-demand notifications for non-user recipients
- `HasLocalePreference` on notifiable models
- `assertQueued()` not `assertSent()` for queued mailables
- Markdown mailables for transactional emails

### 13. Error Handling → `rules/error-handling.md`

- `report()`/`render()` on exception classes or in `bootstrap/app.php` — follow existing pattern
- `ShouldntReport` for exceptions that should never log
- Throttle high-volume exceptions to protect log sinks
- `dontReportDuplicates()` for multi-catch scenarios
- Force JSON rendering for API routes
- Structured context via `context()` on exception classes

### 14. Task Scheduling → `rules/scheduling.md`

- `withoutOverlapping()` on variable-duration tasks
- `onOneServer()` on multi-server deployments
- `runInBackground()` for concurrent long tasks
- `environments()` to restrict to appropriate environments
- `takeUntilTimeout()` for time-bounded processing
- Schedule groups for shared configuration

### 15. Architecture → `rules/architecture.md`

- Single-purpose Action classes; dependency injection over `app()` helper
- Prefer official Laravel packages and follow conventions, don't override defaults
- Default to `ORDER BY id DESC` or `created_at DESC`; `mb_*` for UTF-8 safety
- `defer()` for post-response work; `Context` for request-scoped data; `Concurrency::run()` for parallel execution

### 16. Migrations → `rules/migrations.md`

- Generate migrations with `php artisan make:migration`
- `constrained()` for foreign keys
- Never modify migrations that have run in production
- Add indexes in the migration, not as an afterthought
- Mirror column defaults in model `$attributes`
- Reversible `down()` by default; forward-fix migrations for intentionally irreversible changes
- One concern per migration — never mix DDL and DML

### 17. Collections → `rules/collections.md`

- Higher-order messages for simple collection operations
- `cursor()` vs. `lazy()` — choose based on relationship needs
- `lazyById()` when updating records while iterating
- `toQuery()` for bulk operations on collections

### 18. Blade & Views → `rules/blade-views.md`

- `$attributes->merge()` in component templates
- Blade components over `@include`; `@pushOnce` for per-component scripts
- View Composers for shared view data
- `@aware` for deeply nested component props

### 19. Conventions & Style → `rules/style.md`

- Follow Laravel naming conventions for all entities
- Prefer Laravel helpers (`Str`, `Arr`, `Number`, `Uri`, `Str::of()`, `$request->string()`) over raw PHP functions
- No JS/CSS in Blade, no HTML in PHP classes
- Code should be readable; comments only for config files

## How to Apply

Always use a sub-agent to read rule files and explore this skill's content.

1. Identify the file type and select relevant sections (e.g., migration → §16, controller → §1, §3, §5, §6, §10)
2. Check sibling files for existing patterns — follow those first per Consistency First
3. Verify API syntax with `search-docs` for the installed Laravel version

status: completed


command: sed -n '1,220p' /Users/fabianwesner/Herd/shop/.agents/skills/pest-testing/SKILL.md

aggregated_output:
---
name: pest-testing
description: "Use this skill for Pest PHP testing in Laravel projects only. Trigger whenever any test is being written, edited, fixed, or refactored — including fixing tests that broke after a code change, adding assertions, converting PHPUnit to Pest, adding datasets, and TDD workflows. Always activate when the user asks how to write something in Pest, mentions test files or directories (tests/Feature, tests/Unit, tests/Browser), or needs browser testing, smoke testing multiple pages for JS errors, or architecture tests. Covers: test()/it()/expect() syntax, datasets, mocking, browser testing (visit/click/fill), smoke testing, arch(), Livewire component tests, RefreshDatabase, and all Pest 4 features. Do not use for factories, seeders, migrations, controllers, models, or non-test PHP code."
license: MIT
metadata:
  author: laravel
---

# Pest Testing 4

## Documentation

Use `search-docs` for detailed Pest 4 patterns and documentation.

## Basic Usage

### Creating Tests

All tests must be written using Pest. Use `php artisan make:test --pest {name}`.

### Test Organization

- Unit/Feature tests: `tests/Feature` and `tests/Unit` directories.
- Browser tests: `tests/Browser/` directory.
- Do NOT remove tests without approval - these are core application code.

### Basic Test Structure

Pest supports both `test()` and `it()` functions. Before writing new tests, check existing test files in the same directory to match the project's convention. Use `test()` if existing tests use `test()`, or `it()` if they use `it()`.

<!-- Basic Pest Test Example -->
```php
it('is true', function () {
    expect(true)->toBeTrue();
});
```

### Running Tests

- Run minimal tests with filter before finalizing: `php artisan test --compact --filter=testName`.
- Run all tests: `php artisan test --compact`.
- Run file: `php artisan test --compact tests/Feature/ExampleTest.php`.

## Assertions

Use specific assertions (`assertSuccessful()`, `assertNotFound()`) instead of `assertStatus()`:

<!-- Pest Response Assertion -->
```php
it('returns all', function () {
    $this->postJson('/api/docs', [])->assertSuccessful();
});
```

| Use | Instead of |
|-----|------------|
| `assertSuccessful()` | `assertStatus(200)` |
| `assertNotFound()` | `assertStatus(404)` |
| `assertForbidden()` | `assertStatus(403)` |

## Mocking

Import mock function before use: `use function Pest\Laravel\mock;`

## Datasets

Use datasets for repetitive tests (validation rules, etc.):

<!-- Pest Dataset Example -->
```php
it('has emails', function (string $email) {
    expect($email)->not->toBeEmpty();
})->with([
    'james' => 'james@laravel.com',
    'taylor' => 'taylor@laravel.com',
]);
```

## Pest 4 Features

| Feature | Purpose |
|---------|---------|
| Browser Testing | Full integration tests in real browsers |
| Smoke Testing | Validate multiple pages quickly |
| Visual Regression | Compare screenshots for visual changes |
| Test Sharding | Parallel CI runs |
| Architecture Testing | Enforce code conventions |

### Browser Test Example

Browser tests run in real browsers for full integration testing:

- Browser tests live in `tests/Browser/`.
- Use Laravel features like `Event::fake()`, `assertAuthenticated()`, and model factories.
- Use `RefreshDatabase` for clean state per test.
- Interact with page: click, type, scroll, select, submit, drag-and-drop, touch gestures.
- Test on multiple browsers (Chrome, Firefox, Safari) if requested.
- Test on different devices/viewports (iPhone 14 Pro, tablets) if requested.
- Switch color schemes (light/dark mode) when appropriate.
- Take screenshots or pause tests for debugging.

<!-- Pest Browser Test Example -->
```php
it('may reset the password', function () {
    Notification::fake();

    $this->actingAs(User::factory()->create());

    $page = visit('/sign-in');

    $page->assertSee('Sign In')
        ->assertNoJavaScriptErrors()
        ->click('Forgot Password?')
        ->fill('email', 'nuno@laravel.com')
        ->click('Send Reset Link')
        ->assertSee('We have emailed your password reset link!');

    Notification::assertSent(ResetPassword::class);
});
```

### Smoke Testing

Quickly validate multiple pages have no JavaScript errors:

<!-- Pest Smoke Testing Example -->
```php
$pages = visit(['/', '/about', '/contact']);

$pages->assertNoJavaScriptErrors()->assertNoConsoleLogs();
```

### Visual Regression Testing

Capture and compare screenshots to detect visual changes.

### Test Sharding

Split tests across parallel processes for faster CI runs.

### Architecture Testing

Pest 4 includes architecture testing (from Pest 3):

<!-- Architecture Test Example -->
```php
arch('controllers')
    ->expect('App\Http\Controllers')
    ->toExtendNothing()
    ->toHaveSuffix('Controller');
```

## Common Pitfalls

- Not importing `use function Pest\Laravel\mock;` before using mock
- Using `assertStatus(200)` instead of `assertSuccessful()`
- Forgetting datasets for repetitive validation tests
- Deleting tests without approval
- Forgetting `assertNoJavaScriptErrors()` in browser tests


output:
Chunk ID: f4fd4b
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2249
Output:
---
name: laravel-best-practices
description: "Apply this skill whenever writing, reviewing, or refactoring Laravel PHP code. This includes creating or modifying controllers, models, migrations, form requests, policies, jobs, scheduled commands, service classes, and Eloquent queries. Triggers for N+1 and query performance issues, caching strategies, authorization and security patterns, validation, error handling, queue and job configuration, route definitions, and architectural decisions. Also use for Laravel code reviews and refactoring existing Laravel code to follow best practices. Covers any task involving Laravel backend PHP code patterns."
license: MIT
metadata:
  author: laravel
---

# Laravel Best Practices

Best practices for Laravel, prioritized by impact. Each rule teaches what to do and why. For exact API syntax, verify with `search-docs`.

## Consistency First

Before applying any rule, check what the application already does. Laravel offers multiple valid approaches — the best choice is the one the codebase already uses, even if another pattern would be theoretically better. Inconsistency is worse than a suboptimal pattern.

Check sibling files, related controllers, models, or tests for established patterns. If one exists, follow it — don't introduce a second way. These rules are defaults for when no pattern exists yet, not overrides.

## Quick Reference

### 1. Database Performance → `rules/db-performance.md`

- Eager load with `with()` to prevent N+1 queries
- Enable `Model::preventLazyLoading()` in development
- Select only needed columns, avoid `SELECT *`
- `chunk()` / `chunkById()` for large datasets
- Index columns used in `WHERE`, `ORDER BY`, `JOIN`
- `withCount()` instead of loading relations to count
- `cursor()` for memory-efficient read-only iteration
- Never query in Blade templates

### 2. Advanced Query Patterns → `rules/advanced-queries.md`

- `addSelect()` subqueries over eager-loading entire has-many for a single value
- Dynamic relationships via subquery FK + `belongsTo`
- Conditional aggregates (`CASE WHEN` in `selectRaw`) over multiple count queries
- `setRelation()` to prevent circular N+1 queries
- `whereIn` + `pluck()` over `whereHas` for better index usage
- Two simple queries can beat one complex query
- Compound indexes matching `orderBy` column order
- Correlated subqueries in `orderBy` for has-many sorting (avoid joins)

### 3. Security → `rules/security.md`

- Define `$fillable` or `$guarded` on every model, authorize every action via policies or gates
- No raw SQL with user input — use Eloquent or query builder
- `{{ }}` for output escaping, `@csrf` on all POST/PUT/DELETE forms, `throttle` on auth and API routes
- Validate MIME type, extension, and size for file uploads
- Never commit `.env`, use `config()` for secrets, `encrypted` cast for sensitive DB fields

### 4. Caching → `rules/caching.md`

- `Cache::remember()` over manual get/put
- `Cache::flexible()` for stale-while-revalidate on high-traffic data
- `Cache::memo()` to avoid redundant cache hits within a request
- Cache tags to invalidate related groups
- `Cache::add()` for atomic conditional writes
- `once()` to memoize per-request or per-object lifetime
- `Cache::lock()` / `lockForUpdate()` for race conditions
- Failover cache stores in production

### 5. Eloquent Patterns → `rules/eloquent.md`

- Correct relationship types with return type hints
- Local scopes for reusable query constraints
- Global scopes sparingly — document their existence
- Attribute casts in the `casts()` method
- Cast date columns, use Carbon instances in templates
- `whereBelongsTo($model)` for cleaner queries
- Never hardcode table names — use `(new Model)->getTable()` or Eloquent queries

### 6. Validation & Forms → `rules/validation.md`

- Form Request classes, not inline validation
- Array notation `['required', 'email']` for new code; follow existing convention
- `$request->validated()` only — never `$request->all()`
- `Rule::when()` for conditional validation
- `after()` instead of `withValidator()`

### 7. Configuration → `rules/config.md`

- `env()` only inside config files
- `App::environment()` or `app()->isProduction()`
- Config, lang files, and constants over hardcoded text

### 8. Testing Patterns → `rules/testing.md`

- `LazilyRefreshDatabase` over `RefreshDatabase` for speed
- `assertModelExists()` over raw `assertDatabaseHas()`
- Factory states and sequences over manual overrides
- Use fakes (`Event::fake()`, `Exceptions::fake()`, etc.) — but always after factory setup, not before
- `recycle()` to share relationship instances across factories

### 9. Queue & Job Patterns → `rules/queue-jobs.md`

- `retry_after` must exceed job `timeout`; use exponential backoff `[1, 5, 10]`
- `ShouldBeUnique` to prevent duplicates; `ShouldBeUniqueUntilProcessing` for early lock release
- Always implement `failed()`; with `retryUntil()`, set `$tries = 0`
- `RateLimited` middleware for external API calls; `Bus::batch()` for related jobs
- Horizon for complex multi-queue scenarios

### 10. Routing & Controllers → `rules/routing.md`

- Implicit route model binding
- Scoped bindings for nested resources
- `Route::resource()` or `apiResource()`
- Methods under 10 lines — extract to actions/services
- Type-hint Form Requests for auto-validation

### 11. HTTP Client → `rules/http-client.md`

- Explicit `timeout` and `connectTimeout` on every request
- `retry()` with exponential backoff for external APIs
- Check response status or use `throw()`
- `Http::pool()` for concurrent independent requests
- `Http::fake()` and `preventStrayRequests()` in tests

### 12. Events, Notifications & Mail → `rules/events-notifications.md`, `rules/mail.md`

- Event discovery over manual registration; `event:cache` in production
- `ShouldDispatchAfterCommit` / `afterCommit()` inside transactions
- Queue notifications and mailables with `ShouldQueue`
- On-demand notifications for non-user recipients
- `HasLocalePreference` on notifiable models
- `assertQueued()` not `assertSent()` for queued mailables
- Markdown mailables for transactional emails

### 13. Error Handling → `rules/error-handling.md`

- `report()`/`render()` on exception classes or in `bootstrap/app.php` — follow existing pattern
- `ShouldntReport` for exceptions that should never log
- Throttle high-volume exceptions to protect log sinks
- `dontReportDuplicates()` for multi-catch scenarios
- Force JSON rendering for API routes
- Structured context via `context()` on exception classes

### 14. Task Scheduling → `rules/scheduling.md`

- `withoutOverlapping()` on variable-duration tasks
- `onOneServer()` on multi-server deployments
- `runInBackground()` for concurrent long tasks
- `environments()` to restrict to appropriate environments
- `takeUntilTimeout()` for time-bounded processing
- Schedule groups for shared configuration

### 15. Architecture → `rules/architecture.md`

- Single-purpose Action classes; dependency injection over `app()` helper
- Prefer official Laravel packages and follow conventions, don't override defaults
- Default to `ORDER BY id DESC` or `created_at DESC`; `mb_*` for UTF-8 safety
- `defer()` for post-response work; `Context` for request-scoped data; `Concurrency::run()` for parallel execution

### 16. Migrations → `rules/migrations.md`

- Generate migrations with `php artisan make:migration`
- `constrained()` for foreign keys
- Never modify migrations that have run in production
- Add indexes in the migration, not as an afterthought
- Mirror column defaults in model `$attributes`
- Reversible `down()` by default; forward-fix migrations for intentionally irreversible changes
- One concern per migration — never mix DDL and DML

### 17. Collections → `rules/collections.md`

- Higher-order messages for simple collection operations
- `cursor()` vs. `lazy()` — choose based on relationship needs
- `lazyById()` when updating records while iterating
- `toQuery()` for bulk operations on collections

### 18. Blade & Views → `rules/blade-views.md`

- `$attributes->merge()` in component templates
- Blade components over `@include`; `@pushOnce` for per-component scripts
- View Composers for shared view data
- `@aware` for deeply nested component props

### 19. Conventions & Style → `rules/style.md`

- Follow Laravel naming conventions for all entities
- Prefer Laravel helpers (`Str`, `Arr`, `Number`, `Uri`, `Str::of()`, `$request->string()`) over raw PHP functions
- No JS/CSS in Blade, no HTML in PHP classes
- Code should be readable; comments only for config files

## How to Apply

Always use a sub-agent to read rule files and explore this skill's content.

1. Identify the file type and select relevant sections (e.g., migration → §16, controller → §1, §3, §5, §6, §10)
2. Check sibling files for existing patterns — follow those first per Consistency First
3. Verify API syntax with `search-docs` for the installed Laravel version


output:
Chunk ID: 9996e3
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1275
Output:
---
name: pest-testing
description: "Use this skill for Pest PHP testing in Laravel projects only. Trigger whenever any test is being written, edited, fixed, or refactored — including fixing tests that broke after a code change, adding assertions, converting PHPUnit to Pest, adding datasets, and TDD workflows. Always activate when the user asks how to write something in Pest, mentions test files or directories (tests/Feature, tests/Unit, tests/Browser), or needs browser testing, smoke testing multiple pages for JS errors, or architecture tests. Covers: test()/it()/expect() syntax, datasets, mocking, browser testing (visit/click/fill), smoke testing, arch(), Livewire component tests, RefreshDatabase, and all Pest 4 features. Do not use for factories, seeders, migrations, controllers, models, or non-test PHP code."
license: MIT
metadata:
  author: laravel
---

# Pest Testing 4

## Documentation

Use `search-docs` for detailed Pest 4 patterns and documentation.

## Basic Usage

### Creating Tests

All tests must be written using Pest. Use `php artisan make:test --pest {name}`.

### Test Organization

- Unit/Feature tests: `tests/Feature` and `tests/Unit` directories.
- Browser tests: `tests/Browser/` directory.
- Do NOT remove tests without approval - these are core application code.

### Basic Test Structure

Pest supports both `test()` and `it()` functions. Before writing new tests, check existing test files in the same directory to match the project's convention. Use `test()` if existing tests use `test()`, or `it()` if they use `it()`.

<!-- Basic Pest Test Example -->
```php
it('is true', function () {
    expect(true)->toBeTrue();
});
```

### Running Tests

- Run minimal tests with filter before finalizing: `php artisan test --compact --filter=testName`.
- Run all tests: `php artisan test --compact`.
- Run file: `php artisan test --compact tests/Feature/ExampleTest.php`.

## Assertions

Use specific assertions (`assertSuccessful()`, `assertNotFound()`) instead of `assertStatus()`:

<!-- Pest Response Assertion -->
```php
it('returns all', function () {
    $this->postJson('/api/docs', [])->assertSuccessful();
});
```

| Use | Instead of |
|-----|------------|
| `assertSuccessful()` | `assertStatus(200)` |
| `assertNotFound()` | `assertStatus(404)` |
| `assertForbidden()` | `assertStatus(403)` |

## Mocking

Import mock function before use: `use function Pest\Laravel\mock;`

## Datasets

Use datasets for repetitive tests (validation rules, etc.):

<!-- Pest Dataset Example -->
```php
it('has emails', function (string $email) {
    expect($email)->not->toBeEmpty();
})->with([
    'james' => 'james@laravel.com',
    'taylor' => 'taylor@laravel.com',
]);
```

## Pest 4 Features

| Feature | Purpose |
|---------|---------|
| Browser Testing | Full integration tests in real browsers |
| Smoke Testing | Validate multiple pages quickly |
| Visual Regression | Compare screenshots for visual changes |
| Test Sharding | Parallel CI runs |
| Architecture Testing | Enforce code conventions |

### Browser Test Example

Browser tests run in real browsers for full integration testing:

- Browser tests live in `tests/Browser/`.
- Use Laravel features like `Event::fake()`, `assertAuthenticated()`, and model factories.
- Use `RefreshDatabase` for clean state per test.
- Interact with page: click, type, scroll, select, submit, drag-and-drop, touch gestures.
- Test on multiple browsers (Chrome, Firefox, Safari) if requested.
- Test on different devices/viewports (iPhone 14 Pro, tablets) if requested.
- Switch color schemes (light/dark mode) when appropriate.
- Take screenshots or pause tests for debugging.

<!-- Pest Browser Test Example -->
```php
it('may reset the password', function () {
    Notification::fake();

    $this->actingAs(User::factory()->create());

    $page = visit('/sign-in');

    $page->assertSee('Sign In')
        ->assertNoJavaScriptErrors()
        ->click('Forgot Password?')
        ->fill('email', 'nuno@laravel.com')
        ->click('Send Reset Link')
        ->assertSee('We have emailed your password reset link!');

    Notification::assertSent(ResetPassword::class);
});
```

### Smoke Testing

Quickly validate multiple pages have no JavaScript errors:

<!-- Pest Smoke Testing Example -->
```php
$pages = visit(['/', '/about', '/contact']);

$pages->assertNoJavaScriptErrors()->assertNoConsoleLogs();
```

### Visual Regression Testing

Capture and compare screenshots to detect visual changes.

### Test Sharding

Split tests across parallel processes for faster CI runs.

### Architecture Testing

Pest 4 includes architecture testing (from Pest 3):

<!-- Architecture Test Example -->
```php
arch('controllers')
    ->expect('App\Http\Controllers')
    ->toExtendNothing()
    ->toHaveSuffix('Controller');
```

## Common Pitfalls

- Not importing `use function Pest\Laravel\mock;` before using mock
- Using `assertStatus(200)` instead of `assertSuccessful()`
- Forgetting datasets for repetitive validation tests
- Deleting tests without approval
- Forgetting `assertNoJavaScriptErrors()` in browser tests


message: Using `laravel-best-practices` for the API/controller/resource work and `pest-testing` for the new feature tests. The docs confirm the JSON API helpers and 422 validation behavior we’ll assert against.

phase: commentary


type: message

role: assistant

text: Using `laravel-best-practices` for the API/controller/resource work and `pest-testing` for the new feature tests. The docs confirm the JSON API helpers and 422 validation behavior we’ll assert against.


cmd: sed -n '1,220p' .agents/skills/laravel-best-practices/rules/routing.md


cmd: sed -n '1,220p' .agents/skills/laravel-best-practices/rules/validation.md


cmd: sed -n '1,220p' .agents/skills/laravel-best-practices/rules/security.md


cmd: rg -n "withHeader\('Host'|postJson\('/api|Storefront" tests/Feature -g '*.php'


cmd: rg -n "class .*Factory|state\(" database/factories app/Models -g '*.php'


command: sed -n '1,220p' .agents/skills/laravel-best-practices/rules/routing.md

aggregated_output:
# Routing & Controllers Best Practices

## Use Implicit Route Model Binding

Let Laravel resolve models automatically from route parameters.

Incorrect:
```php
public function show(int $id)
{
    $post = Post::findOrFail($id);
}
```

Correct:
```php
public function show(Post $post)
{
    return view('posts.show', ['post' => $post]);
}
```

## Use Scoped Bindings for Nested Resources

Enforce parent-child relationships automatically.

```php
Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
    // $post is automatically scoped to $user
})->scopeBindings();
```

## Use Resource Controllers

Use `Route::resource()` or `apiResource()` for RESTful endpoints.

```php
Route::resource('posts', PostController::class);
// In routes/api.php — the /api prefix is applied automatically
Route::apiResource('posts', Api\PostController::class);
```

## Keep Controllers Thin

Aim for under 10 lines per method. Extract business logic to action or service classes.

Incorrect:
```php
public function store(Request $request)
{
    $validated = $request->validate([...]);
    if ($request->hasFile('image')) {
        $request->file('image')->move(public_path('images'));
    }
    $post = Post::create($validated);
    $post->tags()->sync($validated['tags']);
    event(new PostCreated($post));
    return redirect()->route('posts.show', $post);
}
```

Correct:
```php
public function store(StorePostRequest $request, CreatePostAction $create)
{
    $post = $create->execute($request->validated());

    return redirect()->route('posts.show', $post);
}
```

## Type-Hint Form Requests

Type-hinting Form Requests triggers automatic validation and authorization before the method executes.

Incorrect:
```php
public function store(Request $request): RedirectResponse
{
    $validated = $request->validate([
        'title' => ['required', 'max:255'],
        'body' => ['required'],
    ]);

    Post::create($validated);

    return redirect()->route('posts.index');
}
```

Correct:
```php
public function store(StorePostRequest $request): RedirectResponse
{
    Post::create($request->validated());

    return redirect()->route('posts.index');
}
```


output:
Chunk ID: 3fc4c7
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 543
Output:
# Routing & Controllers Best Practices

## Use Implicit Route Model Binding

Let Laravel resolve models automatically from route parameters.

Incorrect:
```php
public function show(int $id)
{
    $post = Post::findOrFail($id);
}
```

Correct:
```php
public function show(Post $post)
{
    return view('posts.show', ['post' => $post]);
}
```

## Use Scoped Bindings for Nested Resources

Enforce parent-child relationships automatically.

```php
Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
    // $post is automatically scoped to $user
})->scopeBindings();
```

## Use Resource Controllers

Use `Route::resource()` or `apiResource()` for RESTful endpoints.

```php
Route::resource('posts', PostController::class);
// In routes/api.php — the /api prefix is applied automatically
Route::apiResource('posts', Api\PostController::class);
```

## Keep Controllers Thin

Aim for under 10 lines per method. Extract business logic to action or service classes.

Incorrect:
```php
public function store(Request $request)
{
    $validated = $request->validate([...]);
    if ($request->hasFile('image')) {
        $request->file('image')->move(public_path('images'));
    }
    $post = Post::create($validated);
    $post->tags()->sync($validated['tags']);
    event(new PostCreated($post));
    return redirect()->route('posts.show', $post);
}
```

Correct:
```php
public function store(StorePostRequest $request, CreatePostAction $create)
{
    $post = $create->execute($request->validated());

    return redirect()->route('posts.show', $post);
}
```

## Type-Hint Form Requests

Type-hinting Form Requests triggers automatic validation and authorization before the method executes.

Incorrect:
```php
public function store(Request $request): RedirectResponse
{
    $validated = $request->validate([
        'title' => ['required', 'max:255'],
        'body' => ['required'],
    ]);

    Post::create($validated);

    return redirect()->route('posts.index');
}
```

Correct:
```php
public function store(StorePostRequest $request): RedirectResponse
{
    Post::create($request->validated());

    return redirect()->route('posts.index');
}
```


command: sed -n '1,220p' .agents/skills/laravel-best-practices/rules/validation.md

aggregated_output:
# Validation & Forms Best Practices

## Use Form Request Classes

Extract validation from controllers into dedicated Form Request classes.

Incorrect:
```php
public function store(Request $request)
{
    $request->validate([
        'title' => 'required|max:255',
        'body' => 'required',
    ]);
}
```

Correct:
```php
public function store(StorePostRequest $request)
{
    Post::create($request->validated());
}
```

## Array vs. String Notation for Rules

Array syntax is more readable and composes cleanly with `Rule::` objects. Prefer it in new code, but check existing Form Requests first and match whatever notation the project already uses.

```php
// Preferred for new code
'email' => ['required', 'email', Rule::unique('users')],

// Follow existing convention if the project uses string notation
'email' => 'required|email|unique:users',
```

## Always Use `validated()`

Get only validated data. Never use `$request->all()` for mass operations.

Incorrect:
```php
Post::create($request->all());
```

Correct:
```php
Post::create($request->validated());
```

## Use `Rule::when()` for Conditional Validation

```php
'company_name' => [
    Rule::when($this->account_type === 'business', ['required', 'string', 'max:255']),
],
```

## Use the `after()` Method for Custom Validation

Use `after()` instead of `withValidator()` for custom validation logic that depends on multiple fields.

```php
public function after(): array
{
    return [
        function (Validator $validator) {
            if ($this->quantity > Product::find($this->product_id)?->stock) {
                $validator->errors()->add('quantity', 'Not enough stock.');
            }
        },
    ];
}
```


output:
Chunk ID: 45c825
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 423
Output:
# Validation & Forms Best Practices

## Use Form Request Classes

Extract validation from controllers into dedicated Form Request classes.

Incorrect:
```php
public function store(Request $request)
{
    $request->validate([
        'title' => 'required|max:255',
        'body' => 'required',
    ]);
}
```

Correct:
```php
public function store(StorePostRequest $request)
{
    Post::create($request->validated());
}
```

## Array vs. String Notation for Rules

Array syntax is more readable and composes cleanly with `Rule::` objects. Prefer it in new code, but check existing Form Requests first and match whatever notation the project already uses.

```php
// Preferred for new code
'email' => ['required', 'email', Rule::unique('users')],

// Follow existing convention if the project uses string notation
'email' => 'required|email|unique:users',
```

## Always Use `validated()`

Get only validated data. Never use `$request->all()` for mass operations.

Incorrect:
```php
Post::create($request->all());
```

Correct:
```php
Post::create($request->validated());
```

## Use `Rule::when()` for Conditional Validation

```php
'company_name' => [
    Rule::when($this->account_type === 'business', ['required', 'string', 'max:255']),
],
```

## Use the `after()` Method for Custom Validation

Use `after()` instead of `withValidator()` for custom validation logic that depends on multiple fields.

```php
public function after(): array
{
    return [
        function (Validator $validator) {
            if ($this->quantity > Product::find($this->product_id)?->stock) {
                $validator->errors()->add('quantity', 'Not enough stock.');
            }
        },
    ];
}
```


command: sed -n '1,220p' .agents/skills/laravel-best-practices/rules/security.md

aggregated_output:
# Security Best Practices

## Mass Assignment Protection

Every model must define `$fillable` (whitelist) or `$guarded` (blacklist).

Incorrect:
```php
class User extends Model
{
    protected $guarded = []; // All fields are mass assignable
}
```

Correct:
```php
class User extends Model
{
    protected $fillable = [
        'name',
        'email',
        'password',
    ];
}
```

Never use `$guarded = []` on models that accept user input.

## Authorize Every Action

Use policies or gates in controllers. Never skip authorization.

Incorrect:
```php
public function update(UpdatePostRequest $request, Post $post)
{
    $post->update($request->validated());
}
```

Correct:
```php
public function update(UpdatePostRequest $request, Post $post)
{
    Gate::authorize('update', $post);

    $post->update($request->validated());
}
```

Or via Form Request:

```php
public function authorize(): bool
{
    return $this->user()->can('update', $this->route('post'));
}
```

## Prevent SQL Injection

Always use parameter binding. Never interpolate user input into queries.

Incorrect:
```php
DB::select("SELECT * FROM users WHERE name = '{$request->name}'");
```

Correct:
```php
User::where('name', $request->name)->get();

// Raw expressions with bindings
User::whereRaw('LOWER(name) = ?', [strtolower($request->name)])->get();
```

## Escape Output to Prevent XSS

Use `{{ }}` for HTML escaping. Only use `{!! !!}` for trusted, pre-sanitized content.

Incorrect:
```blade
{!! $user->bio !!}
```

Correct:
```blade
{{ $user->bio }}
```

## CSRF Protection

Include `@csrf` in all POST/PUT/DELETE Blade forms. In Inertia apps, the `@csrf` directive is automatically applied.

Incorrect:
```blade
<form method="POST" action="/posts">
    <input type="text" name="title">
</form>
```

Correct:
```blade
<form method="POST" action="/posts">
    @csrf
    <input type="text" name="title">
</form>
```

## Rate Limit Auth and API Routes

Apply `throttle` middleware to authentication and API routes.

```php
RateLimiter::for('login', function (Request $request) {
    return Limit::perMinute(5)->by($request->ip());
});

Route::post('/login', LoginController::class)->middleware('throttle:login');
```

## Validate File Uploads

Validate extension, MIME type, and size. The `mimes` rule checks extensions; use `mimetypes` for actual MIME type validation. Never trust client-provided filenames.

```php
public function rules(): array
{
    return [
        'avatar' => ['required', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048'],
    ];
}
```

Store with generated filenames:

```php
$path = $request->file('avatar')->store('avatars', 'public');
```

## Keep Secrets Out of Code

Never commit `.env`. Access secrets via `config()` only.

Incorrect:
```php
$key = env('API_KEY');
```

Correct:
```php
// config/services.php
'api_key' => env('API_KEY'),

// In application code
$key = config('services.api_key');
```

## Audit Dependencies

Run `composer audit` periodically to check for known vulnerabilities in dependencies. Automate this in CI to catch issues before deployment.

```bash
composer audit
```

## Encrypt Sensitive Database Fields

Use `encrypted` cast for API keys/tokens and mark the attribute as `hidden`.

Incorrect:
```php
class Integration extends Model
{
    protected function casts(): array
    {
        return [
            'api_key' => 'string',
        ];
    }
}
```

Correct:
```php
class Integration extends Model
{
    protected $hidden = ['api_key', 'api_secret'];

    protected function casts(): array
    {
        return [
            'api_key' => 'encrypted',
            'api_secret' => 'encrypted',
        ];
    }
}
```


output:
Chunk ID: 21fe49
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 915
Output:
# Security Best Practices

## Mass Assignment Protection

Every model must define `$fillable` (whitelist) or `$guarded` (blacklist).

Incorrect:
```php
class User extends Model
{
    protected $guarded = []; // All fields are mass assignable
}
```

Correct:
```php
class User extends Model
{
    protected $fillable = [
        'name',
        'email',
        'password',
    ];
}
```

Never use `$guarded = []` on models that accept user input.

## Authorize Every Action

Use policies or gates in controllers. Never skip authorization.

Incorrect:
```php
public function update(UpdatePostRequest $request, Post $post)
{
    $post->update($request->validated());
}
```

Correct:
```php
public function update(UpdatePostRequest $request, Post $post)
{
    Gate::authorize('update', $post);

    $post->update($request->validated());
}
```

Or via Form Request:

```php
public function authorize(): bool
{
    return $this->user()->can('update', $this->route('post'));
}
```

## Prevent SQL Injection

Always use parameter binding. Never interpolate user input into queries.

Incorrect:
```php
DB::select("SELECT * FROM users WHERE name = '{$request->name}'");
```

Correct:
```php
User::where('name', $request->name)->get();

// Raw expressions with bindings
User::whereRaw('LOWER(name) = ?', [strtolower($request->name)])->get();
```

## Escape Output to Prevent XSS

Use `{{ }}` for HTML escaping. Only use `{!! !!}` for trusted, pre-sanitized content.

Incorrect:
```blade
{!! $user->bio !!}
```

Correct:
```blade
{{ $user->bio }}
```

## CSRF Protection

Include `@csrf` in all POST/PUT/DELETE Blade forms. In Inertia apps, the `@csrf` directive is automatically applied.

Incorrect:
```blade
<form method="POST" action="/posts">
    <input type="text" name="title">
</form>
```

Correct:
```blade
<form method="POST" action="/posts">
    @csrf
    <input type="text" name="title">
</form>
```

## Rate Limit Auth and API Routes

Apply `throttle` middleware to authentication and API routes.

```php
RateLimiter::for('login', function (Request $request) {
    return Limit::perMinute(5)->by($request->ip());
});

Route::post('/login', LoginController::class)->middleware('throttle:login');
```

## Validate File Uploads

Validate extension, MIME type, and size. The `mimes` rule checks extensions; use `mimetypes` for actual MIME type validation. Never trust client-provided filenames.

```php
public function rules(): array
{
    return [
        'avatar' => ['required', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048'],
    ];
}
```

Store with generated filenames:

```php
$path = $request->file('avatar')->store('avatars', 'public');
```

## Keep Secrets Out of Code

Never commit `.env`. Access secrets via `config()` only.

Incorrect:
```php
$key = env('API_KEY');
```

Correct:
```php
// config/services.php
'api_key' => env('API_KEY'),

// In application code
$key = config('services.api_key');
```

## Audit Dependencies

Run `composer audit` periodically to check for known vulnerabilities in dependencies. Automate this in CI to catch issues before deployment.

```bash
composer audit
```

## Encrypt Sensitive Database Fields

Use `encrypted` cast for API keys/tokens and mark the attribute as `hidden`.

Incorrect:
```php
class Integration extends Model
{
    protected function casts(): array
    {
        return [
            'api_key' => 'string',
        ];
    }
}
```

Correct:
```php
class Integration extends Model
{
    protected $hidden = ['api_key', 'api_secret'];

    protected function casts(): array
    {
        return [
            'api_key' => 'encrypted',
            'api_secret' => 'encrypted',
        ];
    }
}
```


command: rg -n "withHeader\('Host'|postJson\('/api|Storefront" tests/Feature -g '*.php'

type: search

cmd: rg -n "withHeader\\('Host'|postJson\\('/api|Storefront" tests/Feature -g '*.php'

query: withHeader\('Host'|postJson\('/api|Storefront

path: Feature

aggregated_output:
tests/Feature/Storefront/ThemeDataTest.php:123:    $this->withHeader('Host', 'shop.test')
tests/Feature/Storefront/ThemeDataTest.php:129:    $this->withHeader('Host', 'shop.test')
tests/Feature/Storefront/CartCheckoutUiTest.php:5:use App\Livewire\Storefront\Account\Auth\Login as CustomerLogin;
tests/Feature/Storefront/CartCheckoutUiTest.php:6:use App\Livewire\Storefront\Cart\Show as CartShow;
tests/Feature/Storefront/CartCheckoutUiTest.php:7:use App\Livewire\Storefront\Checkout\Show as CheckoutShow;
tests/Feature/Storefront/CartCheckoutUiTest.php:8:use App\Livewire\Storefront\Products\Show as ProductShow;
tests/Feature/Catalog/CatalogUiTest.php:23:    $this->withHeader('Host', 'shop.test')
tests/Feature/Catalog/CatalogUiTest.php:30:    $this->withHeader('Host', 'shop.test')
tests/Feature/Catalog/CatalogUiTest.php:37:    $this->withHeader('Host', 'shop.test')
tests/Feature/Catalog/CatalogUiTest.php:45:    $this->withHeader('Host', 'shop.test')
tests/Feature/Foundation/CustomerAuthTest.php:3:use App\Livewire\Storefront\Account\Auth\Login as CustomerLogin;
tests/Feature/Foundation/CustomerAuthTest.php:4:use App\Livewire\Storefront\Account\Auth\Register as CustomerRegister;


output:
Chunk ID: 6ef831
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 298
Output:
tests/Feature/Storefront/ThemeDataTest.php:123:    $this->withHeader('Host', 'shop.test')
tests/Feature/Storefront/ThemeDataTest.php:129:    $this->withHeader('Host', 'shop.test')
tests/Feature/Storefront/CartCheckoutUiTest.php:5:use App\Livewire\Storefront\Account\Auth\Login as CustomerLogin;
tests/Feature/Storefront/CartCheckoutUiTest.php:6:use App\Livewire\Storefront\Cart\Show as CartShow;
tests/Feature/Storefront/CartCheckoutUiTest.php:7:use App\Livewire\Storefront\Checkout\Show as CheckoutShow;
tests/Feature/Storefront/CartCheckoutUiTest.php:8:use App\Livewire\Storefront\Products\Show as ProductShow;
tests/Feature/Catalog/CatalogUiTest.php:23:    $this->withHeader('Host', 'shop.test')
tests/Feature/Catalog/CatalogUiTest.php:30:    $this->withHeader('Host', 'shop.test')
tests/Feature/Catalog/CatalogUiTest.php:37:    $this->withHeader('Host', 'shop.test')
tests/Feature/Catalog/CatalogUiTest.php:45:    $this->withHeader('Host', 'shop.test')
tests/Feature/Foundation/CustomerAuthTest.php:3:use App\Livewire\Storefront\Account\Auth\Login as CustomerLogin;
tests/Feature/Foundation/CustomerAuthTest.php:4:use App\Livewire\Storefront\Account\Auth\Register as CustomerRegister;


command: rg -n "class .*Factory|state\(" database/factories app/Models -g '*.php'

type: search

cmd: rg -n "class .*Factory|state\\(" database/factories app/Models -g '*.php'

query: class .*Factory|state\(

path: factories

aggregated_output:
database/factories/ShippingZoneFactory.php:11:class ShippingZoneFactory extends Factory
database/factories/ProductVariantFactory.php:12:class ProductVariantFactory extends Factory
database/factories/ProductVariantFactory.php:38:        return $this->state(fn (array $attributes): array => [
database/factories/ProductVariantFactory.php:46:        return $this->state(fn (array $attributes): array => [
database/factories/ProductVariantFactory.php:54:        return $this->state(fn (array $attributes): array => [
database/factories/ProductVariantFactory.php:61:        return $this->state(fn (array $attributes): array => [
database/factories/TaxSettingsFactory.php:12:class TaxSettingsFactory extends Factory
database/factories/TaxSettingsFactory.php:39:        return $this->state(fn (array $attributes): array => [
database/factories/PageFactory.php:13:class PageFactory extends Factory
database/factories/PageFactory.php:36:        return $this->state(fn (array $attributes): array => [
database/factories/PageFactory.php:44:        return $this->state(fn (array $attributes): array => [
database/factories/ProductFactory.php:15:class ProductFactory extends Factory
database/factories/ProductFactory.php:41:        return $this->state(fn (array $attributes): array => [
database/factories/ProductFactory.php:49:        return $this->state(fn (array $attributes): array => [
database/factories/ProductOptionValueFactory.php:11:class ProductOptionValueFactory extends Factory
database/factories/ThemeFactory.php:12:class ThemeFactory extends Factory
database/factories/ThemeFactory.php:32:        return $this->state(fn (array $attributes): array => [
database/factories/ThemeFileFactory.php:12:class ThemeFileFactory extends Factory
database/factories/OrganizationFactory.php:10:class OrganizationFactory extends Factory
database/factories/ProductMediaFactory.php:13:class ProductMediaFactory extends Factory
database/factories/ProductMediaFactory.php:38:        return $this->state(fn (array $attributes): array => [
database/factories/ProductMediaFactory.php:45:        return $this->state(fn (array $attributes): array => [
database/factories/ProductOptionFactory.php:11:class ProductOptionFactory extends Factory
database/factories/CollectionFactory.php:14:class CollectionFactory extends Factory
database/factories/CollectionFactory.php:37:        return $this->state(fn (array $attributes): array => [
database/factories/CollectionFactory.php:44:        return $this->state(fn (array $attributes): array => [
database/factories/InventoryItemFactory.php:13:class InventoryItemFactory extends Factory
database/factories/InventoryItemFactory.php:39:        return $this->state(fn (array $attributes): array => [
database/factories/InventoryItemFactory.php:47:        return $this->state(fn (array $attributes): array => [
database/factories/InventoryItemFactory.php:54:        return $this->state(fn (array $attributes): array => [
database/factories/CartLineFactory.php:12:class CartLineFactory extends Factory
database/factories/CustomerFactory.php:11:class CustomerFactory extends Factory
database/factories/CustomerFactory.php:31:        return $this->state(fn (array $attributes): array => [
database/factories/StoreFactory.php:13:class StoreFactory extends Factory
database/factories/StoreFactory.php:37:        return $this->state(fn (array $attributes): array => [
database/factories/CheckoutFactory.php:14:class CheckoutFactory extends Factory
database/factories/CheckoutFactory.php:42:        return $this->state(fn (array $attributes): array => [
database/factories/NavigationMenuFactory.php:12:class NavigationMenuFactory extends Factory
database/factories/CartFactory.php:13:class CartFactory extends Factory
database/factories/CartFactory.php:33:        return $this->state(fn (array $attributes): array => [
database/factories/CartFactory.php:40:        return $this->state(fn (array $attributes): array => [
database/factories/UserFactory.php:12:class UserFactory extends Factory
database/factories/UserFactory.php:45:        return $this->state(fn (array $attributes) => [
database/factories/UserFactory.php:55:        return $this->state(fn (array $attributes) => [
database/factories/UserFactory.php:64:        return $this->state(fn (array $attributes): array => [
database/factories/ThemeSettingsFactory.php:11:class ThemeSettingsFactory extends Factory
database/factories/NavigationItemFactory.php:12:class NavigationItemFactory extends Factory
database/factories/NavigationItemFactory.php:33:        return $this->state(fn (array $attributes): array => [
database/factories/NavigationItemFactory.php:42:        return $this->state(fn (array $attributes): array => [
database/factories/NavigationItemFactory.php:51:        return $this->state(fn (array $attributes): array => [
database/factories/StoreDomainFactory.php:12:class StoreDomainFactory extends Factory
database/factories/StoreDomainFactory.php:32:        return $this->state(fn (array $attributes): array => [
database/factories/StoreDomainFactory.php:39:        return $this->state(fn (array $attributes): array => [
database/factories/StoreDomainFactory.php:46:        return $this->state(fn (array $attributes): array => [
database/factories/StoreSettingsFactory.php:11:class StoreSettingsFactory extends Factory
database/factories/ShippingRateFactory.php:12:class ShippingRateFactory extends Factory
database/factories/ShippingRateFactory.php:32:        return $this->state(fn (array $attributes): array => [
database/factories/ShippingRateFactory.php:45:        return $this->state(fn (array $attributes): array => [
database/factories/DiscountFactory.php:14:class DiscountFactory extends Factory
database/factories/DiscountFactory.php:40:        return $this->state(fn (array $attributes): array => [
database/factories/DiscountFactory.php:48:        return $this->state(fn (array $attributes): array => [


output:
Chunk ID: 246120
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1476
Output:
database/factories/ShippingZoneFactory.php:11:class ShippingZoneFactory extends Factory
database/factories/ProductVariantFactory.php:12:class ProductVariantFactory extends Factory
database/factories/ProductVariantFactory.php:38:        return $this->state(fn (array $attributes): array => [
database/factories/ProductVariantFactory.php:46:        return $this->state(fn (array $attributes): array => [
database/factories/ProductVariantFactory.php:54:        return $this->state(fn (array $attributes): array => [
database/factories/ProductVariantFactory.php:61:        return $this->state(fn (array $attributes): array => [
database/factories/TaxSettingsFactory.php:12:class TaxSettingsFactory extends Factory
database/factories/TaxSettingsFactory.php:39:        return $this->state(fn (array $attributes): array => [
database/factories/PageFactory.php:13:class PageFactory extends Factory
database/factories/PageFactory.php:36:        return $this->state(fn (array $attributes): array => [
database/factories/PageFactory.php:44:        return $this->state(fn (array $attributes): array => [
database/factories/ProductFactory.php:15:class ProductFactory extends Factory
database/factories/ProductFactory.php:41:        return $this->state(fn (array $attributes): array => [
database/factories/ProductFactory.php:49:        return $this->state(fn (array $attributes): array => [
database/factories/ProductOptionValueFactory.php:11:class ProductOptionValueFactory extends Factory
database/factories/ThemeFactory.php:12:class ThemeFactory extends Factory
database/factories/ThemeFactory.php:32:        return $this->state(fn (array $attributes): array => [
database/factories/ThemeFileFactory.php:12:class ThemeFileFactory extends Factory
database/factories/OrganizationFactory.php:10:class OrganizationFactory extends Factory
database/factories/ProductMediaFactory.php:13:class ProductMediaFactory extends Factory
database/factories/ProductMediaFactory.php:38:        return $this->state(fn (array $attributes): array => [
database/factories/ProductMediaFactory.php:45:        return $this->state(fn (array $attributes): array => [
database/factories/ProductOptionFactory.php:11:class ProductOptionFactory extends Factory
database/factories/CollectionFactory.php:14:class CollectionFactory extends Factory
database/factories/CollectionFactory.php:37:        return $this->state(fn (array $attributes): array => [
database/factories/CollectionFactory.php:44:        return $this->state(fn (array $attributes): array => [
database/factories/InventoryItemFactory.php:13:class InventoryItemFactory extends Factory
database/factories/InventoryItemFactory.php:39:        return $this->state(fn (array $attributes): array => [
database/factories/InventoryItemFactory.php:47:        return $this->state(fn (array $attributes): array => [
database/factories/InventoryItemFactory.php:54:        return $this->state(fn (array $attributes): array => [
database/factories/CartLineFactory.php:12:class CartLineFactory extends Factory
database/factories/CustomerFactory.php:11:class CustomerFactory extends Factory
database/factories/CustomerFactory.php:31:        return $this->state(fn (array $attributes): array => [
database/factories/StoreFactory.php:13:class StoreFactory extends Factory
database/factories/StoreFactory.php:37:        return $this->state(fn (array $attributes): array => [
database/factories/CheckoutFactory.php:14:class CheckoutFactory extends Factory
database/factories/CheckoutFactory.php:42:        return $this->state(fn (array $attributes): array => [
database/factories/NavigationMenuFactory.php:12:class NavigationMenuFactory extends Factory
database/factories/CartFactory.php:13:class CartFactory extends Factory
database/factories/CartFactory.php:33:        return $this->state(fn (array $attributes): array => [
database/factories/CartFactory.php:40:        return $this->state(fn (array $attributes): array => [
database/factories/UserFactory.php:12:class UserFactory extends Factory
database/factories/UserFactory.php:45:        return $this->state(fn (array $attributes) => [
database/factories/UserFactory.php:55:        return $this->state(fn (array $attributes) => [
database/factories/UserFactory.php:64:        return $this->state(fn (array $attributes): array => [
database/factories/ThemeSettingsFactory.php:11:class ThemeSettingsFactory extends Factory
database/factories/NavigationItemFactory.php:12:class NavigationItemFactory extends Factory
database/factories/NavigationItemFactory.php:33:        return $this->state(fn (array $attributes): array => [
database/factories/NavigationItemFactory.php:42:        return $this->state(fn (array $attributes): array => [
database/factories/NavigationItemFactory.php:51:        return $this->state(fn (array $attributes): array => [
database/factories/StoreDomainFactory.php:12:class StoreDomainFactory extends Factory
database/factories/StoreDomainFactory.php:32:        return $this->state(fn (array $attributes): array => [
database/factories/StoreDomainFactory.php:39:        return $this->state(fn (array $attributes): array => [
database/factories/StoreDomainFactory.php:46:        return $this->state(fn (array $attributes): array => [
database/factories/StoreSettingsFactory.php:11:class StoreSettingsFactory extends Factory
database/factories/ShippingRateFactory.php:12:class ShippingRateFactory extends Factory
database/factories/ShippingRateFactory.php:32:        return $this->state(fn (array $attributes): array => [
database/factories/ShippingRateFactory.php:45:        return $this->state(fn (array $attributes): array => [
database/factories/DiscountFactory.php:14:class DiscountFactory extends Factory
database/factories/DiscountFactory.php:40:        return $this->state(fn (array $attributes): array => [
database/factories/DiscountFactory.php:48:        return $this->state(fn (array $attributes): array => [


cmd: sed -n '1,220p' tests/Feature/Storefront/CartCheckoutUiTest.php


cmd: sed -n '1,260p' tests/Feature/Cart/CartServiceTest.php


cmd: sed -n '1,320p' tests/Feature/Checkout/CheckoutServiceTest.php


cmd: sed -n '1,220p' database/seeders/DatabaseSeeder.php


cmd: sed -n '1,260p' database/seeders/CartCheckoutSeeder.php


command: sed -n '1,260p' tests/Feature/Cart/CartServiceTest.php

aggregated_output:
<?php

use App\Enums\CartStatus;
use App\Enums\InventoryPolicy;
use App\Exceptions\CartVersionMismatchException;
use App\Exceptions\InsufficientInventoryException;
use App\Exceptions\InvalidCartOperationException;
use App\Models\Cart;
use App\Models\Customer;
use App\Models\InventoryItem;
use App\Models\Product;
use App\Models\ProductVariant;
use App\Models\Store;
use App\Services\CartService;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

function cartServiceStore(): Store
{
    $store = Store::factory()->create();
    app()->instance('current_store', $store);

    return $store;
}

function cartServiceVariant(Store $store, int $price = 2500, int $stock = 20, InventoryPolicy $policy = InventoryPolicy::Deny): ProductVariant
{
    $product = Product::factory()
        ->withDefaultVariant($price)
        ->create(['store_id' => $store->getKey()]);
    $variant = ProductVariant::withoutGlobalScopes()
        ->where('product_id', $product->getKey())
        ->firstOrFail();

    InventoryItem::withoutGlobalScopes()
        ->where('variant_id', $variant->getKey())
        ->update([
            'quantity_on_hand' => $stock,
            'quantity_reserved' => 0,
            'policy' => $policy,
        ]);

    return $variant->refresh();
}

test('cart service creates carts and mutates lines with version increments and price snapshots', function () {
    $store = cartServiceStore();
    $variant = cartServiceVariant($store, price: 2500, stock: 20);
    $service = app(CartService::class);
    $cart = $service->create($store);

    expect($cart->store_id)->toBe($store->getKey())
        ->and($cart->currency)->toBe('EUR')
        ->and($cart->cart_version)->toBe(1);

    $line = $service->addLine($cart, $variant->getKey(), 2, expectedVersion: 1);

    expect($line->quantity)->toBe(2)
        ->and($line->unit_price_amount)->toBe(2500)
        ->and($line->line_subtotal_amount)->toBe(5000)
        ->and($cart->refresh()->cart_version)->toBe(2);

    $service->addLine($cart, $variant->getKey(), 1, expectedVersion: 2);

    expect(Cart::withoutGlobalScopes()->find($cart->getKey())?->lines()->withoutGlobalScopes()->count())->toBe(1)
        ->and($line->refresh()->quantity)->toBe(3)
        ->and($cart->refresh()->cart_version)->toBe(3);

    $variant->forceFill(['price_amount' => 9999])->save();
    $service->updateLineQuantity($cart, $line->getKey(), 4, expectedVersion: 3);

    expect($line->refresh()->unit_price_amount)->toBe(2500)
        ->and($line->line_subtotal_amount)->toBe(10000)
        ->and($cart->refresh()->cart_version)->toBe(4);

    $service->updateLineQuantity($cart, $line->getKey(), 0, expectedVersion: 4);

    expect($cart->refresh()->cart_version)->toBe(5)
        ->and($cart->lines()->withoutGlobalScopes()->count())->toBe(0);
});

test('cart service rejects stale versions inactive products and insufficient stock', function () {
    $store = cartServiceStore();
    $variant = cartServiceVariant($store, stock: 2);
    $service = app(CartService::class);
    $cart = $service->create($store);

    expect(fn () => $service->addLine($cart, $variant->getKey(), 1, expectedVersion: 99))
        ->toThrow(CartVersionMismatchException::class);

    $draftProduct = Product::factory()
        ->draft()
        ->withDefaultVariant()
        ->create(['store_id' => $store->getKey()]);
    $draftVariant = ProductVariant::withoutGlobalScopes()
        ->where('product_id', $draftProduct->getKey())
        ->firstOrFail();

    expect(fn () => $service->addLine($cart, $draftVariant->getKey(), 1))
        ->toThrow(InvalidCartOperationException::class);

    expect(fn () => $service->addLine($cart, $variant->getKey(), 5))
        ->toThrow(InsufficientInventoryException::class);
});

test('cart service allows oversell for continue policy and merges guest carts into customer carts', function () {
    $store = cartServiceStore();
    $firstVariant = cartServiceVariant($store, stock: 0, policy: InventoryPolicy::Continue);
    $secondVariant = cartServiceVariant($store, stock: 10);
    $service = app(CartService::class);
    $guestCart = $service->create($store);
    $customer = Customer::factory()->create(['store_id' => $store->getKey()]);
    $customerCart = $service->create($store, $customer);

    $service->addLine($guestCart, $firstVariant->getKey(), 5);
    $service->addLine($customerCart, $firstVariant->getKey(), 2);
    $service->addLine($customerCart, $secondVariant->getKey(), 3);

    $merged = $service->mergeOnLogin($guestCart, $customerCart);

    expect($merged->lines()->withoutGlobalScopes()->where('variant_id', $firstVariant->getKey())->first()?->quantity)->toBe(5)
        ->and($merged->lines()->withoutGlobalScopes()->where('variant_id', $secondVariant->getKey())->first()?->quantity)->toBe(3)
        ->and($guestCart->refresh()->status)->toBe(CartStatus::Abandoned);
});


command: sed -n '1,220p' tests/Feature/Storefront/CartCheckoutUiTest.php

aggregated_output:
<?php

use App\Enums\CartStatus;
use App\Enums\CheckoutStatus;
use App\Livewire\Storefront\Account\Auth\Login as CustomerLogin;
use App\Livewire\Storefront\Cart\Show as CartShow;
use App\Livewire\Storefront\Checkout\Show as CheckoutShow;
use App\Livewire\Storefront\Products\Show as ProductShow;
use App\Models\Cart;
use App\Models\Checkout;
use App\Models\Customer;
use App\Models\InventoryItem;
use App\Models\Product;
use App\Models\ProductVariant;
use App\Models\ShippingRate;
use App\Models\Store;
use App\Services\CartService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;

uses(RefreshDatabase::class);

beforeEach(function () {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function storefrontUiStore(): Store
{
    $store = Store::query()->where('handle', 'acme-fashion')->firstOrFail();
    app()->instance('current_store', $store);

    return $store;
}

function storefrontUiVariant(Store $store): ProductVariant
{
    $product = Product::withoutGlobalScopes()
        ->where('store_id', $store->getKey())
        ->where('handle', 'classic-cotton-t-shirt')
        ->firstOrFail();

    return ProductVariant::withoutGlobalScopes()
        ->where('product_id', $product->getKey())
        ->oldest('position')
        ->firstOrFail();
}

test('product detail add to cart persists a session cart', function () {
    $store = storefrontUiStore();
    $variant = storefrontUiVariant($store);

    Livewire::test(ProductShow::class, ['handle' => 'classic-cotton-t-shirt'])
        ->set('quantity', 2)
        ->call('addToCart')
        ->assertDispatched('cart-updated');

    $cart = Cart::withoutGlobalScopes()->findOrFail(session('cart_id'));

    expect($cart->store_id)->toBe($store->getKey())
        ->and($cart->lines()->withoutGlobalScopes()->where('variant_id', $variant->getKey())->first()?->quantity)->toBe(2);
});

test('cart page updates and removes line quantities', function () {
    $store = storefrontUiStore();
    $variant = storefrontUiVariant($store);
    $cart = app(CartService::class)->create($store);
    session(['cart_id' => $cart->getKey()]);
    $line = app(CartService::class)->addLine($cart, $variant->getKey(), 2);

    Livewire::test(CartShow::class)
        ->assertSee('Classic Cotton T-Shirt')
        ->call('increaseQuantity', $line->getKey())
        ->assertDispatched('cart-updated');

    expect($line->refresh()->quantity)->toBe(3);

    Livewire::test(CartShow::class)
        ->call('removeLine', $line->getKey())
        ->assertDispatched('cart-updated');

    expect($cart->lines()->withoutGlobalScopes()->count())->toBe(0);
});

test('customer login merges an existing guest cart without creating empty carts', function () {
    $store = storefrontUiStore();
    $variant = storefrontUiVariant($store);
    $customer = Customer::withoutGlobalScopes()
        ->where('store_id', $store->getKey())
        ->where('email', 'customer@acme.test')
        ->firstOrFail();
    $customerCart = app(CartService::class)->create($store, $customer);
    $guestCart = app(CartService::class)->create($store);
    session(['cart_id' => $guestCart->getKey()]);

    app(CartService::class)->addLine($customerCart, $variant->getKey(), 1);
    app(CartService::class)->addLine($guestCart, $variant->getKey(), 2);

    Livewire::test(CustomerLogin::class)
        ->set('email', 'customer@acme.test')
        ->set('password', 'password')
        ->call('login');

    expect($guestCart->refresh()->status)->toBe(CartStatus::Abandoned)
        ->and($customerCart->refresh()->lines()->withoutGlobalScopes()->where('variant_id', $variant->getKey())->first()?->quantity)->toBe(2)
        ->and(Cart::withoutGlobalScopes()->where('store_id', $store->getKey())->count())->toBe(2);
});

test('checkout page progresses through address shipping discount and payment selection', function () {
    $store = storefrontUiStore();
    $variant = storefrontUiVariant($store);
    $cart = app(CartService::class)->create($store);
    session(['cart_id' => $cart->getKey()]);
    app(CartService::class)->addLine($cart, $variant->getKey(), 2);

    $rate = ShippingRate::withoutGlobalScopes()
        ->whereHas('zone', fn ($query) => $query->withoutGlobalScopes()->where('store_id', $store->getKey()))
        ->where('name', 'Standard Shipping')
        ->firstOrFail();

    Livewire::test(CheckoutShow::class)
        ->set('email', 'buyer@example.test')
        ->set('shippingAddress.first_name', 'Test')
        ->set('shippingAddress.last_name', 'Buyer')
        ->set('shippingAddress.address1', 'Main Street 1')
        ->set('shippingAddress.city', 'Berlin')
        ->set('shippingAddress.country', 'DE')
        ->set('shippingAddress.postal_code', '10115')
        ->call('saveAddress')
        ->assertSet('step', 'shipping')
        ->set('selectedShippingRateId', $rate->getKey())
        ->call('selectShippingMethod')
        ->assertSet('step', 'payment')
        ->set('discountCode', 'SAVE10')
        ->call('applyDiscount')
        ->assertHasNoErrors()
        ->set('paymentMethod', 'credit_card')
        ->call('selectPaymentMethod')
        ->assertSet('step', 'reserved');

    $checkout = Checkout::withoutGlobalScopes()->where('cart_id', $cart->getKey())->firstOrFail();
    $inventory = InventoryItem::withoutGlobalScopes()->where('variant_id', $variant->getKey())->firstOrFail();

    expect($checkout->status)->toBe(CheckoutStatus::PaymentSelected)
        ->and($checkout->discount_code)->toBe('SAVE10')
        ->and($checkout->totals_json['discount'])->toBeGreaterThan(0)
        ->and($inventory->quantity_reserved)->toBe(2);
});