The staged diff check passed. I’m committing the storefront cart/checkout UI slice now.


git commit -m "Connect storefront cart checkout UI"


git commit -m "Connect storefront cart checkout UI"

[2026-05-04-codex-5-5-goal bdf85923] Connect storefront cart checkout UI
 15 files changed, 1268 insertions(+), 14 deletions(-)
 create mode 100644 app/Livewire/Storefront/Cart/Show.php
 create mode 100644 app/Livewire/Storefront/CartDrawer.php
 create mode 100644 app/Livewire/Storefront/Checkout/Show.php
 create mode 100644 resources/views/livewire/storefront/cart-drawer.blade.php
 create mode 100644 resources/views/livewire/storefront/cart/show.blade.php
 create mode 100644 resources/views/livewire/storefront/checkout/show.blade.php
 create mode 100644 tests/Feature/Storefront/CartCheckoutUiTest.php


Output:
Chunk ID: 1a7bb2
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 153
Output:
[2026-05-04-codex-5-5-goal bdf85923] Connect storefront cart checkout UI
 15 files changed, 1268 insertions(+), 14 deletions(-)
 create mode 100644 app/Livewire/Storefront/Cart/Show.php
 create mode 100644 app/Livewire/Storefront/CartDrawer.php
 create mode 100644 app/Livewire/Storefront/Checkout/Show.php
 create mode 100644 resources/views/livewire/storefront/cart-drawer.blade.php
 create mode 100644 resources/views/livewire/storefront/cart/show.blade.php
 create mode 100644 resources/views/livewire/storefront/checkout/show.blade.php
 create mode 100644 tests/Feature/Storefront/CartCheckoutUiTest.php


Committed `bdf85923`. I’m moving to the remaining Phase 4 API surface next: versioned storefront cart and checkout JSON endpoints with resources and focused API tests.


api routing laravel 12 apiResource json resources

eloquent api resources conditional relationships


# Search Results

## Http Tests Testing JSON APIs
Http Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md

## Testing JSON APIs

Laravel 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:

```php tab=Pest
<?php

test('making an api request', function () {
    $response = $this->postJson('/api/user', ['name' => 'Sally']);

    $response
        ->assertStatus(201)
        ->assertJson([
            'created' => true,
        ]);
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * A basic functional test example.
     */
    public function test_making_an_api_request(): void
    {
        $response = $this->postJson('/api/user', ['name' => 'Sally']);

        $response
            ->assertStatus(201)
            ->assertJson([
                'created' => true,
            ]);
    }
}
```

In 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:

```php tab=Pest
expect($response['created'])->toBeTrue();
```

```php tab=PHPUnit
$this->assertTrue($response['created']);
```

> [!NOTE]
> 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.

---

## Validation Validation Quickstart Displaying the Validation Errors XHR Requests and Validation
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### XHR Requests and Validation

In 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.

---

## Pest3 Now Available New Configuration API
Pest3 Now Available | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/pest3-now-available.md

## New Configuration API

Pest 1 / Pest 2's configuration API was a little bit confusing, the `uses()` function that was originally made only for having the `$this` variable within closure bound to the test case instance, ended up being used for pretty much everything.

In Pest 3, we've introduced a new configuration API that is more intuitive and easier to use. The new configuration API is based on the `pest()` function, which allows you to configure Pest using a fluent and expressive API.

> Note: the `uses()` function is still available in Pest 3, and we don't have plans to remove it. However, we recommend using the new configuration API for new projects.

```diff
-uses(TestCase::class)->in(__DIR__);
+pest()->extends(TestCase::class);

-uses(TestCase::class, RefreshDatabase::class)->in('Features');
+pest()->extends(TestCase::class)->use(RefreshDatabase::class)->in('Features');

-uses()->compact();
+pest()->printer()->compact();
```

And of course, any method that was available on the `uses()` API, like `->beforeEach()` or `->group()` is still available on the new `pest()` configuration API; we've just made it more intuitive and easier to use.

---

## Urls URLs for Named Routes Eloquent Models
Urls | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/urls.md

#### Eloquent Models

You will often be generating URLs using the route key (typically the primary key) of [Eloquent models](/docs/{{version}}/eloquent). For this reason, you may pass Eloquent models as parameter values. The `route` helper will automatically extract the model's route key:

```php
echo route('post.show', ['post' => $post]);
```

---

## Validation Available Validation Rules json
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### json

The field under validation must be a valid JSON string.

---

## Http Tests Testing JSON APIs Fluent JSON Testing
Http Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md

### Fluent JSON Testing

Laravel also offers a beautiful way to fluently test your application's JSON responses. To get started, pass a closure to the `assertJson` method. This closure will be invoked with an instance of `Illuminate\Testing\Fluent\AssertableJson` which can be used to make assertions against the JSON that was returned by your application. The `where` method may be used to make assertions against a particular attribute of the JSON, while the `missing` method may be used to assert that a particular attribute is missing from the JSON:

```php tab=Pest
use Illuminate\Testing\Fluent\AssertableJson;

test('fluent json', function () {
    $response = $this->getJson('/users/1');

    $response
        ->assertJson(fn (AssertableJson $json) =>
            $json->where('id', 1)
                ->where('name', 'Victoria Faith')
                ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com'))
                ->whereNot('status', 'pending')
                ->missing('password')
                ->etc()
        );
});
```

```php tab=PHPUnit
use Illuminate\Testing\Fluent\AssertableJson;

/**
 * A basic functional test example.
 */
public function test_fluent_json(): void
{
    $response = $this->getJson('/users/1');

    $response
        ->assertJson(fn (AssertableJson $json) =>
            $json->where('id', 1)
                ->where('name', 'Victoria Faith')
                ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com'))
                ->whereNot('status', 'pending')
                ->missing('password')
                ->etc()
        );
}
```

#### Understanding the `etc` Method

In the example above, you may have noticed we invoked the `etc` method at the end of our assertion chain. This method informs Laravel that there may be other attributes present on the JSON object. If the `etc` method is not used, the test will fail if other attributes that you did not make assertions against exist on the JSON object.

The intention behind this behavior is to protect you from unintentionally exposing sensitive information in your JSON responses by forcing you to either explicitly make an assertion against the attribute or explicitly allow additional attributes via the `etc` method.

However, you should be aware that not including the `etc` method in your assertion chain does not ensure that additional attributes are not being added to arrays that are nested within your JSON object. The `etc` method only ensures that no additional attributes exist at the nesting level in which the `etc` method is invoked.

---

## Cli Api Reference
Cli Api Reference | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/cli-api-reference.md

---
title: CLI API Reference
description: In the preceding chapters of the Pest documentation, we have covered numerous CLI options that are available in Pest. Nevertheless, Pest provides many other options that could prove beneficial. The complete CLI API Reference is provided below for your convenience.
---

# CLI API Reference

In the preceding chapters of the Pest documentation, we have covered numerous CLI options that are available in Pest. Nevertheless, Pest provides many other options that could prove beneficial. The complete CLI API Reference is provided below for your convenience.

## Configuration

- `--init`: Initialize a standard Pest configuration.
- `--bootstrap <file>` A PHP script that is included before the tests run.
- `-c|--configuration <file>`: Read configuration from XML file.
- `--no-configuration`: Ignore default configuration file (phpunit.xml).
- `--extension <class>`: Register test runner extension with bootstrap <class>.
- `--no-extensions`: Do not load PHPUnit extensions.
- `--include-path <path(s)>`: Prepend PHP's include_path with given path(s).
- `-d <key[=value]>`: Set a php.ini value.
- `--cache-directory <dir>`: Specify cache directory.
- `--generate-configuration`: Generate configuration file with suggested settings.
- `--migrate-configuration`: Migrate configuration file to current format.
- `--generate-baseline <file>`: Generate baseline for issues.
- `--use-baseline <file>`: Use baseline to ignore issues.
- `--ignore-baseline`: Do not use baseline to ignore issues.
- `--test-directory`: Specify test directory containing Pest.php, TestCase.php, helpers and your tests. Default: tests

## Selection

- `--bail`: Stop execution upon first not-passed test.
- `--ci`: Ignore focused tests using `->only()` and run the entire test suite.
- `--todos`: Output to standard output the list of todos.
- `--notes`: Output to standard output tests with notes.
- `--issue`: Output to standard output tests with the given issue number.
- `--pr`: Output to standard output tests with the given pull request number.
- `--pull-request`: Output to standard output tests with the given pull request number (alias for `--pr`).
- `--flaky`: Output to standard output tests marked as flaky.
- `--retry`: Run non-passing tests first and stop execution upon first error or failure.
- `--list-suites` List available test suites.
- `--testsuite <name>`: Only run tests from the specified test suite(s).
- `--exclude-testsuite <name>`: Exclude tests from the specified test suite(s).
- `--list-groups`: List available test groups.
- `--group <name>`: Only run tests from the specified group(s).
- `--exclude-group <name>`: Exclude tests from the specified group(s).
- `--covers <name>`: Only run tests that intend to cover `<name>`.
- `--uses <name>`: Only run tests that intend to use `<name>`.
- `--requires-php-extension <name>`: Only run tests that require PHP extension <name>.
- `--list-test-files`: List available test files.
- `--list-tests`: List available tests.
- `--list-tests-xml <file>`: List available tests in XML format.
- `--filter <pattern>`: Filter which tests to run
- `--exclude-filter <pattern>`: Exclude tests for the specified filter pattern.
- `--test-suffix <suffixes>`: Only search for test in files with specified suffix(es). Default: Test.php,.phpt

## Execution

- `--parallel` Run tests in parallel.
- `--shard <index/total>`: Run only the given shard of tests (e.g. `--shard=1/5`). Uses time-balanced distribution when `tests/.pest/shards.json` exists.
- `--update-shards`: Update `tests/.pest/shards.json` with test timing data for time-balanced sharding. Can be combined with `--parallel`.
- `--update-snapshots`: Update snapshots for tests using the "toMatchSnapshot" expectation.
- `--globals-backup`: Backup and restore $GLOBALS for each test.
- `--static-backup`: Backup and restore static properties for each test.
- `--strict-coverage`: Be strict about code coverage metadata.
- `--strict-global-state`: Be strict about changes to global state.
- `--disallow-test-output`: Be strict about output during tests.
- `--enforce-time-limit`: Enforce time limit based on test size.
- `--default-time-limit <sec>`: Timeout in seconds for tests that have no declared size.
- `--dont-report-useless-tests`: Do not report tests that do not test anything.
- `--stop-on-defect`: Stop execution upon first not-passed test.
- `--stop-on-error`: Stop execution upon first error.
- `--stop-on-failure`: Stop execution upon first error or failure.
- `--stop-on-warning`: Stop execution upon first warning.
- `--stop-on-risky`: Stop execution upon first risky test.
- `--stop-on-deprecation`: Stop after first test that triggered a deprecation.
- `--stop-on-notice`: Stop after first test that triggered a notice.
- `--stop-on-skipped`: Stop execution upon first skipped test.
- `--stop-on-incomplete`: Stop execution upon first incomplete test.
- `--fail-on-empty-test-suite`: Signal failure using shell exit code when no tests were run.
- `--fail-on-warning`: Treat tests with warnings as failures.
- `--fail-on-risky`: Treat risky tests as failures.
- `--fail-on-deprecation`: Signal failure using shell exit code when a deprecation was triggered.
- `--fail-on-phpunit-deprecation`: Signal failure using shell exit code when a PHPUnit deprecation was triggered.
- `--fail-on-notice`: Signal failure using shell exit code when a notice was triggered.
- `--fail-on-skipped`: Treat skipped tests as failures.
- `--fail-on-incomplete`: Signal failure using shell exit code when a test was marked incomplete.
- `--cache-result`: Write test results to cache file.
- `--do-not-cache-result`: Do not write test results to cache file
- `--order-by <order>`: Run tests in order: default|defects|depends|duration|no-depends|random|reverse|size.
- `--random-order-seed <N>`: Use the specified random seed when running tests in random order

## Reporting

- `--colors <flag>`: Use colors in output ("never", "auto" or "always").
- `--columns <n>`: Number of columns to use for progress output.
- `--columns max`: Use maximum number of columns for progress output.
- `--stderr`: Write to STDERR instead of STDOUT.
- `--no-progress`: Disable output of test execution progress.
- `--no-results`: Disable output of test results.
- `--no-output`: Disable all output.
- `--display-incomplete`: Display details for incomplete tests.
- `--display-skipped`: Display details for skipped tests.
- `--display-deprecations`: Display details for deprecations triggered by tests.
- `--display-phpunit-deprecations`: Display details for PHPUnit deprecations.
- `--display-errors`: Display details for errors triggered by tests.
- `--display-notices`: Display details for notices triggered by tests.
- `--display-warnings`: Display details for warnings triggered by tests.
- `--reverse-list`: Print defects in reverse order.
- `--teamcity`: Replace default progress and result output with TeamCity format.
- `--testdox`: Replace default result output with TestDox format
- `--testdox-summary`: Repeat TestDox output for tests with errors, failures, or issues.
- `--debug`: Replace default progress and result output with debugging information.
- `--compact`:  Replace default result output with Compact format

## Logging

- `--log-junit <file>`: Write test results in JUnit XML format to file.
- `--log-teamcity <file>`: Write test results in TeamCity format to file.
- `--testdox-html <file>`: Write test results in TestDox format (HTML) to file.
- `--testdox-text <file>`: Write test results in TestDox format (plain text) to file.
- `--log-events-text <file>`: Stream events as plain text to file.
- `--log-events-verbose-text <file>`: Stream events as plain text (with telemetry information) to file.
- `--no-logging`: Ignore logging configured in the XML configuration file

## Code Coverage

- `--coverage`: Generate code coverage report and output to standard output.
- `--coverage --min=<value>`: Set the minimum required coverage percentage, and fail if not met.
- `--coverage --exactly=<value>`: Set the exact required coverage percentage, and fail if not met.
- `--coverage --only-covered`: Hide files with 0% coverage from the code coverage report.
- `--coverage-clover <file>`: Write code coverage report in Clover XML format to file.
- `--coverage-cobertura <file>`: Write code coverage report in Cobertura XML format to file.
- `--coverage-crap4j <file>`: Write code coverage report in Crap4J XML format to file.
- `--coverage-html <dir>`: Write code coverage report in HTML format to directory.
- `--coverage-php <file>`: Write serialized code coverage data to file.
- `--coverage-text=<file>`: Write code coverage report in text format to file [default: standard output].
- `--only-summary-for-coverage-text`: Option for code coverage report in text format: only show summary.
- `--show-uncovered-for-coverage-text`: Option for code coverage report in text format: show uncovered files.
- `--coverage-xml <dir>`: Write code coverage report in XML format to directory.
- `--warm-coverage-cache`: Warm static analysis cache.
- `--coverage-filter <dir>`: Include `<dir>`: in code coverage reporting.
- `--path-coverage`: Report path coverage in addition to line coverage.
- `--disable-coverage-ignore`: Disable metadata for ignoring code coverage.
- `--no-coverage`: Ignore code coverage reporting configured in the XML configuration file

## Mutation Testing

- `--mutate`: Runs mutation testing, to understand the quality of your tests.
- `--mutate --parallel`: Runs mutation testing in parallel.
- `--mutate --min`: Set the minimum required mutation score, and fail if not met.
- `--mutate --id`:  Run only the mutation with the given ID. But E.g. --id=ecb35ab30ffd3491. Note, you need to provide the same options as the original run.
- `--mutate --covered-only`: Only generate mutations for classes that are covered by tests.
- `--mutate --bail`: Stop mutation testing execution upon first untested or uncovered mutation.
- `--mutate --class`: Generate mutations for the given class(es). E.g. --class=App\\Models.
- `--mutate --ignore`: Ignore the given class(es) when generating mutations. E.g. --ignore=App\\Http\\Requests.
- `--mutate --clear-cache`: Clear the mutation cache.
- `--mutate --no-cache`: Clear the mutation cache.
- `--mutate --ignore-min-score-on-zero-mutations`: Ignore the minimum score requirement when there are no mutations.
- `--mutate --covered-only`: Only generate mutations for classes that are covered by tests.
- `--mutate --everything`: Generate mutations for all classes, even if they are not covered by tests.
- `--mutate --profile`: Output to standard output the top ten slowest mutations.
- `--mutate --retry`: Run untested or uncovered mutations first and stop execution upon first error or failure.
- `--mutate --stop-on-uncovered`: Stop mutation testing execution upon first untested mutation.
- `--mutate --stop-on-untested`: Stop mutation testing execution upon first untested mutation.

## Profiling

- `--profile`: Output to standard output the top ten slowest tests

---

In this chapter, you found a complete list of CLI options provided by Pest. In the subsequent documentation, we will explore the topic of test dependencies: [Test Dependencies](/docs/test-dependencies)

---

## Validation Form Request Validation
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

## Form Request Validation

---

## Upgrade Upgrading To 12.0 From 11.x Eloquent Models and UUIDv7
Upgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md

#### Models and UUIDv7

**Likelihood Of Impact: Medium**

The `HasUuids` trait now returns UUIDs that are compatible with version 7 of the UUID spec (ordered UUIDs). If you would like to continue using ordered UUIDv4 strings for your model's IDs, you should now use the `HasVersion4Uuids` trait:

```php
use Illuminate\Database\Eloquent\Concerns\HasUuids; // [tl! remove]
use Illuminate\Database\Eloquent\Concerns\HasVersion4Uuids as HasUuids; // [tl! add]
```

The `HasVersion7Uuids` trait has been removed. If you were previously using this trait, you should use the `HasUuids` trait instead, which now provides the same behavior.

---

## Controllers Resource Controllers Partial Resource Routes API Resource Routes
Controllers | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/controllers.md

#### API Resource Routes

When declaring resource routes that will be consumed by APIs, you will commonly want to exclude routes that present HTML templates such as `create` and `edit`. For convenience, you may use the `apiResource` method to automatically exclude these two routes:

```php
use App\Http\Controllers\PhotoController;

Route::apiResource('photos', PhotoController::class);
```

You may register many API resource controllers at once by passing an array to the `apiResources` method:

```php
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\PostController;

Route::apiResources([
    'photos' => PhotoController::class,
    'posts' => PostController::class,
]);
```

To quickly generate an API resource controller that does not include the `create` or `edit` methods, use the `--api` switch when executing the `make:controller` command:

```shell
php artisan make:controller PhotoController --api
```

---

## Eloquent Resources Writing Resources Conditional Relationships
Eloquent Resources | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent-resources.md

### Conditional Relationships

In addition to conditionally loading attributes, you may conditionally include relationships on your resource responses based on if the relationship has already been loaded on the model. This allows your controller to decide which relationships should be loaded on the model and your resource can easily include them only when they have actually been loaded. Ultimately, this makes it easier to avoid "N+1" query problems within your resources.

The `whenLoaded` method may be used to conditionally load a relationship. In order to avoid unnecessarily loading relationships, this method accepts the name of the relationship instead of the relationship itself:

```php
use App\Http\Resources\PostResource;

/**
 * Transform the resource into an array.
 *
 * @return array<string, mixed>
 */
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        'posts' => PostResource::collection($this->whenLoaded('posts')),
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}
```

In this example, if the relationship has not been loaded, the `posts` key will be removed from the resource response before it is sent to the client.

---

## Validation Available Validation Rules date_format:_format_,...
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### date_format:_format_,...

The 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.

For convenience, date-based rules may be constructed using the fluent `date` rule builder:

```php
use Illuminate\Validation\Rule;

'start_date' => [
    'required',
    Rule::date()->format('Y-m-d'),
],
```

---

## Sanctum Mobile Application Authentication Issuing API Tokens
Sanctum | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/sanctum.md

### Issuing API Tokens

To get started, create a route that accepts the user's email / username, password, and device name, then exchanges those credentials for a new Sanctum token. The "device name" given to this endpoint is for informational purposes and may be any value you wish. In general, the device name value should be a name the user would recognize, such as "Nuno's iPhone 17".

Typically, you will make a request to the token endpoint from your mobile application's "login" screen. The endpoint will return the plain-text API token which may then be stored on the mobile device and used to make additional API requests:

```php
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;

Route::post('/sanctum/token', function (Request $request) {
    $request->validate([
        'email' => 'required|email',
        'password' => 'required',
        'device_name' => 'required',
    ]);

    $user = User::where('email', $request->email)->first();

    if (! $user || ! Hash::check($request->password, $user->password)) {
        throw ValidationException::withMessages([
            'email' => ['The provided credentials are incorrect.'],
        ]);
    }

    return $user->createToken($request->device_name)->plainTextToken;
});
```

When the mobile application uses the token to make an API request to your application, it should pass the token in the `Authorization` header as a `Bearer` token.

> [!NOTE]
> When issuing tokens for a mobile application, you are also free to specify [token abilities](#token-abilities).

---

## Scout Searching Where Clauses Customizing the Eloquent Results Query
Scout | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/scout.md

#### Customizing the Eloquent Results Query

After Scout retrieves a list of matching Eloquent models from your application's search engine, Eloquent is used to retrieve all of the matching models by their primary keys. You may customize this query by invoking the `query` method. The `query` method accepts a closure that will receive the Eloquent query builder instance as an argument:

```php
use App\Models\Order;
use Illuminate\Database\Eloquent\Builder;

$orders = Order::search('Star Trek')
    ->query(fn (Builder $query) => $query->with('invoices'))
    ->get();
```

When using a third-party engine, this callback is invoked after the relevant models have already been retrieved from the search engine, so it should not be used for "filtering" results — use [Scout where clauses](#where-clauses) instead. However, when using the database engine, the `query` method's constraints are applied directly to the database query, so you may use it for filtering as well.

---

## Validation Form Request Validation Preparing Input for Validation
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

### Preparing Input for Validation

If you need to prepare or sanitize any data from the request before you apply your validation rules, you may use the `prepareForValidation` method:

```php
use Illuminate\Support\Str;

/**
 * Prepare the data for validation.
 */
protected function prepareForValidation(): void
{
    $this->merge([
        'slug' => Str::slug($this->slug),
    ]);
}
```

Likewise, if you need to normalize any request data after validation is complete, you may use the `passedValidation` method:

```php
/**
 * Handle a passed validation attempt.
 */
protected function passedValidation(): void
{
    $this->replace(['name' => 'Taylor']);
}
```

---

## Pennant Adding Custom Pennant Drivers Defining Features Externally
Pennant | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/pennant.md

### Defining Features Externally

If your driver is a wrapper around a third-party feature flag platform, you will likely define features on the platform rather than using Pennant's `Feature::define` method. If that is the case, your custom driver should also implement the `Laravel\Pennant\Contracts\DefinesFeaturesExternally` interface:

```php
<?php

namespace App\Extensions;

use Laravel\Pennant\Contracts\Driver;
use Laravel\Pennant\Contracts\DefinesFeaturesExternally;

class FeatureFlagServiceDriver implements Driver, DefinesFeaturesExternally
{
    /**
     * Get the features defined for the given scope.
     */
    public function definedFeaturesForScope(mixed $scope): array {}

    /* ... */
}
```

The `definedFeaturesForScope` method should return a list of feature names defined for the provided scope.

---

## Validation Manually Creating Validators Performing Additional Validation
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

### Performing Additional Validation

Sometimes 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:

```php
use Illuminate\Support\Facades\Validator;

$validator = Validator::make(/* ... */);

$validator->after(function ($validator) {
    if ($this->somethingElseIsInvalid()) {
        $validator->errors()->add(
            'field', 'Something is wrong with this field!'
        );
    }
});

if ($validator->fails()) {
    // ...
}
```

As 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:

```php
use App\Validation\ValidateShippingTime;
use App\Validation\ValidateUserStatus;

$validator->after([
    new ValidateUserStatus,
    new ValidateShippingTime,
    function ($validator) {
        // ...
    },
]);
```

---

## Sanctum API Token Authentication Token Expiration
Sanctum | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/sanctum.md

### Token Expiration

By default, Sanctum tokens never expire and may only be invalidated by [revoking the token](#revoking-tokens). However, if you would like to configure an expiration time for your application's API tokens, you may do so via the `expiration` configuration option defined in your application's `sanctum` configuration file. This configuration option defines the number of minutes until an issued token will be considered expired:

```php
'expiration' => 525600,
```

If you would like to specify the expiration time of each token independently, you may do so by providing the expiration time as the third argument to the `createToken` method:

```php
return $user->createToken(
    'token-name', ['*'], now()->plus(weeks: 1)
)->plainTextToken;
```

If you have configured a token expiration time for your application, you may also wish to [schedule a task](/docs/{{version}}/scheduling) to prune your application's expired tokens. Thankfully, Sanctum includes a `sanctum:prune-expired` Artisan command that you may use to accomplish this. For example, you may configure a scheduled task to delete all expired token database records that have been expired for at least 24 hours:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('sanctum:prune-expired --hours=24')->daily();
```

---

## Controllers Resource Controllers Singleton Resource Controllers API Singleton Resources
Controllers | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/controllers.md

#### API Singleton Resources

The `apiSingleton` method may be used to register a singleton resource that will be manipulated via an API, thus rendering the `create` and `edit` routes unnecessary:

```php
Route::apiSingleton('profile', ProfileController::class);
```

Of course, API singleton resources may also be `creatable`, which will register `store` and `destroy` routes for the resource:

```php
Route::apiSingleton('photos.thumbnail', ProfileController::class)->creatable();
```

_[Truncated]_


Output:
Wall time: 4.1541 seconds
Output:
[{"type":"text","text":"# Search Results\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## 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## Pest3 Now Available New Configuration API\nPest3 Now Available | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/pest3-now-available.md\n\n\n## New Configuration API\n\nPest 1 / Pest 2's configuration API was a little bit confusing, the `uses()` function that was originally made only for having the `$this` variable within closure bound to the test case instance, ended up being used for pretty much everything.\n\nIn Pest 3, we've introduced a new configuration API that is more intuitive and easier to use. The new configuration API is based on the `pest()` function, which allows you to configure Pest using a fluent and expressive API.\n\n> Note: the `uses()` function is still available in Pest 3, and we don't have plans to remove it. However, we recommend using the new configuration API for new projects.\n\n```diff\n-uses(TestCase::class)->in(__DIR__);\n+pest()->extends(TestCase::class);\n\n-uses(TestCase::class, RefreshDatabase::class)->in('Features');\n+pest()->extends(TestCase::class)->use(RefreshDatabase::class)->in('Features');\n\n-uses()->compact();\n+pest()->printer()->compact();\n```\n\nAnd of course, any method that was available on the `uses()` API, like `->beforeEach()` or `->group()` is still available on the new `pest()` configuration API; we've just made it more intuitive and easier to use.\n\n---\n\n## Urls URLs for Named Routes Eloquent Models\nUrls | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/urls.md\n\n\n#### Eloquent Models\n\nYou will often be generating URLs using the route key (typically the primary key) of [Eloquent models](/docs/{{version}}/eloquent). For this reason, you may pass Eloquent models as parameter values. The `route` helper will automatically extract the model's route key:\n\n```php\necho route('post.show', ['post' => $post]);\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## Http Tests Testing JSON APIs Fluent JSON Testing\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n### Fluent JSON Testing\n\nLaravel also offers a beautiful way to fluently test your application's JSON responses. To get started, pass a closure to the `assertJson` method. This closure will be invoked with an instance of `Illuminate\\Testing\\Fluent\\AssertableJson` which can be used to make assertions against the JSON that was returned by your application. The `where` method may be used to make assertions against a particular attribute of the JSON, while the `missing` method may be used to assert that a particular attribute is missing from the JSON:\n\n```php tab=Pest\nuse Illuminate\\Testing\\Fluent\\AssertableJson;\n\ntest('fluent json', function () {\n    $response = $this->getJson('/users/1');\n\n    $response\n        ->assertJson(fn (AssertableJson $json) =>\n            $json->where('id', 1)\n                ->where('name', 'Victoria Faith')\n                ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com'))\n                ->whereNot('status', 'pending')\n                ->missing('password')\n                ->etc()\n        );\n});\n```\n\n```php tab=PHPUnit\nuse Illuminate\\Testing\\Fluent\\AssertableJson;\n\n/**\n * A basic functional test example.\n */\npublic function test_fluent_json(): void\n{\n    $response = $this->getJson('/users/1');\n\n    $response\n        ->assertJson(fn (AssertableJson $json) =>\n            $json->where('id', 1)\n                ->where('name', 'Victoria Faith')\n                ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com'))\n                ->whereNot('status', 'pending')\n                ->missing('password')\n                ->etc()\n        );\n}\n```\n\n#### Understanding the `etc` Method\n\nIn the example above, you may have noticed we invoked the `etc` method at the end of our assertion chain. This method informs Laravel that there may be other attributes present on the JSON object. If the `etc` method is not used, the test will fail if other attributes that you did not make assertions against exist on the JSON object.\n\nThe intention behind this behavior is to protect you from unintentionally exposing sensitive information in your JSON responses by forcing you to either explicitly make an assertion against the attribute or explicitly allow additional attributes via the `etc` method.\n\nHowever, you should be aware that not including the `etc` method in your assertion chain does not ensure that additional attributes are not being added to arrays that are nested within your JSON object. The `etc` method only ensures that no additional attributes exist at the nesting level in which the `etc` method is invoked.\n\n---\n\n## Cli Api Reference\nCli Api Reference | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/cli-api-reference.md\n\n---\ntitle: CLI API Reference\ndescription: In the preceding chapters of the Pest documentation, we have covered numerous CLI options that are available in Pest. Nevertheless, Pest provides many other options that could prove beneficial. The complete CLI API Reference is provided below for your convenience.\n---\n\n# CLI API Reference\n\nIn the preceding chapters of the Pest documentation, we have covered numerous CLI options that are available in Pest. Nevertheless, Pest provides many other options that could prove beneficial. The complete CLI API Reference is provided below for your convenience.\n\n## Configuration\n\n- `--init`: Initialize a standard Pest configuration.\n- `--bootstrap <file>` A PHP script that is included before the tests run.\n- `-c|--configuration <file>`: Read configuration from XML file.\n- `--no-configuration`: Ignore default configuration file (phpunit.xml).\n- `--extension <class>`: Register test runner extension with bootstrap <class>.\n- `--no-extensions`: Do not load PHPUnit extensions.\n- `--include-path <path(s)>`: Prepend PHP's include_path with given path(s).\n- `-d <key[=value]>`: Set a php.ini value.\n- `--cache-directory <dir>`: Specify cache directory.\n- `--generate-configuration`: Generate configuration file with suggested settings.\n- `--migrate-configuration`: Migrate configuration file to current format.\n- `--generate-baseline <file>`: Generate baseline for issues.\n- `--use-baseline <file>`: Use baseline to ignore issues.\n- `--ignore-baseline`: Do not use baseline to ignore issues.\n- `--test-directory`: Specify test directory containing Pest.php, TestCase.php, helpers and your tests. Default: tests\n\n## Selection\n\n- `--bail`: Stop execution upon first not-passed test.\n- `--ci`: Ignore focused tests using `->only()` and run the entire test suite.\n- `--todos`: Output to standard output the list of todos.\n- `--notes`: Output to standard output tests with notes.\n- `--issue`: Output to standard output tests with the given issue number.\n- `--pr`: Output to standard output tests with the given pull request number.\n- `--pull-request`: Output to standard output tests with the given pull request number (alias for `--pr`).\n- `--flaky`: Output to standard output tests marked as flaky.\n- `--retry`: Run non-passing tests first and stop execution upon first error or failure.\n- `--list-suites` List available test suites.\n- `--testsuite <name>`: Only run tests from the specified test suite(s).\n- `--exclude-testsuite <name>`: Exclude tests from the specified test suite(s).\n- `--list-groups`: List available test groups.\n- `--group <name>`: Only run tests from the specified group(s).\n- `--exclude-group <name>`: Exclude tests from the specified group(s).\n- `--covers <name>`: Only run tests that intend to cover `<name>`.\n- `--uses <name>`: Only run tests that intend to use `<name>`.\n- `--requires-php-extension <name>`: Only run tests that require PHP extension <name>.\n- `--list-test-files`: List available test files.\n- `--list-tests`: List available tests.\n- `--list-tests-xml <file>`: List available tests in XML format.\n- `--filter <pattern>`: Filter which tests to run\n- `--exclude-filter <pattern>`: Exclude tests for the specified filter pattern.\n- `--test-suffix <suffixes>`: Only search for test in files with specified suffix(es). Default: Test.php,.phpt\n\n## Execution\n\n- `--parallel` Run tests in parallel.\n- `--shard <index/total>`: Run only the given shard of tests (e.g. `--shard=1/5`). Uses time-balanced distribution when `tests/.pest/shards.json` exists.\n- `--update-shards`: Update `tests/.pest/shards.json` with test timing data for time-balanced sharding. Can be combined with `--parallel`.\n- `--update-snapshots`: Update snapshots for tests using the \"toMatchSnapshot\" expectation.\n- `--globals-backup`: Backup and restore $GLOBALS for each test.\n- `--static-backup`: Backup and restore static properties for each test.\n- `--strict-coverage`: Be strict about code coverage metadata.\n- `--strict-global-state`: Be strict about changes to global state.\n- `--disallow-test-output`: Be strict about output during tests.\n- `--enforce-time-limit`: Enforce time limit based on test size.\n- `--default-time-limit <sec>`: Timeout in seconds for tests that have no declared size.\n- `--dont-report-useless-tests`: Do not report tests that do not test anything.\n- `--stop-on-defect`: Stop execution upon first not-passed test.\n- `--stop-on-error`: Stop execution upon first error.\n- `--stop-on-failure`: Stop execution upon first error or failure.\n- `--stop-on-warning`: Stop execution upon first warning.\n- `--stop-on-risky`: Stop execution upon first risky test.\n- `--stop-on-deprecation`: Stop after first test that triggered a deprecation.\n- `--stop-on-notice`: Stop after first test that triggered a notice.\n- `--stop-on-skipped`: Stop execution upon first skipped test.\n- `--stop-on-incomplete`: Stop execution upon first incomplete test.\n- `--fail-on-empty-test-suite`: Signal failure using shell exit code when no tests were run.\n- `--fail-on-warning`: Treat tests with warnings as failures.\n- `--fail-on-risky`: Treat risky tests as failures.\n- `--fail-on-deprecation`: Signal failure using shell exit code when a deprecation was triggered.\n- `--fail-on-phpunit-deprecation`: Signal failure using shell exit code when a PHPUnit deprecation was triggered.\n- `--fail-on-notice`: Signal failure using shell exit code when a notice was triggered.\n- `--fail-on-skipped`: Treat skipped tests as failures.\n- `--fail-on-incomplete`: Signal failure using shell exit code when a test was marked incomplete.\n- `--cache-result`: Write test results to cache file.\n- `--do-not-cache-result`: Do not write test results to cache file\n- `--order-by <order>`: Run tests in order: default|defects|depends|duration|no-depends|random|reverse|size.\n- `--random-order-seed <N>`: Use the specified random seed when running tests in random order\n\n## Reporting\n\n- `--colors <flag>`: Use colors in output (\"never\", \"auto\" or \"always\").\n- `--columns <n>`: Number of columns to use for progress output.\n- `--columns max`: Use maximum number of columns for progress output.\n- `--stderr`: Write to STDERR instead of STDOUT.\n- `--no-progress`: Disable output of test execution progress.\n- `--no-results`: Disable output of test results.\n- `--no-output`: Disable all output.\n- `--display-incomplete`: Display details for incomplete tests.\n- `--display-skipped`: Display details for skipped tests.\n- `--display-deprecations`: Display details for deprecations triggered by tests.\n- `--display-phpunit-deprecations`: Display details for PHPUnit deprecations.\n- `--display-errors`: Display details for errors triggered by tests.\n- `--display-notices`: Display details for notices triggered by tests.\n- `--display-warnings`: Display details for warnings triggered by tests.\n- `--reverse-list`: Print defects in reverse order.\n- `--teamcity`: Replace default progress and result output with TeamCity format.\n- `--testdox`: Replace default result output with TestDox format\n- `--testdox-summary`: Repeat TestDox output for tests with errors, failures, or issues.\n- `--debug`: Replace default progress and result output with debugging information.\n- `--compact`:  Replace default result output with Compact format\n\n## Logging\n\n- `--log-junit <file>`: Write test results in JUnit XML format to file.\n- `--log-teamcity <file>`: Write test results in TeamCity format to file.\n- `--testdox-html <file>`: Write test results in TestDox format (HTML) to file.\n- `--testdox-text <file>`: Write test results in TestDox format (plain text) to file.\n- `--log-events-text <file>`: Stream events as plain text to file.\n- `--log-events-verbose-text <file>`: Stream events as plain text (with telemetry information) to file.\n- `--no-logging`: Ignore logging configured in the XML configuration file\n\n## Code Coverage\n\n- `--coverage`: Generate code coverage report and output to standard output.\n- `--coverage --min=<value>`: Set the minimum required coverage percentage, and fail if not met.\n- `--coverage --exactly=<value>`: Set the exact required coverage percentage, and fail if not met.\n- `--coverage --only-covered`: Hide files with 0% coverage from the code coverage report.\n- `--coverage-clover <file>`: Write code coverage report in Clover XML format to file.\n- `--coverage-cobertura <file>`: Write code coverage report in Cobertura XML format to file.\n- `--coverage-crap4j <file>`: Write code coverage report in Crap4J XML format to file.\n- `--coverage-html <dir>`: Write code coverage report in HTML format to directory.\n- `--coverage-php <file>`: Write serialized code coverage data to file.\n- `--coverage-text=<file>`: Write code coverage report in text format to file [default: standard output].\n- `--only-summary-for-coverage-text`: Option for code coverage report in text format: only show summary.\n- `--show-uncovered-for-coverage-text`: Option for code coverage report in text format: show uncovered files.\n- `--coverage-xml <dir>`: Write code coverage report in XML format to directory.\n- `--warm-coverage-cache`: Warm static analysis cache.\n- `--coverage-filter <dir>`: Include `<dir>`: in code coverage reporting.\n- `--path-coverage`: Report path coverage in addition to line coverage.\n- `--disable-coverage-ignore`: Disable metadata for ignoring code coverage.\n- `--no-coverage`: Ignore code coverage reporting configured in the XML configuration file\n\n## Mutation Testing\n\n- `--mutate`: Runs mutation testing, to understand the quality of your tests.\n- `--mutate --parallel`: Runs mutation testing in parallel.\n- `--mutate --min`: Set the minimum required mutation score, and fail if not met.\n- `--mutate --id`:  Run only the mutation with the given ID. But E.g. --id=ecb35ab30ffd3491. Note, you need to provide the same options as the original run.\n- `--mutate --covered-only`: Only generate mutations for classes that are covered by tests.\n- `--mutate --bail`: Stop mutation testing execution upon first untested or uncovered mutation.\n- `--mutate --class`: Generate mutations for the given class(es). E.g. --class=App\\\\Models.\n- `--mutate --ignore`: Ignore the given class(es) when generating mutations. E.g. --ignore=App\\\\Http\\\\Requests.\n- `--mutate --clear-cache`: Clear the mutation cache.\n- `--mutate --no-cache`: Clear the mutation cache.\n- `--mutate --ignore-min-score-on-zero-mutations`: Ignore the minimum score requirement when there are no mutations.\n- `--mutate --covered-only`: Only generate mutations for classes that are covered by tests.\n- `--mutate --everything`: Generate mutations for all classes, even if they are not covered by tests.\n- `--mutate --profile`: Output to standard output the top ten slowest mutations.\n- `--mutate --retry`: Run untested or uncovered mutations first and stop execution upon first error or failure.\n- `--mutate --stop-on-uncovered`: Stop mutation testing execution upon first untested mutation.\n- `--mutate --stop-on-untested`: Stop mutation testing execution upon first untested mutation.\n\n## Profiling\n\n- `--profile`: Output to standard output the top ten slowest tests\n\n---\n\nIn this chapter, you found a complete list of CLI options provided by Pest. In the subsequent documentation, we will explore the topic of test dependencies: [Test Dependencies](/docs/test-dependencies)\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## Upgrade Upgrading To 12.0 From 11.x Eloquent Models and UUIDv7\nUpgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md\n\n\n#### Models and UUIDv7\n\n**Likelihood Of Impact: Medium**\n\nThe `HasUuids` trait now returns UUIDs that are compatible with version 7 of the UUID spec (ordered UUIDs). If you would like to continue using ordered UUIDv4 strings for your model's IDs, you should now use the `HasVersion4Uuids` trait:\n\n```php\nuse Illuminate\\Database\\Eloquent\\Concerns\\HasUuids; // [tl! remove]\nuse Illuminate\\Database\\Eloquent\\Concerns\\HasVersion4Uuids as HasUuids; // [tl! add]\n```\n\nThe `HasVersion7Uuids` trait has been removed. If you were previously using this trait, you should use the `HasUuids` trait instead, which now provides the same behavior.\n\n---\n\n## Controllers Resource Controllers Partial Resource Routes API Resource Routes\nControllers | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/controllers.md\n\n\n#### API Resource Routes\n\nWhen declaring resource routes that will be consumed by APIs, you will commonly want to exclude routes that present HTML templates such as `create` and `edit`. For convenience, you may use the `apiResource` method to automatically exclude these two routes:\n\n```php\nuse App\\Http\\Controllers\\PhotoController;\n\nRoute::apiResource('photos', PhotoController::class);\n```\n\nYou may register many API resource controllers at once by passing an array to the `apiResources` method:\n\n```php\nuse App\\Http\\Controllers\\PhotoController;\nuse App\\Http\\Controllers\\PostController;\n\nRoute::apiResources([\n    'photos' => PhotoController::class,\n    'posts' => PostController::class,\n]);\n```\n\nTo quickly generate an API resource controller that does not include the `create` or `edit` methods, use the `--api` switch when executing the `make:controller` command:\n\n```shell\nphp artisan make:controller PhotoController --api\n```\n\n---\n\n## Eloquent Resources Writing Resources Conditional Relationships\nEloquent Resources | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent-resources.md\n\n\n### Conditional Relationships\n\nIn addition to conditionally loading attributes, you may conditionally include relationships on your resource responses based on if the relationship has already been loaded on the model. This allows your controller to decide which relationships should be loaded on the model and your resource can easily include them only when they have actually been loaded. Ultimately, this makes it easier to avoid \"N+1\" query problems within your resources.\n\nThe `whenLoaded` method may be used to conditionally load a relationship. In order to avoid unnecessarily loading relationships, this method accepts the name of the relationship instead of the relationship itself:\n\n```php\nuse App\\Http\\Resources\\PostResource;\n\n/**\n * Transform the resource into an array.\n *\n * @return array<string, mixed>\n */\npublic function toArray(Request $request): array\n{\n    return [\n        'id' => $this->id,\n        'name' => $this->name,\n        'email' => $this->email,\n        'posts' => PostResource::collection($this->whenLoaded('posts')),\n        'created_at' => $this->created_at,\n        'updated_at' => $this->updated_at,\n    ];\n}\n```\n\nIn this example, if the relationship has not been loaded, the `posts` key will be removed from the resource response before it is sent to the client.\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## Sanctum Mobile Application Authentication Issuing API Tokens\nSanctum | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/sanctum.md\n\n\n### Issuing API Tokens\n\nTo get started, create a route that accepts the user's email / username, password, and device name, then exchanges those credentials for a new Sanctum token. The \"device name\" given to this endpoint is for informational purposes and may be any value you wish. In general, the device name value should be a name the user would recognize, such as \"Nuno's iPhone 17\".\n\nTypically, you will make a request to the token endpoint from your mobile application's \"login\" screen. The endpoint will return the plain-text API token which may then be stored on the mobile device and used to make additional API requests:\n\n```php\nuse App\\Models\\User;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Hash;\nuse Illuminate\\Validation\\ValidationException;\n\nRoute::post('/sanctum/token', function (Request $request) {\n    $request->validate([\n        'email' => 'required|email',\n        'password' => 'required',\n        'device_name' => 'required',\n    ]);\n\n    $user = User::where('email', $request->email)->first();\n\n    if (! $user || ! Hash::check($request->password, $user->password)) {\n        throw ValidationException::withMessages([\n            'email' => ['The provided credentials are incorrect.'],\n        ]);\n    }\n\n    return $user->createToken($request->device_name)->plainTextToken;\n});\n```\n\nWhen the mobile application uses the token to make an API request to your application, it should pass the token in the `Authorization` header as a `Bearer` token.\n\n> [!NOTE]\n> When issuing tokens for a mobile application, you are also free to specify [token abilities](#token-abilities).\n\n---\n\n## Scout Searching Where Clauses Customizing the Eloquent Results Query\nScout | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/scout.md\n\n\n#### Customizing the Eloquent Results Query\n\nAfter Scout retrieves a list of matching Eloquent models from your application's search engine, Eloquent is used to retrieve all of the matching models by their primary keys. You may customize this query by invoking the `query` method. The `query` method accepts a closure that will receive the Eloquent query builder instance as an argument:\n\n```php\nuse App\\Models\\Order;\nuse Illuminate\\Database\\Eloquent\\Builder;\n\n$orders = Order::search('Star Trek')\n    ->query(fn (Builder $query) => $query->with('invoices'))\n    ->get();\n```\n\nWhen using a third-party engine, this callback is invoked after the relevant models have already been retrieved from the search engine, so it should not be used for \"filtering\" results — use [Scout where clauses](#where-clauses) instead. However, when using the database engine, the `query` method's constraints are applied directly to the database query, so you may use it for filtering as well.\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## Pennant Adding Custom Pennant Drivers Defining Features Externally\nPennant | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/pennant.md\n\n\n### Defining Features Externally\n\nIf your driver is a wrapper around a third-party feature flag platform, you will likely define features on the platform rather than using Pennant's `Feature::define` method. If that is the case, your custom driver should also implement the `Laravel\\Pennant\\Contracts\\DefinesFeaturesExternally` interface:\n\n```php\n<?php\n\nnamespace App\\Extensions;\n\nuse Laravel\\Pennant\\Contracts\\Driver;\nuse Laravel\\Pennant\\Contracts\\DefinesFeaturesExternally;\n\nclass FeatureFlagServiceDriver implements Driver, DefinesFeaturesExternally\n{\n    /**\n     * Get the features defined for the given scope.\n     */\n    public function definedFeaturesForScope(mixed $scope): array {}\n\n    /* ... */\n}\n```\n\nThe `definedFeaturesForScope` method should return a list of feature names defined for the provided scope.\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## Sanctum API Token Authentication Token Expiration\nSanctum | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/sanctum.md\n\n\n### Token Expiration\n\nBy default, Sanctum tokens never expire and may only be invalidated by [revoking the token](#revoking-tokens). However, if you would like to configure an expiration time for your application's API tokens, you may do so via the `expiration` configuration option defined in your application's `sanctum` configuration file. This configuration option defines the number of minutes until an issued token will be considered expired:\n\n```php\n'expiration' => 525600,\n```\n\nIf you would like to specify the expiration time of each token independently, you may do so by providing the expiration time as the third argument to the `createToken` method:\n\n```php\nreturn $user->createToken(\n    'token-name', ['*'], now()->plus(weeks: 1)\n)->plainTextToken;\n```\n\nIf you have configured a token expiration time for your application, you may also wish to [schedule a task](/docs/{{version}}/scheduling) to prune your application's expired tokens. Thankfully, Sanctum includes a `sanctum:prune-expired` Artisan command that you may use to accomplish this. For example, you may configure a scheduled task to delete all expired token database records that have been expired for at least 24 hours:\n\n```php\nuse Illuminate\\Support\\Facades\\Schedule;\n\nSchedule::command('sanctum:prune-expired --hours=24')->daily();\n```\n\n---\n\n## Controllers Resource Controllers Singleton Resource Controllers API Singleton Resources\nControllers | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/controllers.md\n\n\n#### API Singleton Resources\n\nThe `apiSingleton` method may be used to register a singleton resource that will be manipulated via an API, thus rendering the `create` and `edit` routes unnecessary:\n\n```php\nRoute::apiSingleton('profile', ProfileController::class);\n```\n\nOf course, API singleton resources may also be `creatable`, which will register `store` and `destroy` routes for the resource:\n\n```php\nRoute::apiSingleton('photos.thumbnail', ProfileController::class)->creatable();\n```\n\n_[Truncated]_"}]


sed -n '50,90p' app/Providers/AppServiceProvider.php


sed -n '50,90p' app/Providers/AppServiceProvider.php

    {
        Date::use(CarbonImmutable::class);

        DB::prohibitDestructiveCommands(
            app()->isProduction(),
        );

        Password::defaults(fn (): ?Password => app()->isProduction()
            ? Password::min(12)
                ->mixedCase()
                ->letters()
                ->numbers()
                ->symbols()
                ->uncompromised()
            : null
        );

        RateLimiter::for('api.storefront', function (Request $request): Limit {
            return Limit::perMinute(120)->by($request->ip());
        });

        RateLimiter::for('checkout', function (Request $request): Limit {
            return Limit::perMinute(10)->by($request->session()->getId() ?: $request->ip());
        });

        Authenticate::redirectUsing(function (Request $request): string {
            if ($request->is('admin*')) {
                return route('admin.login');
            }

            if ($request->is('account*')) {
                return route('account.login');
            }

            return route('login');
        });
    }

    protected function configureStorefrontViewData(): void
    {
        View::composer('layouts.storefront', function (ViewInstance $view): void {


Output:
Chunk ID: b82fb3
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 310
Output:
    {
        Date::use(CarbonImmutable::class);

        DB::prohibitDestructiveCommands(
            app()->isProduction(),
        );

        Password::defaults(fn (): ?Password => app()->isProduction()
            ? Password::min(12)
                ->mixedCase()
                ->letters()
                ->numbers()
                ->symbols()
                ->uncompromised()
            : null
        );

        RateLimiter::for('api.storefront', function (Request $request): Limit {
            return Limit::perMinute(120)->by($request->ip());
        });

        RateLimiter::for('checkout', function (Request $request): Limit {
            return Limit::perMinute(10)->by($request->session()->getId() ?: $request->ip());
        });

        Authenticate::redirectUsing(function (Request $request): string {
            if ($request->is('admin*')) {
                return route('admin.login');
            }

            if ($request->is('account*')) {
                return route('account.login');
            }

            return route('login');
        });
    }

    protected function configureStorefrontViewData(): void
    {
        View::composer('layouts.storefront', function (ViewInstance $view): void {


For the API slice I’ll use `routes/api.php` with Laravel’s API route group, `ResolveStore` for host tenancy, Eloquent API Resources for response shape, and form requests for validation. I’m scaffolding those files with Artisan now.


php artisan make:controller Api/Storefront/V1/CartController --api --no-interaction


php artisan make:controller Api/Storefront/V1/CartLineController --api --no-interaction


php artisan make:controller Api/Storefront/V1/CheckoutController --api --no-interaction


php artisan make:resource Storefront/V1/CartResource --no-interaction


Output:
Chunk ID: cb3fff
Wall time: 0.1818 seconds
Process exited with code 0
Original token count: 27
Output:

   INFO  Controller [app/Http/Controllers/Api/Storefront/V1/CartController.php] created successfully.


Output:
Chunk ID: e7b3aa
Wall time: 0.1832 seconds
Process exited with code 0
Original token count: 28
Output:

   INFO  Controller [app/Http/Controllers/Api/Storefront/V1/CartLineController.php] created successfully.


Output:
Chunk ID: 10c63c
Wall time: 0.1767 seconds
Process exited with code 0
Original token count: 28
Output:

   INFO  Controller [app/Http/Controllers/Api/Storefront/V1/CheckoutController.php] created successfully.


Output:
Chunk ID: 000235
Wall time: 0.1754 seconds
Process exited with code 0
Original token count: 24
Output:

   INFO  Resource [app/Http/Resources/Storefront/V1/CartResource.php] created successfully.


php artisan make:controller Api/Storefront/V1/CartController --api --no-interaction

INFO  Controller [app/Http/Controllers/Api/Storefront/V1/CartController.php] created successfully.


php artisan make:controller Api/Storefront/V1/CartLineController --api --no-interaction

INFO  Controller [app/Http/Controllers/Api/Storefront/V1/CartLineController.php] created successfully.


php artisan make:resource Storefront/V1/CartResource --no-interaction

INFO  Resource [app/Http/Resources/Storefront/V1/CartResource.php] created successfully.


php artisan make:controller Api/Storefront/V1/CheckoutController --api --no-interaction

INFO  Controller [app/Http/Controllers/Api/Storefront/V1/CheckoutController.php] created successfully.


php artisan make:resource Storefront/V1/CartLineResource --no-interaction


php artisan make:resource Storefront/V1/CheckoutResource --no-interaction


php artisan make:resource Storefront/V1/ShippingRateResource --no-interaction


php artisan make:request Api/Storefront/V1/StoreCartLineRequest --no-interaction


php artisan make:resource Storefront/V1/CheckoutResource --no-interaction

INFO  Resource [app/Http/Resources/Storefront/V1/CheckoutResource.php] created successfully.


php artisan make:request Api/Storefront/V1/StoreCartLineRequest --no-interaction

INFO  Request [app/Http/Requests/Api/Storefront/V1/StoreCartLineRequest.php] created successfully.


php artisan make:resource Storefront/V1/ShippingRateResource --no-interaction

INFO  Resource [app/Http/Resources/Storefront/V1/ShippingRateResource.php] created successfully.


php artisan make:resource Storefront/V1/CartLineResource --no-interaction

INFO  Resource [app/Http/Resources/Storefront/V1/CartLineResource.php] created successfully.


Output:
Chunk ID: 1d7c15
Wall time: 0.0406 seconds
Process exited with code 0
Original token count: 25
Output:

   INFO  Resource [app/Http/Resources/Storefront/V1/CartLineResource.php] created successfully.


Output:
Chunk ID: 621040
Wall time: 0.0418 seconds
Process exited with code 0
Original token count: 25
Output:

   INFO  Resource [app/Http/Resources/Storefront/V1/CheckoutResource.php] created successfully.


Output:
Chunk ID: b70e49
Wall time: 0.0417 seconds
Process exited with code 0
Original token count: 26
Output:

   INFO  Resource [app/Http/Resources/Storefront/V1/ShippingRateResource.php] created successfully.


Output:
Chunk ID: 08fa6c
Wall time: 0.0406 seconds
Process exited with code 0
Original token count: 27
Output:

   INFO  Request [app/Http/Requests/Api/Storefront/V1/StoreCartLineRequest.php] created successfully.


php artisan make:request Api/Storefront/V1/UpdateCartLineRequest --no-interaction


php artisan make:request Api/Storefront/V1/StoreCheckoutRequest --no-interaction


php artisan make:request Api/Storefront/V1/SetCheckoutAddressRequest --no-interaction


php artisan make:request Api/Storefront/V1/SetCheckoutShippingRequest --no-interaction


php artisan make:request Api/Storefront/V1/StoreCheckoutRequest --no-interaction

INFO  Request [app/Http/Requests/Api/Storefront/V1/StoreCheckoutRequest.php] created successfully.


php artisan make:request Api/Storefront/V1/SetCheckoutShippingRequest --no-interaction

INFO  Request [app/Http/Requests/Api/Storefront/V1/SetCheckoutShippingRequest.php] created successfully.


php artisan make:request Api/Storefront/V1/SetCheckoutAddressRequest --no-interaction

INFO  Request [app/Http/Requests/Api/Storefront/V1/SetCheckoutAddressRequest.php] created successfully.


php artisan make:request Api/Storefront/V1/UpdateCartLineRequest --no-interaction

INFO  Request [app/Http/Requests/Api/Storefront/V1/UpdateCartLineRequest.php] created successfully.


Output:
Chunk ID: a9c34f
Wall time: 0.0548 seconds
Process exited with code 0
Original token count: 27
Output:

   INFO  Request [app/Http/Requests/Api/Storefront/V1/UpdateCartLineRequest.php] created successfully.


Output:
Chunk ID: 98938d
Wall time: 0.0537 seconds
Process exited with code 0
Original token count: 27
Output:

   INFO  Request [app/Http/Requests/Api/Storefront/V1/StoreCheckoutRequest.php] created successfully.


Output:
Chunk ID: 7403fa
Wall time: 0.0528 seconds
Process exited with code 0
Original token count: 28
Output:

   INFO  Request [app/Http/Requests/Api/Storefront/V1/SetCheckoutAddressRequest.php] created successfully.


Output:
Chunk ID: b4c84b
Wall time: 0.0539 seconds
Process exited with code 0
Original token count: 28
Output:

   INFO  Request [app/Http/Requests/Api/Storefront/V1/SetCheckoutShippingRequest.php] created successfully.


php artisan make:request Api/Storefront/V1/ApplyCheckoutDiscountRequest --no-interaction


php artisan make:request Api/Storefront/V1/SelectCheckoutPaymentRequest --no-interaction


php artisan make:test --pest Api/StorefrontCartApiTest --no-interaction


php artisan make:test --pest Api/StorefrontCheckoutApiTest --no-interaction


Output:
Chunk ID: 88f3b6
Wall time: 0.0529 seconds
Process exited with code 0
Original token count: 29
Output:

   INFO  Request [app/Http/Requests/Api/Storefront/V1/ApplyCheckoutDiscountRequest.php] created successfully.


Output:
Chunk ID: 9ececd
Wall time: 0.0492 seconds
Process exited with code 0
Original token count: 29
Output:

   INFO  Request [app/Http/Requests/Api/Storefront/V1/SelectCheckoutPaymentRequest.php] created successfully.


Output:
Chunk ID: ead110
Wall time: 0.0460 seconds
Process exited with code 0
Original token count: 22
Output:

   INFO  Test [tests/Feature/Api/StorefrontCartApiTest.php] created successfully.


Output:
Chunk ID: ea5c88
Wall time: 0.0430 seconds
Process exited with code 0
Original token count: 23
Output:

   INFO  Test [tests/Feature/Api/StorefrontCheckoutApiTest.php] created successfully.


php artisan make:request Api/Storefront/V1/SelectCheckoutPaymentRequest --no-interaction

INFO  Request [app/Http/Requests/Api/Storefront/V1/SelectCheckoutPaymentRequest.php] created successfully.


php artisan make:test --pest Api/StorefrontCheckoutApiTest --no-interaction

INFO  Test [tests/Feature/Api/StorefrontCheckoutApiTest.php] created successfully.


php artisan make:request Api/Storefront/V1/ApplyCheckoutDiscountRequest --no-interaction

INFO  Request [app/Http/Requests/Api/Storefront/V1/ApplyCheckoutDiscountRequest.php] created successfully.


php artisan make:test --pest Api/StorefrontCartApiTest --no-interaction

INFO  Test [tests/Feature/Api/StorefrontCartApiTest.php] created successfully.


Scaffolding is done. I’m wiring `routes/api.php` into Laravel’s bootstrap and adding the versioned storefront route group before filling in controllers/resources.


Input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/bootstrap/app.php
@@
     ->withRouting(
         web: __DIR__.'/../routes/web.php',
+        api: __DIR__.'/../routes/api.php',
         commands: __DIR__.'/../routes/console.php',
         health: '/up',
     )
*** End Patch


Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/bootstrap/app.php

@@ -11,2 +11,3 @@
         web: __DIR__.'/../routes/web.php',
+        api: __DIR__.'/../routes/api.php',
         commands: __DIR__.'/../routes/console.php',


Output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/bootstrap/app.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Add File: /Users/fabianwesner/Herd/shop/routes/api.php
+<?php
+
+use App\Http\Controllers\Api\Storefront\V1\CartController;
+use App\Http\Controllers\Api\Storefront\V1\CartLineController;
+use App\Http\Controllers\Api\Storefront\V1\CheckoutController;
+use Illuminate\Support\Facades\Route;
+
+Route::middleware(['store.resolve', 'throttle:api.storefront'])
+    ->prefix('storefront/v1')
+    ->name('api.storefront.v1.')
+    ->group(function (): void {
+        Route::post('carts', [CartController::class, 'store'])->name('carts.store');
+        Route::get('carts/{cart}', [CartController::class, 'show'])->name('carts.show');
+        Route::post('carts/{cart}/lines', [CartLineController::class, 'store'])->name('carts.lines.store');
+        Route::put('carts/{cart}/lines/{cartLine}', [CartLineController::class, 'update'])->name('carts.lines.update');
+        Route::delete('carts/{cart}/lines/{cartLine}', [CartLineController::class, 'destroy'])->name('carts.lines.destroy');
+
+        Route::post('checkouts', [CheckoutController::class, 'store'])->name('checkouts.store');
+        Route::get('checkouts/{checkout}', [CheckoutController::class, 'show'])->name('checkouts.show');
+        Route::put('checkouts/{checkout}/address', [CheckoutController::class, 'address'])->name('checkouts.address');
+        Route::put('checkouts/{checkout}/shipping-method', [CheckoutController::class, 'shippingMethod'])->name('checkouts.shipping-method');
+        Route::post('checkouts/{checkout}/apply-discount', [CheckoutController::class, 'applyDiscount'])->name('checkouts.apply-discount');
+        Route::put('checkouts/{checkout}/payment-method', [CheckoutController::class, 'paymentMethod'])->name('checkouts.payment-method');
+    });
*** End Patch


Success. Updated the following files:
A /Users/fabianwesner/Herd/shop/routes/api.php

<?php

use App\Http\Controllers\Api\Storefront\V1\CartController;
use App\Http\Controllers\Api\Storefront\V1\CartLineController;
use App\Http\Controllers\Api\Storefront\V1\CheckoutController;
use Illuminate\Support\Facades\Route;

Route::middleware(['store.resolve', 'throttle:api.storefront'])
    ->prefix('storefront/v1')
    ->name('api.storefront.v1.')
    ->group(function (): void {
        Route::post('carts', [CartController::class, 'store'])->name('carts.store');
        Route::get('carts/{cart}', [CartController::class, 'show'])->name('carts.show');
        Route::post('carts/{cart}/lines', [CartLineController::class, 'store'])->name('carts.lines.store');
        Route::put('carts/{cart}/lines/{cartLine}', [CartLineController::class, 'update'])->name('carts.lines.update');
        Route::delete('carts/{cart}/lines/{cartLine}', [CartLineController::class, 'destroy'])->name('carts.lines.destroy');

        Route::post('checkouts', [CheckoutController::class, 'store'])->name('checkouts.store');
        Route::get('checkouts/{checkout}', [CheckoutController::class, 'show'])->name('checkouts.show');
        Route::put('checkouts/{checkout}/address', [CheckoutController::class, 'address'])->name('checkouts.address');
        Route::put('checkouts/{checkout}/shipping-method', [CheckoutController::class, 'shippingMethod'])->name('checkouts.shipping-method');
        Route::post('checkouts/{checkout}/apply-discount', [CheckoutController::class, 'applyDiscount'])->name('checkouts.apply-discount');
        Route::put('checkouts/{checkout}/payment-method', [CheckoutController::class, 'paymentMethod'])->name('checkouts.payment-method');
    });


Output: {"output":"Success. Updated the following files:\nA /Users/fabianwesner/Herd/shop/routes/api.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


sed -n '1,120p' app/Http/Resources/Storefront/V1/CartResource.php


sed -n '1,120p' app/Http/Resources/Storefront/V1/CartLineResource.php


sed -n '1,120p' app/Http/Resources/Storefront/V1/CheckoutResource.php


sed -n '1,120p' app/Http/Resources/Storefront/V1/ShippingRateResource.php


sed -n '1,120p' app/Http/Resources/Storefront/V1/CartResource.php

<?php

namespace App\Http\Resources\Storefront\V1;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class CartResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return parent::toArray($request);
    }
}


sed -n '1,120p' app/Http/Resources/Storefront/V1/CheckoutResource.php

<?php

namespace App\Http\Resources\Storefront\V1;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class CheckoutResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return parent::toArray($request);
    }
}


sed -n '1,120p' app/Http/Resources/Storefront/V1/CartLineResource.php

<?php

namespace App\Http\Resources\Storefront\V1;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class CartLineResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return parent::toArray($request);
    }
}


sed -n '1,120p' app/Http/Resources/Storefront/V1/ShippingRateResource.php

<?php

namespace App\Http\Resources\Storefront\V1;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class ShippingRateResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return parent::toArray($request);
    }
}


Output:
Chunk ID: 821054
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 97
Output:
<?php

namespace App\Http\Resources\Storefront\V1;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class CartResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return parent::toArray($request);
    }
}


Output:
Chunk ID: 1ac4f2
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 98
Output:
<?php

namespace App\Http\Resources\Storefront\V1;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class CartLineResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return parent::toArray($request);
    }
}


Output:
Chunk ID: a27e8a
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 98
Output:
<?php

namespace App\Http\Resources\Storefront\V1;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class CheckoutResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return parent::toArray($request);
    }
}


Output:
Chunk ID: f0f1d1
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 99
Output:
<?php

namespace App\Http\Resources\Storefront\V1;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class ShippingRateResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return parent::toArray($request);
    }
}


Input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/app/Http/Resources/Storefront/V1/CartLineResource.php
@@
     public function toArray(Request $request): array
     {
-        return parent::toArray($request);
+        return [
+            'id' => $this->id,
+            'variant_id' => $this->variant_id,
+            'quantity' => $this->quantity,
+            'unit_price_amount' => $this->unit_price_amount,
+            'line_subtotal_amount' => $this->line_subtotal_amount,
+            'line_discount_amount' => $this->line_discount_amount,
+            'line_total_amount' => $this->line_total_amount,
+            'product' => $this->whenLoaded('variant', fn (): ?array => $this->variant?->relationLoaded('product') ? [
+                'id' => $this->variant->product?->id,
+                'title' => $this->variant->product?->title,
+                'handle' => $this->variant->product?->handle,
+            ] : null),
+            'variant' => $this->whenLoaded('variant', fn (): array => [
+                'id' => $this->variant->id,
+                'sku' => $this->variant->sku,
+                'requires_shipping' => $this->variant->requires_shipping,
+                'options' => $this->variant->relationLoaded('optionValues')
+                    ? $this->variant->optionValues->map(fn ($value): array => [
+                        'name' => $value->option?->name,
+                        'value' => $value->value,
+                    ])->values()->all()
+                    : [],
+            ]),
+        ];
     }
 }
*** End Patch


Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/app/Http/Resources/Storefront/V1/CartLineResource.php

@@ -16,3 +16,27 @@
     {
-        return parent::toArray($request);
+        return [
+            'id' => $this->id,
+            'variant_id' => $this->variant_id,
+            'quantity' => $this->quantity,
+            'unit_price_amount' => $this->unit_price_amount,
+            'line_subtotal_amount' => $this->line_subtotal_amount,
+            'line_discount_amount' => $this->line_discount_amount,
+            'line_total_amount' => $this->line_total_amount,
+            'product' => $this->whenLoaded('variant', fn (): ?array => $this->variant?->relationLoaded('product') ? [
+                'id' => $this->variant->product?->id,
+                'title' => $this->variant->product?->title,
+                'handle' => $this->variant->product?->handle,
+            ] : null),
+            'variant' => $this->whenLoaded('variant', fn (): array => [
+                'id' => $this->variant->id,
+                'sku' => $this->variant->sku,
+                'requires_shipping' => $this->variant->requires_shipping,
+                'options' => $this->variant->relationLoaded('optionValues')
+                    ? $this->variant->optionValues->map(fn ($value): array => [
+                        'name' => $value->option?->name,
+                        'value' => $value->value,
+                    ])->values()->all()
+                    : [],
+            ]),
+        ];
     }


Output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/app/Http/Resources/Storefront/V1/CartLineResource.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/app/Http/Resources/Storefront/V1/CartResource.php
@@
     public function toArray(Request $request): array
     {
-        return parent::toArray($request);
+        $lines = $this->whenLoaded('lines');
+
+        return [
+            'id' => $this->id,
+            'store_id' => $this->store_id,
+            'customer_id' => $this->customer_id,
+            'currency' => $this->currency,
+            'status' => $this->status?->value,
+            'cart_version' => $this->cart_version,
+            'line_count' => $this->relationLoaded('lines') ? $this->lines->sum('quantity') : null,
+            'totals' => $this->relationLoaded('lines') ? [
+                'subtotal' => $this->lines->sum('line_subtotal_amount'),
+                'discount' => $this->lines->sum('line_discount_amount'),
+                'total' => $this->lines->sum('line_total_amount'),
+                'currency' => $this->currency,
+            ] : null,
+            'lines' => CartLineResource::collection($lines),
+            'created_at' => $this->created_at?->toIso8601String(),
+            'updated_at' => $this->updated_at?->toIso8601String(),
+        ];
     }
 }
*** End Patch


Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/app/Http/Resources/Storefront/V1/CartResource.php

@@ -16,3 +16,22 @@
     {
-        return parent::toArray($request);
+        $lines = $this->whenLoaded('lines');
+
+        return [
+            'id' => $this->id,
+            'store_id' => $this->store_id,
+            'customer_id' => $this->customer_id,
+            'currency' => $this->currency,
+            'status' => $this->status?->value,
+            'cart_version' => $this->cart_version,
+            'line_count' => $this->relationLoaded('lines') ? $this->lines->sum('quantity') : null,
+            'totals' => $this->relationLoaded('lines') ? [
+                'subtotal' => $this->lines->sum('line_subtotal_amount'),
+                'discount' => $this->lines->sum('line_discount_amount'),
+                'total' => $this->lines->sum('line_total_amount'),
+                'currency' => $this->currency,
+            ] : null,
+            'lines' => CartLineResource::collection($lines),
+            'created_at' => $this->created_at?->toIso8601String(),
+            'updated_at' => $this->updated_at?->toIso8601String(),
+        ];
     }


Output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/app/Http/Resources/Storefront/V1/CartResource.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/app/Http/Resources/Storefront/V1/ShippingRateResource.php
@@
     public function toArray(Request $request): array
     {
-        return parent::toArray($request);
+        return [
+            'id' => $this->id,
+            'zone_id' => $this->zone_id,
+            'name' => $this->name,
+            'type' => $this->type?->value,
+            'amount' => $this->when($this->getAttribute('calculated_amount') !== null, $this->getAttribute('calculated_amount')),
+            'config' => $this->config_json,
+        ];
     }
 }
*** End Patch


Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/app/Http/Resources/Storefront/V1/ShippingRateResource.php

@@ -16,3 +16,10 @@
     {
-        return parent::toArray($request);
+        return [
+            'id' => $this->id,
+            'zone_id' => $this->zone_id,
+            'name' => $this->name,
+            'type' => $this->type?->value,
+            'amount' => $this->when($this->getAttribute('calculated_amount') !== null, $this->getAttribute('calculated_amount')),
+            'config' => $this->config_json,
+        ];
     }


Output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/app/Http/Resources/Storefront/V1/ShippingRateResource.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/app/Http/Resources/Storefront/V1/CheckoutResource.php
@@
     public function toArray(Request $request): array
     {
-        return parent::toArray($request);
+        return [
+            'id' => $this->id,
+            'store_id' => $this->store_id,
+            'cart_id' => $this->cart_id,
+            'customer_id' => $this->customer_id,
+            'status' => $this->status?->value,
+            'payment_method' => $this->payment_method,
+            'email' => $this->email,
+            'shipping_address' => $this->shipping_address_json,
+            'billing_address' => $this->billing_address_json,
+            'shipping_method_id' => $this->shipping_method_id,
+            'discount_code' => $this->discount_code,
+            'totals' => $this->totals_json,
+            'tax_provider_snapshot' => $this->tax_provider_snapshot_json,
+            'available_shipping_rates' => ShippingRateResource::collection($this->whenLoaded('availableRates')),
+            'cart' => new CartResource($this->whenLoaded('cart')),
+            'expires_at' => $this->expires_at?->toIso8601String(),
+            'created_at' => $this->created_at?->toIso8601String(),
+            'updated_at' => $this->updated_at?->toIso8601String(),
+        ];
     }
 }
*** End Patch


Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/app/Http/Resources/Storefront/V1/CheckoutResource.php

@@ -16,3 +16,22 @@
     {
-        return parent::toArray($request);
+        return [
+            'id' => $this->id,
+            'store_id' => $this->store_id,
+            'cart_id' => $this->cart_id,
+            'customer_id' => $this->customer_id,
+            'status' => $this->status?->value,
+            'payment_method' => $this->payment_method,
+            'email' => $this->email,
+            'shipping_address' => $this->shipping_address_json,
+            'billing_address' => $this->billing_address_json,
+            'shipping_method_id' => $this->shipping_method_id,
+            'discount_code' => $this->discount_code,
+            'totals' => $this->totals_json,
+            'tax_provider_snapshot' => $this->tax_provider_snapshot_json,
+            'available_shipping_rates' => ShippingRateResource::collection($this->whenLoaded('availableRates')),
+            'cart' => new CartResource($this->whenLoaded('cart')),
+            'expires_at' => $this->expires_at?->toIso8601String(),
+            'created_at' => $this->created_at?->toIso8601String(),
+            'updated_at' => $this->updated_at?->toIso8601String(),
+        ];
     }


Output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/app/Http/Resources/Storefront/V1/CheckoutResource.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


The API resources now return explicit cart, line, checkout, and contextual shipping-rate JSON instead of raw model arrays. I’m filling the request validation classes next.


sed -n '1,120p' app/Http/Requests/Api/Storefront/V1/StoreCartLineRequest.php


sed -n '1,120p' app/Http/Requests/Api/Storefront/V1/StoreCartLineRequest.php

<?php

namespace App\Http\Requests\Api\Storefront\V1;

use Illuminate\Foundation\Http\FormRequest;

class StoreCartLineRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return false;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            //
        ];
    }
}


Output:
Chunk ID: 25b20a
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 144
Output:
<?php

namespace App\Http\Requests\Api\Storefront\V1;

use Illuminate\Foundation\Http\FormRequest;

class StoreCartLineRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return false;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            //
        ];
    }
}


Input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/app/Http/Requests/Api/Storefront/V1/StoreCartLineRequest.php
@@
     public function authorize(): bool
     {
-        return false;
+        return true;
@@
         return [
-            //
+            'variant_id' => ['required', 'integer', 'exists:product_variants,id'],
+            'quantity' => ['required', 'integer', 'min:1'],
+            'expected_version' => ['nullable', 'integer', 'min:1'],
         ];
     }
 }
*** End Patch


Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/app/Http/Requests/Api/Storefront/V1/StoreCartLineRequest.php

@@ -13,3 +13,3 @@
     {
-        return false;
+        return true;
     }
@@ -24,3 +24,5 @@
         return [
-            //
+            'variant_id' => ['required', 'integer', 'exists:product_variants,id'],
+            'quantity' => ['required', 'integer', 'min:1'],
+            'expected_version' => ['nullable', 'integer', 'min:1'],
         ];


Output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/app/Http/Requests/Api/Storefront/V1/StoreCartLineRequest.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/app/Http/Requests/Api/Storefront/V1/UpdateCartLineRequest.php
@@
     public function authorize(): bool
     {
-        return false;
+        return true;
@@
         return [
-            //
+            'quantity' => ['required', 'integer', 'min:0'],
+            'expected_version' => ['nullable', 'integer', 'min:1'],
         ];
     }
 }
*** End Patch


Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/app/Http/Requests/Api/Storefront/V1/UpdateCartLineRequest.php

@@ -13,3 +13,3 @@
     {
-        return false;
+        return true;
     }
@@ -24,3 +24,4 @@
         return [
-            //
+            'quantity' => ['required', 'integer', 'min:0'],
+            'expected_version' => ['nullable', 'integer', 'min:1'],
         ];


Output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/app/Http/Requests/Api/Storefront/V1/UpdateCartLineRequest.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/app/Http/Requests/Api/Storefront/V1/StoreCheckoutRequest.php
@@
     public function authorize(): bool
     {
-        return false;
+        return true;
@@
         return [
-            //
+            'cart_id' => ['required', 'integer', 'exists:carts,id'],
         ];
     }
 }
*** End Patch


Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/app/Http/Requests/Api/Storefront/V1/StoreCheckoutRequest.php

@@ -13,3 +13,3 @@
     {
-        return false;
+        return true;
     }
@@ -24,3 +24,3 @@
         return [
-            //
+            'cart_id' => ['required', 'integer', 'exists:carts,id'],
         ];


Output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/app/Http/Requests/Api/Storefront/V1/StoreCheckoutRequest.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}