Output:
Chunk ID: 10c8c3
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 84
Output:
[2026-05-04-codex-5-5-goal a8f0799e] Add admin tax settings API
 5 files changed, 321 insertions(+), 4 deletions(-)
 create mode 100644 app/Http/Controllers/Api/Admin/V1/TaxSettingsController.php
 create mode 100644 app/Http/Resources/Admin/V1/TaxSettingsResource.php
 create mode 100644 tests/Feature/Api/AdminTaxSettingsApiTest.php


Committed `a8f0799e` for tax settings. I’m moving to shipping settings next; it is larger than tax, but the spec-defined endpoints map cleanly to the existing `ShippingZone` and `ShippingRate` models.


array validation wildcard distinct size uppercase JSON API testing

exists validation route model binding nested resources


sed -n '2280,2420p' specs/02-API-ROUTES.md


sed -n '1,260p' app/Models/ShippingZone.php


sed -n '1,260p' app/Models/ShippingRate.php


Output:
Chunk ID: b66f53
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 0
Output:


# Search Results

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

#### Scoping Nested Resources

Laravel's [implicit model binding](/docs/{{version}}/routing#implicit-model-binding-scoping) feature can automatically scope nested bindings such that the resolved child model is confirmed to belong to the parent model. By using the `scoped` method when defining your nested resource, you may enable automatic scoping as well as instruct Laravel which field the child resource should be retrieved by. For more information on how to accomplish this, please see the documentation on [scoping resource routes](#restful-scoping-resource-routes).

---

## Verification Introduction Database Preparation
Verification | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/verification.md

### Database Preparation

Next, your `users` table must contain an `email_verified_at` column to store the date and time that the user's email address was verified. Typically, this is included in Laravel's default `0001_01_01_000000_create_users_table.php` database migration.

---

## Expectations `toHaveKeys(array $keys)`
Expectations | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/expectations.md

### `toHaveKeys(array $keys)`

This expectation ensures that `$value` contains the provided `$keys`.

```php
expect(['id' => 1, 'name' => 'Nuno'])->toHaveKeys(['id', 'name']);
expect(['message' => ['from' => 'Nuno', 'to' => 'Luke'] ])->toHaveKeys(['message.from', 'message.to']);
```

---

## Validation Available Validation Rules Basic Usage of Exists Rule
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### Basic Usage of Exists Rule

```php
'state' => 'exists:states'
```

If the `column` option is not specified, the field name will be used. So, in this case, the rule will validate that the `states` database table contains a record with a `state` column value matching the request's `state` attribute value.

---

## Views Creating and Rendering Views Determining if a View Exists
Views | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/views.md

### Determining if a View Exists

If you need to determine if a view exists, you may use the `View` facade. The `exists` method will return `true` if the view exists:

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

if (View::exists('admin.profile')) {
    // ...
}
```

---

## Expectations `toBeArray()`
Expectations | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/expectations.md

### `toBeArray()`

This expectation ensures that `$value` is an array.

```php
expect(['Pest','PHP','Laravel'])->toBeArray();
```

---

## Upgrade Upgrading To 12.0 From 11.x Database Database Constructor Signature Changes
Upgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md

#### Database Constructor Signature Changes

**Likelihood Of Impact: Very Low**

In Laravel 12, several low-level database classes now require an `Illuminate\Database\Connection` instance to be provided via their constructors.

**These changes are primarily applicable to database package maintainers - it is extremely unlikely any of these changes affect normal application development.**

`Illuminate\Database\Schema\Blueprint`

The constructor of the `Illuminate\Database\Schema\Blueprint` class now expects a `Connection` instance as its first argument. This primarily affects applications or packages that manually instantiate `Blueprint` instances.

`Illuminate\Database\Grammar`

The constructor of the `Illuminate\Database\Grammar` class also now requires a `Connection` instance. In previous versions, the connection was assigned after construction using the `setConnection()` method. This method has been removed in Laravel 12:

```php
// Laravel <= 11.x
$grammar = new MySqlGrammar;
$grammar->setConnection($connection);

// Laravel >= 12.x
$grammar = new MySqlGrammar($connection);
````

In addition, the following APIs have been removed or deprecated:

- The `Blueprint::getPrefix()` method is deprecated.
- The `Connection::withTablePrefix()` method has been removed.
- The `Grammar::getTablePrefix()` and `setTablePrefix()` methods are deprecated.
- The `Grammar::setConnection()` method has been removed.

When working with table prefixes, you should now retrieve them directly from the database connection:

```php
$prefix = $connection->getTablePrefix();
```

If you maintain custom database drivers, schema builders, or grammar implementations, you should review their constructors and ensure a `Connection` instance is provided.

---

## Precognition Validating Arrays
Precognition | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/precognition.md

## Validating Arrays

You may use wildcards to validate fields within arrays or nested objects. Each `*` matches a single path segment:

```js
// Validate email for all users in an array...
form.validate('users.*.email');

// Validate all fields in a profile object...
form.validate('profile.*');

// Validate all fields for all users...
form.validate('users.*.*');
```

---

## Database Database Transactions
Database | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/database.md

## Database Transactions

You may use the `transaction` method provided by the `DB` facade to run a set of operations within a database transaction. If an exception is thrown within the transaction closure, the transaction will automatically be rolled back and the exception is re-thrown. If the closure executes successfully, the transaction will automatically be committed. You don't need to worry about manually rolling back or committing while using the `transaction` method:

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

DB::transaction(function () {
    DB::update('update users set votes = 1');

    DB::delete('delete from posts');
});
```

---

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

#### exists:_table_,_column_

The field under validation must exist in a given database table.

---

## Expectations `toMatchArray($array)`
Expectations | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/expectations.md

### `toMatchArray($array)`

This expectation ensures that the `$value` array matches the given `$array` subset.

```php
$user = [
    'id'    => 1,
    'name'  => 'Nuno',
    'email' => 'enunomaduro@gmail.com',
    'is_active' => true,
];

expect($user)->toMatchArray([
    'email' => 'enunomaduro@gmail.com',
    'name' => 'Nuno'
]);
```

---

## Upgrade Upgrading To 12.0 From 11.x Database Multi-Schema Database Inspecting
Upgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md

#### Multi-Schema Database Inspecting

**Likelihood Of Impact: Low**

The `Schema::getTables()`, `Schema::getViews()`, and `Schema::getTypes()` methods now include the results from all schemas by default. You may pass the `schema` argument to retrieve the result for the given schema only:

```php
// All tables on all schemas...
$tables = Schema::getTables();

// All tables on the 'main' schema...
$tables = Schema::getTables(schema: 'main');

// All tables on the 'main' and 'blog' schemas...
$tables = Schema::getTables(schema: ['main', 'blog']);
```

The `Schema::getTableListing()` method now returns schema-qualified table names by default. You may pass the `schemaQualified` argument to change the behavior as desired:

```php
$tables = Schema::getTableListing();
// ['main.migrations', 'main.users', 'blog.posts']

$tables = Schema::getTableListing(schema: 'main');
// ['main.migrations', 'main.users']

$tables = Schema::getTableListing(schema: 'main', schemaQualified: false);
// ['migrations', 'users']
```

The `db:table` and `db:show` commands now output the results of all schemas on MySQL, MariaDB, and SQLite, just like PostgreSQL and SQL Server.

---

## Validation Validating Arrays Error Message Indexes and Positions
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

### Error Message Indexes and Positions

When validating arrays, you may want to reference the index or position of a particular item that failed validation within the error message displayed by your application. To accomplish this, you may include the `:index` (starts from `0`), `:position` (starts from `1`), or `:ordinal-position` (starts from `1st`) placeholders within your [custom validation message](#manual-customizing-the-error-messages):

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

$input = [
    'photos' => [
        [
            'name' => 'BeachVacation.jpg',
            'description' => 'A photo of my beach vacation!',
        ],
        [
            'name' => 'GrandCanyon.jpg',
            'description' => '',
        ],
    ],
];

Validator::validate($input, [
    'photos.*.description' => 'required',
], [
    'photos.*.description.required' => 'Please describe photo #:position.',
]);
```

Given the example above, validation will fail and the user will be presented with the following error of _"Please describe photo #2."_

If necessary, you may reference more deeply nested indexes and positions via `second-index`, `second-position`, `third-index`, `third-position`, etc.

```php
'photos.*.attributes.*.string' => 'Invalid attribute for photo #:second-position.',
```

---

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

## Validating Arrays

As discussed in the [array validation rule documentation](#rule-array), the `array` rule accepts a list of allowed array keys. If any additional keys are present within the array, validation will fail:

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

$input = [
    'user' => [
        'name' => 'Taylor Otwell',
        'username' => 'taylorotwell',
        'admin' => true,
    ],
];

Validator::make($input, [
    'user' => 'array:name,username',
]);
```

In general, you should always specify the array keys that are allowed to be present within your array. Otherwise, the validator's `validate` and `validated` methods will return all of the validated data, including the array and all of its keys, even if those keys were not validated by other nested array validation rules.

---

## Validation Validating Arrays Validating Nested Array Input
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

### Validating Nested Array Input

Validating nested array-based form input fields doesn't have to be a pain. You may use "dot notation" to validate attributes within an array. For example, if the incoming HTTP request contains a `photos[profile]` field, you may validate it like so:

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

$validator = Validator::make($request->all(), [
    'photos.profile' => 'required|image',
]);
```

You may also validate each element of an array. For example, to validate that each email in a given array input field is unique, you may do the following:

```php
$validator = Validator::make($request->all(), [
    'users.*.email' => 'email|unique:users',
    'users.*.first_name' => 'required_with:users.*.last_name',
]);
```

Likewise, you may use the `*` character when specifying [custom validation messages in your language files](#custom-messages-for-specific-attributes), making it a breeze to use a single validation message for array-based fields:

```php
'custom' => [
    'users.*.email' => [
        'unique' => 'Each user must have a unique email address',
    ]
],
```

---

## Http Tests Available Assertions Response Assertions assertJsonIsArray
Http Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md

#### assertJsonIsArray

Assert that the response JSON is an array:

```php
$response->assertJsonIsArray();
```

---

## Upgrade Upgrading To 12.0 From 11.x Authentication Updated `DatabaseTokenRepository` Constructor Signature
Upgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md

#### Updated `DatabaseTokenRepository` Constructor Signature

**Likelihood Of Impact: Very Low**

The constructor of the `Illuminate\Auth\Passwords\DatabaseTokenRepository` class now expects the `$expires` parameter to be given in seconds, rather than minutes.

---

## Validation Working With Error Messages Determining if Messages Exist for a Field
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### Determining if Messages Exist for a Field

The `has` method may be used to determine if any error messages exist for a given field:

```php
if ($errors->has('email')) {
    // ...
}
```

---

## Validation Validating Arrays Validating Nested Array Input Accessing Nested Array Data
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### Accessing Nested Array Data

Sometimes you may need to access the value for a given nested array element when assigning validation rules to the attribute. You may accomplish this using the `Rule::forEach` method. The `forEach` method accepts a closure that will be invoked for each iteration of the array attribute under validation and will receive the attribute's value and explicit, fully-expanded attribute name. The closure should return an array of rules to assign to the array element:

```php
use App\Rules\HasPermission;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

$validator = Validator::make($request->all(), [
    'companies.*.id' => Rule::forEach(function (string|null $value, string $attribute) {
        return [
            Rule::exists(Company::class, 'id'),
            new HasPermission('manage-company', $value),
        ];
    }),
]);
```

---

## Database Database Transactions Manually Using Transactions
Database | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/database.md

#### Manually Using Transactions

If you would like to begin a transaction manually and have complete control over rollbacks and commits, you may use the `beginTransaction` method provided by the `DB` facade:

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

DB::beginTransaction();
```

You can rollback the transaction via the `rollBack` method:

```php
DB::rollBack();
```

Lastly, you can commit a transaction via the `commit` method:

```php
DB::commit();
```

> [!NOTE]
> The `DB` facade's transaction methods control the transactions for both the [query builder](/docs/{{version}}/queries) and [Eloquent ORM](/docs/{{version}}/eloquent).

---

## Testing Running Tests Running Tests in Parallel Parallel Testing and Databases
Testing | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/testing.md

#### Parallel Testing and Databases

As long as you have configured a primary database connection, Laravel automatically handles creating and migrating a test database for each parallel process that is running your tests. The test databases will be suffixed with a process token which is unique per process. For example, if you have two parallel test processes, Laravel will create and use `your_db_test_1` and `your_db_test_2` test databases.

By default, test databases persist between calls to the `test` Artisan command so that they can be used again by subsequent `test` invocations. However, you may re-create them using the `--recreate-databases` option:

```shell
php artisan test --parallel --recreate-databases
```

---

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

#### required_array_keys:_foo_,_bar_,...

The field under validation must be an array and must contain at least the specified keys.

---

## Session Interacting With the Session Retrieving Data Determining if an Item Exists in the Session
Session | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/session.md

#### Determining if an Item Exists in the Session

To determine if an item is present in the session, you may use the `has` method. The `has` method returns `true` if the item is present and is not `null`:

```php
if ($request->session()->has('users')) {
    // ...
}
```

To determine if an item is present in the session, even if its value is `null`, you may use the `exists` method:

```php
if ($request->session()->exists('users')) {
    // ...
}
```

To determine if an item is not present in the session, you may use the `missing` method. The `missing` method returns `true` if the item is not present:

```php
if ($request->session()->missing('users')) {
    // ...
}
```

---

## Structure The Root Directory The Database Directory
Structure | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/structure.md

### The Database Directory

The `database` directory contains your database migrations, model factories, and seeds. If you wish, you may also use this directory to hold an SQLite database.

---

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

#### in_array_keys:_value_.*

The field under validation must be an array having at least one of the given _values_ as a key within the array:

```php
'config' => 'array|in_array_keys:timezone'
```

---

## Eloquent Relationships Eager Loading Constraining Eager Loads Constraining Eager Loads With Relationship Existence
Eloquent Relationships | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent-relationships.md

#### Constraining Eager Loads With Relationship Existence

You may sometimes find yourself needing to check for the existence of a relationship while simultaneously loading the relationship based on the same conditions. For example, you may wish to only retrieve `User` models that have child `Post` models matching a given query condition while also eager loading the matching posts. You may accomplish this using the `withWhereHas` method:

```php
use App\Models\User;

$users = User::withWhereHas('posts', function ($query) {
    $query->where('featured', true);
})->get();
```

---

## Validation Conditionally Adding Rules Complex Conditional Array Validation
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### Complex Conditional Array Validation

Sometimes you may want to validate a field based on another field in the same nested array whose index you do not know. In these situations, you may allow your closure to receive a second argument which will be the current individual item in the array being validated:

```php
$input = [
    'channels' => [
        [
            'type' => 'email',
            'address' => 'abigail@example.com',
        ],
        [
            'type' => 'url',
            'address' => 'https://example.com',
        ],
    ],
];

$validator->sometimes('channels.*.address', 'email', function (Fluent $input, Fluent $item) {
    return $item->type === 'email';
});

$validator->sometimes('channels.*.address', 'url', function (Fluent $input, Fluent $item) {
    return $item->type !== 'email';
});
```

Like the `$input` parameter passed to the closure, the `$item` parameter is an instance of `Illuminate\Support\Fluent` when the attribute data is an array; otherwise, it is a string.

---

## Sail Installation and Setup Installing Sail Into Existing Applications Using Devcontainers
Sail | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/sail.md

#### Using Devcontainers

If you would like to develop within a [Devcontainer](https://code.visualstudio.com/docs/remote/containers), you may provide the `--devcontainer` option to the `sail:install` command. The `--devcontainer` option will instruct the `sail:install` command to publish a default `.devcontainer/devcontainer.json ` file to the root of your application:

```shell
php artisan sail:install --devcontainer
```

---

## Database Database Transactions Handling Deadlocks
Database | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/database.md

#### Handling Deadlocks

The `transaction` method accepts an optional second argument which defines the number of times a transaction should be retried when a deadlock occurs. Once these attempts have been exhausted, an exception will be thrown:

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

DB::transaction(function () {
    DB::update('update users set votes = 1');

    DB::delete('delete from posts');
}, attempts: 5);
```

---

## Session Introduction Driver Prerequisites Database
Session | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/session.md

#### Database

When using the `database` session driver, you will need to ensure that you have a database table to contain the session data. Typically, this is included in Laravel's default `0001_01_01_000000_create_users_table.php` [database migration](/docs/{{version}}/migrations); however, if for any reason you do not have a `sessions` table, you may use the `make:session-table` Artisan command to generate this migration:

```shell
php artisan make:session-table

php artisan migrate
```

---

## Sail Installation and Setup Installing Sail Into Existing Applications Adding Additional Services
Sail | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/sail.md

#### Adding Additional Services

If you would like to add an additional service to your existing Sail installation, you may run the `sail:add` Artisan command:

```shell
php artisan sail:add
```

---

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

#### array

The field under validation must be a PHP `array`.

When additional values are provided to the `array` rule, each key in the input array must be present within the list of values provided to the rule. In the following example, the `admin` key in the input array is invalid since it is not contained in the list of values provided to the `array` rule:

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

$input = [
    'user' => [
        'name' => 'Taylor Otwell',
        'username' => 'taylorotwell',
        'admin' => true,
    ],
];

Validator::make($input, [
    'user' => 'array:name,username',
]);
```

In general, you should always specify the array keys that are allowed to be present within your array.

---

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

#### in_array:_anotherfield_.*

The field under validation must exist in _anotherfield_'s values.

---

## Eloquent Relationships Querying Relations Querying Relationship Existence
Eloquent Relationships | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent-relationships.md

### Querying Relationship Existence

When retrieving model records, you may wish to limit your results based on the existence of a relationship. For example, imagine you want to retrieve all blog posts that have at least one comment. To do so, you may pass the name of the relationship to the `has` and `orHas` methods:

```php
use App\Models\Post;

// Retrieve all posts that have at least one comment...
$posts = Post::has('comments')->get();
```

You may also specify an operator and count value to further customize the query:

```php
// Retrieve all posts that have three or more comments...
$posts = Post::has('comments', '>=', 3)->get();
```

Nested `has` statements may be constructed using "dot" notation. For example, you may retrieve all posts that have at least one comment that has at least one image:

```php
// Retrieve posts that have at least one comment with images...
$posts = Post::has('comments.images')->get();
```

If you need even more power, you may use the `whereHas` and `orWhereHas` methods to define additional query constraints on your `has` queries, such as inspecting the content of a comment:

```php
use Illuminate\Database\Eloquent\Builder;

// Retrieve posts with at least one comment containing words like code%...
$posts = Post::whereHas('comments', function (Builder $query) {
    $query->where('content', 'like', 'code%');
})->get();

// Retrieve posts with at least ten comments containing words like code%...
$posts = Post::whereHas('comments', function (Builder $query) {
    $query->where('content', 'like', 'code%');
}, '>=', 10)->get();
```

> [!WARNING]
> Eloquent does not currently support querying for relationship existence across databases. The relationships must exist within the same database.

---

## Search Laravel Scout Database Engine
Search | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/search.md

### Database Engine

Scout's built-in database engine performs full-text and `LIKE` searches against your existing database — no external service or extra infrastructure required. Simply add the `Searchable` trait to your model and define a `toSearchableArray` method that returns the columns you want to be searchable.

You may use PHP attributes to control the search strategy for each column. `SearchUsingFullText` will use your database's full-text index, `SearchUsingPrefix` will only match from the beginning of the string (`example%`), and any columns without an attribute use a default `LIKE` strategy with wildcards on both sides (`%example%`):

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Attributes\SearchUsingFullText;
use Laravel\Scout\Attributes\SearchUsingPrefix;
use Laravel\Scout\Searchable;

class Article extends Model
{
    use Searchable;

    #[SearchUsingPrefix(['id'])]
    #[SearchUsingFullText(['title', 'body'])]
    public function toSearchableArray(): array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'body' => $this->body,
        ];
    }
}
```

> [!WARNING]
> Before specifying that a column should use full-text query constraints, ensure that the column has been assigned a [full-text index](/docs/{{version}}/migrations#available-index-types).

Once the trait is added, you may search your model using Scout's `search` method. Scout's database engine will automatically order results by relevance, even on PostgreSQL:

```php
$articles = Article::search('Laravel')->get();
```

The database engine is a great choice when your search needs are moderate and you want the convenience of Scout's automatic index syncing without deploying an external service. It handles the most common search use cases well, including filtering, pagination, and soft-deleted record handling. For complete details, consult the [Scout documentation](/docs/{{version}}/scout#database-engine).

---

## Sail Installation and Setup Installing Sail Into Existing Applications
Sail | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/sail.md

### Installing Sail Into Existing Applications

If you are interested in using Sail with an existing Laravel application, you may simply install Sail using the Composer package manager. Of course, these steps assume that your existing local development environment allows you to install Composer dependencies:

```shell
composer require laravel/sail --dev
```

After Sail has been installed, you may run the `sail:install` Artisan command. This command will publish Sail's `compose.yaml` file to the root of your application and modify your `.env` file with the required environment variables in order to connect to the Docker services:

```shell
php artisan sail:install
```

Finally, you may start Sail. To continue learning how to use Sail, please continue reading the remainder of this documentation:

```shell
./vendor/bin/sail up
```

> [!WARNING]
> If you are using Docker Desktop for Linux, you should use the `default` Docker context by executing the following command: `docker context use default`. In addition, if you encounter file permission errors within containers, you may need to set the `SUPERVISOR_PHP_USER` environment variable to `root`.

---

## Queries Advanced Where Clauses Where Exists Clauses
Queries | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/queries.md

### Where Exists Clauses

The `whereExists` method allows you to write "where exists" SQL clauses. The `whereExists` method accepts a closure which will receive a query builder instance, allowing you to define the query that should be placed inside of the "exists" clause:

```php
$users = DB::table('users')
    ->whereExists(function (Builder $query) {
        $query->select(DB::raw(1))
            ->from('orders')
            ->whereColumn('orders.user_id', 'users.id');
    })
    ->get();
```

Alternatively, you may provide a query object to the `whereExists` method instead of a closure:

```php
$orders = DB::table('orders')
    ->select(DB::raw(1))
    ->whereColumn('orders.user_id', 'users.id');

$users = DB::table('users')
    ->whereExists($orders)
    ->get();
```

Both of the examples above will produce the following SQL:

```sql
select * from users
where exists (
    select 1
    from orders
    where orders.user_id = users.id
)
```

---

## Database Running SQL Queries Implicit Commits
Database | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/database.md

#### Implicit Commits

When using the `DB` facade's `statement` and `unprepared` methods within transactions you must be careful to avoid statements that cause [implicit commits](https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html). These statements will cause the database engine to indirectly commit the entire transaction, leaving Laravel unaware of the database's transaction level. An example of such a statement is creating a database table:

```php
DB::unprepared('create table a (col varchar(1) null)');
```

Please refer to the MySQL manual for [a list of all statements](https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html) that trigger implicit commits.

_[Truncated]_


Output:
Wall time: 3.3309 seconds
Output:
[{"type":"text","text":"# Search Results\n\n## Controllers Resource Controllers Nested Resources Scoping Nested Resources\nControllers | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/controllers.md\n\n\n#### Scoping Nested Resources\n\nLaravel's [implicit model binding](/docs/{{version}}/routing#implicit-model-binding-scoping) feature can automatically scope nested bindings such that the resolved child model is confirmed to belong to the parent model. By using the `scoped` method when defining your nested resource, you may enable automatic scoping as well as instruct Laravel which field the child resource should be retrieved by. For more information on how to accomplish this, please see the documentation on [scoping resource routes](#restful-scoping-resource-routes).\n\n---\n\n## Verification Introduction Database Preparation\nVerification | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/verification.md\n\n\n### Database Preparation\n\nNext, your `users` table must contain an `email_verified_at` column to store the date and time that the user's email address was verified. Typically, this is included in Laravel's default `0001_01_01_000000_create_users_table.php` database migration.\n\n---\n\n## Expectations `toHaveKeys(array $keys)`\nExpectations | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/expectations.md\n\n\n### `toHaveKeys(array $keys)`\n\nThis expectation ensures that `$value` contains the provided `$keys`.\n\n```php\nexpect(['id' => 1, 'name' => 'Nuno'])->toHaveKeys(['id', 'name']);\nexpect(['message' => ['from' => 'Nuno', 'to' => 'Luke'] ])->toHaveKeys(['message.from', 'message.to']);\n```\n\n---\n\n## Validation Available Validation Rules Basic Usage of Exists Rule\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### Basic Usage of Exists Rule\n\n```php\n'state' => 'exists:states'\n```\n\nIf the `column` option is not specified, the field name will be used. So, in this case, the rule will validate that the `states` database table contains a record with a `state` column value matching the request's `state` attribute value.\n\n---\n\n## Views Creating and Rendering Views Determining if a View Exists\nViews | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/views.md\n\n\n### Determining if a View Exists\n\nIf you need to determine if a view exists, you may use the `View` facade. The `exists` method will return `true` if the view exists:\n\n```php\nuse Illuminate\\Support\\Facades\\View;\n\nif (View::exists('admin.profile')) {\n    // ...\n}\n```\n\n---\n\n## Expectations `toBeArray()`\nExpectations | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/expectations.md\n\n\n### `toBeArray()`\n\nThis expectation ensures that `$value` is an array.\n\n```php\nexpect(['Pest','PHP','Laravel'])->toBeArray();\n```\n\n---\n\n## Upgrade Upgrading To 12.0 From 11.x Database Database Constructor Signature Changes\nUpgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md\n\n\n#### Database Constructor Signature Changes\n\n**Likelihood Of Impact: Very Low**\n\nIn Laravel 12, several low-level database classes now require an `Illuminate\\Database\\Connection` instance to be provided via their constructors.\n\n**These changes are primarily applicable to database package maintainers - it is extremely unlikely any of these changes affect normal application development.**\n\n`Illuminate\\Database\\Schema\\Blueprint`\n\nThe constructor of the `Illuminate\\Database\\Schema\\Blueprint` class now expects a `Connection` instance as its first argument. This primarily affects applications or packages that manually instantiate `Blueprint` instances.\n\n`Illuminate\\Database\\Grammar`\n\nThe constructor of the `Illuminate\\Database\\Grammar` class also now requires a `Connection` instance. In previous versions, the connection was assigned after construction using the `setConnection()` method. This method has been removed in Laravel 12:\n\n```php\n// Laravel <= 11.x\n$grammar = new MySqlGrammar;\n$grammar->setConnection($connection);\n\n// Laravel >= 12.x\n$grammar = new MySqlGrammar($connection);\n````\n\nIn addition, the following APIs have been removed or deprecated:\n\n
\n\n- The `Blueprint::getPrefix()` method is deprecated.\n- The `Connection::withTablePrefix()` method has been removed.\n- The `Grammar::getTablePrefix()` and `setTablePrefix()` methods are deprecated.\n- The `Grammar::setConnection()` method has been removed.\n\n
\n\nWhen working with table prefixes, you should now retrieve them directly from the database connection:\n\n```php\n$prefix = $connection->getTablePrefix();\n```\n\nIf you maintain custom database drivers, schema builders, or grammar implementations, you should review their constructors and ensure a `Connection` instance is provided.\n\n---\n\n## Precognition Validating Arrays\nPrecognition | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/precognition.md\n\n\n## Validating Arrays\n\nYou may use wildcards to validate fields within arrays or nested objects. Each `*` matches a single path segment:\n\n```js\n// Validate email for all users in an array...\nform.validate('users.*.email');\n\n// Validate all fields in a profile object...\nform.validate('profile.*');\n\n// Validate all fields for all users...\nform.validate('users.*.*');\n```\n\n---\n\n## Database Database Transactions\nDatabase | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/database.md\n\n\n## Database Transactions\n\nYou may use the `transaction` method provided by the `DB` facade to run a set of operations within a database transaction. If an exception is thrown within the transaction closure, the transaction will automatically be rolled back and the exception is re-thrown. If the closure executes successfully, the transaction will automatically be committed. You don't need to worry about manually rolling back or committing while using the `transaction` method:\n\n```php\nuse Illuminate\\Support\\Facades\\DB;\n\nDB::transaction(function () {\n    DB::update('update users set votes = 1');\n\n    DB::delete('delete from posts');\n});\n```\n\n---\n\n## Validation Available Validation Rules exists:_table_,_column_\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### exists:_table_,_column_\n\nThe field under validation must exist in a given database table.\n\n---\n\n## Expectations `toMatchArray($array)`\nExpectations | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/expectations.md\n\n\n### `toMatchArray($array)`\n\nThis expectation ensures that the `$value` array matches the given `$array` subset.\n\n```php\n$user = [\n    'id'    => 1,\n    'name'  => 'Nuno',\n    'email' => 'enunomaduro@gmail.com',\n    'is_active' => true,\n];\n\nexpect($user)->toMatchArray([\n    'email' => 'enunomaduro@gmail.com',\n    'name' => 'Nuno'\n]);\n```\n\n---\n\n## Upgrade Upgrading To 12.0 From 11.x Database Multi-Schema Database Inspecting\nUpgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md\n\n\n#### Multi-Schema Database Inspecting\n\n**Likelihood Of Impact: Low**\n\nThe `Schema::getTables()`, `Schema::getViews()`, and `Schema::getTypes()` methods now include the results from all schemas by default. You may pass the `schema` argument to retrieve the result for the given schema only:\n\n```php\n// All tables on all schemas...\n$tables = Schema::getTables();\n\n// All tables on the 'main' schema...\n$tables = Schema::getTables(schema: 'main');\n\n// All tables on the 'main' and 'blog' schemas...\n$tables = Schema::getTables(schema: ['main', 'blog']);\n```\n\nThe `Schema::getTableListing()` method now returns schema-qualified table names by default. You may pass the `schemaQualified` argument to change the behavior as desired:\n\n```php\n$tables = Schema::getTableListing();\n// ['main.migrations', 'main.users', 'blog.posts']\n\n$tables = Schema::getTableListing(schema: 'main');\n// ['main.migrations', 'main.users']\n\n$tables = Schema::getTableListing(schema: 'main', schemaQualified: false);\n// ['migrations', 'users']\n```\n\nThe `db:table` and `db:show` commands now output the results of all schemas on MySQL, MariaDB, and SQLite, just like PostgreSQL and SQL Server.\n\n---\n\n## Validation Validating Arrays Error Message Indexes and Positions\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n### Error Message Indexes and Positions\n\nWhen validating arrays, you may want to reference the index or position of a particular item that failed validation within the error message displayed by your application. To accomplish this, you may include the `:index` (starts from `0`), `:position` (starts from `1`), or `:ordinal-position` (starts from `1st`) placeholders within your [custom validation message](#manual-customizing-the-error-messages):\n\n```php\nuse Illuminate\\Support\\Facades\\Validator;\n\n$input = [\n    'photos' => [\n        [\n            'name' => 'BeachVacation.jpg',\n            'description' => 'A photo of my beach vacation!',\n        ],\n        [\n            'name' => 'GrandCanyon.jpg',\n            'description' => '',\n        ],\n    ],\n];\n\nValidator::validate($input, [\n    'photos.*.description' => 'required',\n], [\n    'photos.*.description.required' => 'Please describe photo #:position.',\n]);\n```\n\nGiven the example above, validation will fail and the user will be presented with the following error of _\"Please describe photo #2.\"_\n\nIf necessary, you may reference more deeply nested indexes and positions via `second-index`, `second-position`, `third-index`, `third-position`, etc.\n\n```php\n'photos.*.attributes.*.string' => 'Invalid attribute for photo #:second-position.',\n```\n\n---\n\n## Validation Validating Arrays\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n## Validating Arrays\n\nAs discussed in the [array validation rule documentation](#rule-array), the `array` rule accepts a list of allowed array keys. If any additional keys are present within the array, validation will fail:\n\n```php\nuse Illuminate\\Support\\Facades\\Validator;\n\n$input = [\n    'user' => [\n        'name' => 'Taylor Otwell',\n        'username' => 'taylorotwell',\n        'admin' => true,\n    ],\n];\n\nValidator::make($input, [\n    'user' => 'array:name,username',\n]);\n```\n\nIn general, you should always specify the array keys that are allowed to be present within your array. Otherwise, the validator's `validate` and `validated` methods will return all of the validated data, including the array and all of its keys, even if those keys were not validated by other nested array validation rules.\n\n---\n\n## Validation Validating Arrays Validating Nested Array Input\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n### Validating Nested Array Input\n\nValidating nested array-based form input fields doesn't have to be a pain. You may use \"dot notation\" to validate attributes within an array. For example, if the incoming HTTP request contains a `photos[profile]` field, you may validate it like so:\n\n```php\nuse Illuminate\\Support\\Facades\\Validator;\n\n$validator = Validator::make($request->all(), [\n    'photos.profile' => 'required|image',\n]);\n```\n\nYou may also validate each element of an array. For example, to validate that each email in a given array input field is unique, you may do the following:\n\n```php\n$validator = Validator::make($request->all(), [\n    'users.*.email' => 'email|unique:users',\n    'users.*.first_name' => 'required_with:users.*.last_name',\n]);\n```\n\nLikewise, you may use the `*` character when specifying [custom validation messages in your language files](#custom-messages-for-specific-attributes), making it a breeze to use a single validation message for array-based fields:\n\n```php\n'custom' => [\n    'users.*.email' => [\n        'unique' => 'Each user must have a unique email address',\n    ]\n],\n```\n\n---\n\n## Http Tests Available Assertions Response Assertions assertJsonIsArray\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n#### assertJsonIsArray\n\nAssert that the response JSON is an array:\n\n```php\n$response->assertJsonIsArray();\n```\n\n---\n\n## Upgrade Upgrading To 12.0 From 11.x Authentication Updated `DatabaseTokenRepository` Constructor Signature\nUpgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md\n\n\n#### Updated `DatabaseTokenRepository` Constructor Signature\n\n**Likelihood Of Impact: Very Low**\n\nThe constructor of the `Illuminate\\Auth\\Passwords\\DatabaseTokenRepository` class now expects the `$expires` parameter to be given in seconds, rather than minutes.\n\n---\n\n## Validation Working With Error Messages Determining if Messages Exist for a Field\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### Determining if Messages Exist for a Field\n\nThe `has` method may be used to determine if any error messages exist for a given field:\n\n```php\nif ($errors->has('email')) {\n    // ...\n}\n```\n\n---\n\n## Validation Validating Arrays Validating Nested Array Input Accessing Nested Array Data\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### Accessing Nested Array Data\n\nSometimes you may need to access the value for a given nested array element when assigning validation rules to the attribute. You may accomplish this using the `Rule::forEach` method. The `forEach` method accepts a closure that will be invoked for each iteration of the array attribute under validation and will receive the attribute's value and explicit, fully-expanded attribute name. The closure should return an array of rules to assign to the array element:\n\n```php\nuse App\\Rules\\HasPermission;\nuse Illuminate\\Support\\Facades\\Validator;\nuse Illuminate\\Validation\\Rule;\n\n$validator = Validator::make($request->all(), [\n    'companies.*.id' => Rule::forEach(function (string|null $value, string $attribute) {\n        return [\n            Rule::exists(Company::class, 'id'),\n            new HasPermission('manage-company', $value),\n        ];\n    }),\n]);\n```\n\n---\n\n## Database Database Transactions Manually Using Transactions\nDatabase | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/database.md\n\n\n#### Manually Using Transactions\n\nIf you would like to begin a transaction manually and have complete control over rollbacks and commits, you may use the `beginTransaction` method provided by the `DB` facade:\n\n```php\nuse Illuminate\\Support\\Facades\\DB;\n\nDB::beginTransaction();\n```\n\nYou can rollback the transaction via the `rollBack` method:\n\n```php\nDB::rollBack();\n```\n\nLastly, you can commit a transaction via the `commit` method:\n\n```php\nDB::commit();\n```\n\n> [!NOTE]\n> The `DB` facade's transaction methods control the transactions for both the [query builder](/docs/{{version}}/queries) and [Eloquent ORM](/docs/{{version}}/eloquent).\n\n---\n\n## Testing Running Tests Running Tests in Parallel Parallel Testing and Databases\nTesting | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/testing.md\n\n\n#### Parallel Testing and Databases\n\nAs long as you have configured a primary database connection, Laravel automatically handles creating and migrating a test database for each parallel process that is running your tests. The test databases will be suffixed with a process token which is unique per process. For example, if you have two parallel test processes, Laravel will create and use `your_db_test_1` and `your_db_test_2` test databases.\n\nBy default, test databases persist between calls to the `test` Artisan command so that they can be used again by subsequent `test` invocations. However, you may re-create them using the `--recreate-databases` option:\n\n```shell\nphp artisan test --parallel --recreate-databases\n```\n\n---\n\n## Validation Available Validation Rules required_array_keys:_foo_,_bar_,...\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### required_array_keys:_foo_,_bar_,...\n\nThe field under validation must be an array and must contain at least the specified keys.\n\n---\n\n## Session Interacting With the Session Retrieving Data Determining if an Item Exists in the Session\nSession | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/session.md\n\n\n#### Determining if an Item Exists in the Session\n\nTo determine if an item is present in the session, you may use the `has` method. The `has` method returns `true` if the item is present and is not `null`:\n\n```php\nif ($request->session()->has('users')) {\n    // ...\n}\n```\n\nTo determine if an item is present in the session, even if its value is `null`, you may use the `exists` method:\n\n```php\nif ($request->session()->exists('users')) {\n    // ...\n}\n```\n\nTo determine if an item is not present in the session, you may use the `missing` method. The `missing` method returns `true` if the item is not present:\n\n```php\nif ($request->session()->missing('users')) {\n    // ...\n}\n```\n\n---\n\n## Structure The Root Directory The Database Directory\nStructure | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/structure.md\n\n\n### The Database Directory\n\nThe `database` directory contains your database migrations, model factories, and seeds. If you wish, you may also use this directory to hold an SQLite database.\n\n---\n\n## Validation Available Validation Rules in_array_keys:_value_.*\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### in_array_keys:_value_.*\n\nThe field under validation must be an array having at least one of the given _values_ as a key within the array:\n\n```php\n'config' => 'array|in_array_keys:timezone'\n```\n\n---\n\n## Eloquent Relationships Eager Loading Constraining Eager Loads Constraining Eager Loads With Relationship Existence\nEloquent Relationships | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent-relationships.md\n\n\n#### Constraining Eager Loads With Relationship Existence\n\nYou may sometimes find yourself needing to check for the existence of a relationship while simultaneously loading the relationship based on the same conditions. For example, you may wish to only retrieve `User` models that have child `Post` models matching a given query condition while also eager loading the matching posts. You may accomplish this using the `withWhereHas` method:\n\n```php\nuse App\\Models\\User;\n\n$users = User::withWhereHas('posts', function ($query) {\n    $query->where('featured', true);\n})->get();\n```\n\n---\n\n## Validation Conditionally Adding Rules Complex Conditional Array Validation\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### Complex Conditional Array Validation\n\nSometimes you may want to validate a field based on another field in the same nested array whose index you do not know. In these situations, you may allow your closure to receive a second argument which will be the current individual item in the array being validated:\n\n```php\n$input = [\n    'channels' => [\n        [\n            'type' => 'email',\n            'address' => 'abigail@example.com',\n        ],\n        [\n            'type' => 'url',\n            'address' => 'https://example.com',\n        ],\n    ],\n];\n\n$validator->sometimes('channels.*.address', 'email', function (Fluent $input, Fluent $item) {\n    return $item->type === 'email';\n});\n\n$validator->sometimes('channels.*.address', 'url', function (Fluent $input, Fluent $item) {\n    return $item->type !== 'email';\n});\n```\n\nLike the `$input` parameter passed to the closure, the `$item` parameter is an instance of `Illuminate\\Support\\Fluent` when the attribute data is an array; otherwise, it is a string.\n\n---\n\n## Sail Installation and Setup Installing Sail Into Existing Applications Using Devcontainers\nSail | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/sail.md\n\n\n#### Using Devcontainers\n\nIf you would like to develop within a [Devcontainer](https://code.visualstudio.com/docs/remote/containers), you may provide the `--devcontainer` option to the `sail:install` command. The `--devcontainer` option will instruct the `sail:install` command to publish a default `.devcontainer/devcontainer.json ` file to the root of your application:\n\n```shell\nphp artisan sail:install --devcontainer\n```\n\n---\n\n## Database Database Transactions Handling Deadlocks\nDatabase | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/database.md\n\n\n#### Handling Deadlocks\n\nThe `transaction` method accepts an optional second argument which defines the number of times a transaction should be retried when a deadlock occurs. Once these attempts have been exhausted, an exception will be thrown:\n\n```php\nuse Illuminate\\Support\\Facades\\DB;\n\nDB::transaction(function () {\n    DB::update('update users set votes = 1');\n\n    DB::delete('delete from posts');\n}, attempts: 5);\n```\n\n---\n\n## Session Introduction Driver Prerequisites Database\nSession | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/session.md\n\n\n#### Database\n\nWhen using the `database` session driver, you will need to ensure that you have a database table to contain the session data. Typically, this is included in Laravel's default `0001_01_01_000000_create_users_table.php` [database migration](/docs/{{version}}/migrations); however, if for any reason you do not have a `sessions` table, you may use the `make:session-table` Artisan command to generate this migration:\n\n```shell\nphp artisan make:session-table\n\nphp artisan migrate\n```\n\n---\n\n## Sail Installation and Setup Installing Sail Into Existing Applications Adding Additional Services\nSail | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/sail.md\n\n\n#### Adding Additional Services\n\nIf you would like to add an additional service to your existing Sail installation, you may run the `sail:add` Artisan command:\n\n```shell\nphp artisan sail:add\n```\n\n---\n\n## Validation Available Validation Rules array\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### array\n\nThe field under validation must be a PHP `array`.\n\nWhen additional values are provided to the `array` rule, each key in the input array must be present within the list of values provided to the rule. In the following example, the `admin` key in the input array is invalid since it is not contained in the list of values provided to the `array` rule:\n\n```php\nuse Illuminate\\Support\\Facades\\Validator;\n\n$input = [\n    'user' => [\n        'name' => 'Taylor Otwell',\n        'username' => 'taylorotwell',\n        'admin' => true,\n    ],\n];\n\nValidator::make($input, [\n    'user' => 'array:name,username',\n]);\n```\n\nIn general, you should always specify the array keys that are allowed to be present within your array.\n\n---\n\n## Validation Available Validation Rules in_array:_anotherfield_.*\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### in_array:_anotherfield_.*\n\nThe field under validation must exist in _anotherfield_'s values.\n\n---\n\n## Eloquent Relationships Querying Relations Querying Relationship Existence\nEloquent Relationships | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent-relationships.md\n\n\n### Querying Relationship Existence\n\nWhen retrieving model records, you may wish to limit your results based on the existence of a relationship. For example, imagine you want to retrieve all blog posts that have at least one comment. To do so, you may pass the name of the relationship to the `has` and `orHas` methods:\n\n```php\nuse App\\Models\\Post;\n\n// Retrieve all posts that have at least one comment...\n$posts = Post::has('comments')->get();\n```\n\nYou may also specify an operator and count value to further customize the query:\n\n```php\n// Retrieve all posts that have three or more comments...\n$posts = Post::has('comments', '>=', 3)->get();\n```\n\nNested `has` statements may be constructed using \"dot\" notation. For example, you may retrieve all posts that have at least one comment that has at least one image:\n\n```php\n// Retrieve posts that have at least one comment with images...\n$posts = Post::has('comments.images')->get();\n```\n\nIf you need even more power, you may use the `whereHas` and `orWhereHas` methods to define additional query constraints on your `has` queries, such as inspecting the content of a comment:\n\n```php\nuse Illuminate\\Database\\Eloquent\\Builder;\n\n// Retrieve posts with at least one comment containing words like code%...\n$posts = Post::whereHas('comments', function (Builder $query) {\n    $query->where('content', 'like', 'code%');\n})->get();\n\n// Retrieve posts with at least ten comments containing words like code%...\n$posts = Post::whereHas('comments', function (Builder $query) {\n    $query->where('content', 'like', 'code%');\n}, '>=', 10)->get();\n```\n\n> [!WARNING]\n> Eloquent does not currently support querying for relationship existence across databases. The relationships must exist within the same database.\n\n---\n\n## Search Laravel Scout Database Engine\nSearch | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/search.md\n\n\n### Database Engine\n\nScout's built-in database engine performs full-text and `LIKE` searches against your existing database — no external service or extra infrastructure required. Simply add the `Searchable` trait to your model and define a `toSearchableArray` method that returns the columns you want to be searchable.\n\nYou may use PHP attributes to control the search strategy for each column. `SearchUsingFullText` will use your database's full-text index, `SearchUsingPrefix` will only match from the beginning of the string (`example%`), and any columns without an attribute use a default `LIKE` strategy with wildcards on both sides (`%example%`):\n\n```php\n<?php\n\nnamespace App\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Laravel\\Scout\\Attributes\\SearchUsingFullText;\nuse Laravel\\Scout\\Attributes\\SearchUsingPrefix;\nuse Laravel\\Scout\\Searchable;\n\nclass Article extends Model\n{\n    use Searchable;\n\n    #[SearchUsingPrefix(['id'])]\n    #[SearchUsingFullText(['title', 'body'])]\n    public function toSearchableArray(): array\n    {\n        return [\n            'id' => $this->id,\n            'title' => $this->title,\n            'body' => $this->body,\n        ];\n    }\n}\n```\n\n> [!WARNING]\n> Before specifying that a column should use full-text query constraints, ensure that the column has been assigned a [full-text index](/docs/{{version}}/migrations#available-index-types).\n\nOnce the trait is added, you may search your model using Scout's `search` method. Scout's database engine will automatically order results by relevance, even on PostgreSQL:\n\n```php\n$articles = Article::search('Laravel')->get();\n```\n\nThe database engine is a great choice when your search needs are moderate and you want the convenience of Scout's automatic index syncing without deploying an external service. It handles the most common search use cases well, including filtering, pagination, and soft-deleted record handling. For complete details, consult the [Scout documentation](/docs/{{version}}/scout#database-engine).\n\n---\n\n## Sail Installation and Setup Installing Sail Into Existing Applications\nSail | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/sail.md\n\n\n### Installing Sail Into Existing Applications\n\nIf you are interested in using Sail with an existing Laravel application, you may simply install Sail using the Composer package manager. Of course, these steps assume that your existing local development environment allows you to install Composer dependencies:\n\n```shell\ncomposer require laravel/sail --dev\n```\n\nAfter Sail has been installed, you may run the `sail:install` Artisan command. This command will publish Sail's `compose.yaml` file to the root of your application and modify your `.env` file with the required environment variables in order to connect to the Docker services:\n\n```shell\nphp artisan sail:install\n```\n\nFinally, you may start Sail. To continue learning how to use Sail, please continue reading the remainder of this documentation:\n\n```shell\n./vendor/bin/sail up\n```\n\n> [!WARNING]\n> If you are using Docker Desktop for Linux, you should use the `default` Docker context by executing the following command: `docker context use default`. In addition, if you encounter file permission errors within containers, you may need to set the `SUPERVISOR_PHP_USER` environment variable to `root`.\n\n---\n\n## Queries Advanced Where Clauses Where Exists Clauses\nQueries | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/queries.md\n\n\n### Where Exists Clauses\n\nThe `whereExists` method allows you to write \"where exists\" SQL clauses. The `whereExists` method accepts a closure which will receive a query builder instance, allowing you to define the query that should be placed inside of the \"exists\" clause:\n\n```php\n$users = DB::table('users')\n    ->whereExists(function (Builder $query) {\n        $query->select(DB::raw(1))\n            ->from('orders')\n            ->whereColumn('orders.user_id', 'users.id');\n    })\n    ->get();\n```\n\nAlternatively, you may provide a query object to the `whereExists` method instead of a closure:\n\n```php\n$orders = DB::table('orders')\n    ->select(DB::raw(1))\n    ->whereColumn('orders.user_id', 'users.id');\n\n$users = DB::table('users')\n    ->whereExists($orders)\n    ->get();\n```\n\nBoth of the examples above will produce the following SQL:\n\n```sql\nselect * from users\nwhere exists (\n    select 1\n    from orders\n    where orders.user_id = users.id\n)\n```\n\n---\n\n## Database Running SQL Queries Implicit Commits\nDatabase | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/database.md\n\n\n#### Implicit Commits\n\nWhen using the `DB` facade's `statement` and `unprepared` methods within transactions you must be careful to avoid statements that cause [implicit commits](https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html). These statements will cause the database engine to indirectly commit the entire transaction, leaving Laravel unaware of the database's transaction level. An example of such a statement is creating a database table:\n\n```php\nDB::unprepared('create table a (col varchar(1) null)');\n```\n\nPlease refer to the MySQL manual for [a list of all statements](https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html) that trigger implicit commits.\n\n_[Truncated]_"}]


sed -n '1,260p' app/Models/ShippingZone.php

<?php

namespace App\Models;

use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class ShippingZone extends Model
{
    /** @use HasFactory<\Database\Factories\ShippingZoneFactory> */
    use BelongsToStore, HasFactory;

    public $timestamps = false;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'name',
        'countries_json',
        'regions_json',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'countries_json' => '[]',
        'regions_json' => '[]',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return HasMany<ShippingRate, $this>
     */
    public function rates(): HasMany
    {
        return $this->hasMany(ShippingRate::class, 'zone_id');
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'countries_json' => 'array',
            'regions_json' => 'array',
        ];
    }
}


sed -n '1,260p' app/Models/ShippingRate.php

<?php

namespace App\Models;

use App\Enums\ShippingRateType;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class ShippingRate extends Model
{
    /** @use HasFactory<\Database\Factories\ShippingRateFactory> */
    use HasFactory;

    public $timestamps = false;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'zone_id',
        'name',
        'type',
        'config_json',
        'is_active',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'type' => 'flat',
        'config_json' => '{}',
        'is_active' => true,
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if (! $store instanceof Store) {
                return;
            }

            $builder->whereHas('zone', function (Builder $query) use ($store): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $store->getKey());
            });
        });
    }

    /**
     * @return BelongsTo<ShippingZone, $this>
     */
    public function zone(): BelongsTo
    {
        return $this->belongsTo(ShippingZone::class, 'zone_id');
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'type' => ShippingRateType::class,
            'config_json' => 'array',
            'is_active' => 'bool',
        ];
    }
}


sed -n '2280,2420p' specs/02-API-ROUTES.md

#### Admin Shipping Endpoint Summary

| Method | Path | Auth Scope | Description | Rate Limit |
|--------|------|------------|-------------|------------|
| GET | `/api/admin/v1/stores/{storeId}/shipping/zones` | `read-settings` | List shipping zones with rates | `api.admin` (60/min) |
| POST | `/api/admin/v1/stores/{storeId}/shipping/zones` | `write-settings` | Create a shipping zone | `api.admin` (60/min) |
| PUT | `/api/admin/v1/stores/{storeId}/shipping/zones/{zoneId}` | `write-settings` | Update a shipping zone | `api.admin` (60/min) |
| POST | `/api/admin/v1/stores/{storeId}/shipping/zones/{zoneId}/rates` | `write-settings` | Add a shipping rate to a zone | `api.admin` (60/min) |

#### GET /api/admin/v1/stores/{storeId}/shipping/zones

List shipping zones with their rates.

**Success response:** `200 OK`

```json
{
  "data": [
    {
      "id": 1,
      "store_id": 1,
      "name": "Germany",
      "countries_json": ["DE"],
      "regions_json": [],
      "rates": [
        {
          "id": 1,
          "name": "Standard Shipping",
          "type": "flat",
          "config_json": {
            "price_amount": 500,
            "currency": "EUR"
          },
          "is_active": true
        },
        {
          "id": 3,
          "name": "Weight-Based Shipping",
          "type": "weight",
          "config_json": {
            "currency": "EUR",
            "tiers": [
              { "min_weight_g": 0, "max_weight_g": 1000, "price_amount": 500 },
              { "min_weight_g": 1001, "max_weight_g": 5000, "price_amount": 800 },
              { "min_weight_g": 5001, "max_weight_g": null, "price_amount": 1200 }
            ]
          },
          "is_active": true
        }
      ]
    }
  ]
}
```

---

#### POST /api/admin/v1/stores/{storeId}/shipping/zones

Create a shipping zone.

**Request body:**

```json
{
  "name": "Europe (EU)",
  "countries_json": ["DE", "FR", "NL", "BE", "AT", "IT", "ES"],
  "regions_json": []
}
```

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `name` | string | Yes | Max: 255 |
| `countries_json` | array of strings | Yes | Valid ISO 3166-1 alpha-2 codes. At least one country. |
| `regions_json` | array of strings | No | Region/province codes for more granular zones. |

**Success response:** `201 Created`

**Error responses:**
- `403` - Insufficient permissions
- `422` - Validation errors (overlapping countries with existing zones)

---

#### PUT /api/admin/v1/stores/{storeId}/shipping/zones/{zoneId}

Update a shipping zone.

Same fields as POST.

**Success response:** `200 OK`

**Error responses:**
- `403` - Insufficient permissions
- `404` - Zone not found
- `422` - Validation errors

---

#### POST /api/admin/v1/stores/{storeId}/shipping/zones/{zoneId}/rates

Add a shipping rate to a zone.

**Request body:**

```json
{
  "name": "Express Shipping",
  "type": "flat",
  "config_json": {
    "price_amount": 1200,
    "currency": "EUR"
  },
  "is_active": true
}
```

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `name` | string | Yes | Max: 255 |
| `type` | string | Yes | One of: `flat`, `weight`, `price`, `carrier`. |
| `config_json` | object | Yes | Schema depends on type. |
| `is_active` | boolean | No | Default: true. |

**Success response:** `201 Created`

**Error responses:**
- `403` - Insufficient permissions
- `404` - Zone not found
- `422` - Validation errors

---

### 3.7 Tax Settings

#### Admin Tax Endpoint Summary

| Method | Path | Auth Scope | Description | Rate Limit |
|--------|------|------------|-------------|------------|
| GET | `/api/admin/v1/stores/{storeId}/tax/settings` | `read-settings` | Get tax configuration | `api.admin` (60/min) |


Output:
Chunk ID: 469009
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 936
Output:
#### Admin Shipping Endpoint Summary

| Method | Path | Auth Scope | Description | Rate Limit |
|--------|------|------------|-------------|------------|
| GET | `/api/admin/v1/stores/{storeId}/shipping/zones` | `read-settings` | List shipping zones with rates | `api.admin` (60/min) |
| POST | `/api/admin/v1/stores/{storeId}/shipping/zones` | `write-settings` | Create a shipping zone | `api.admin` (60/min) |
| PUT | `/api/admin/v1/stores/{storeId}/shipping/zones/{zoneId}` | `write-settings` | Update a shipping zone | `api.admin` (60/min) |
| POST | `/api/admin/v1/stores/{storeId}/shipping/zones/{zoneId}/rates` | `write-settings` | Add a shipping rate to a zone | `api.admin` (60/min) |

#### GET /api/admin/v1/stores/{storeId}/shipping/zones

List shipping zones with their rates.

**Success response:** `200 OK`

```json
{
  "data": [
    {
      "id": 1,
      "store_id": 1,
      "name": "Germany",
      "countries_json": ["DE"],
      "regions_json": [],
      "rates": [
        {
          "id": 1,
          "name": "Standard Shipping",
          "type": "flat",
          "config_json": {
            "price_amount": 500,
            "currency": "EUR"
          },
          "is_active": true
        },
        {
          "id": 3,
          "name": "Weight-Based Shipping",
          "type": "weight",
          "config_json": {
            "currency": "EUR",
            "tiers": [
              { "min_weight_g": 0, "max_weight_g": 1000, "price_amount": 500 },
              { "min_weight_g": 1001, "max_weight_g": 5000, "price_amount": 800 },
              { "min_weight_g": 5001, "max_weight_g": null, "price_amount": 1200 }
            ]
          },
          "is_active": true
        }
      ]
    }
  ]
}
```

---

#### POST /api/admin/v1/stores/{storeId}/shipping/zones

Create a shipping zone.

**Request body:**

```json
{
  "name": "Europe (EU)",
  "countries_json": ["DE", "FR", "NL", "BE", "AT", "IT", "ES"],
  "regions_json": []
}
```

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `name` | string | Yes | Max: 255 |
| `countries_json` | array of strings | Yes | Valid ISO 3166-1 alpha-2 codes. At least one country. |
| `regions_json` | array of strings | No | Region/province codes for more granular zones. |

**Success response:** `201 Created`

**Error responses:**
- `403` - Insufficient permissions
- `422` - Validation errors (overlapping countries with existing zones)

---

#### PUT /api/admin/v1/stores/{storeId}/shipping/zones/{zoneId}

Update a shipping zone.

Same fields as POST.

**Success response:** `200 OK`

**Error responses:**
- `403` - Insufficient permissions
- `404` - Zone not found
- `422` - Validation errors

---

#### POST /api/admin/v1/stores/{storeId}/shipping/zones/{zoneId}/rates

Add a shipping rate to a zone.

**Request body:**

```json
{
  "name": "Express Shipping",
  "type": "flat",
  "config_json": {
    "price_amount": 1200,
    "currency": "EUR"
  },
  "is_active": true
}
```

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `name` | string | Yes | Max: 255 |
| `type` | string | Yes | One of: `flat`, `weight`, `price`, `carrier`. |
| `config_json` | object | Yes | Schema depends on type. |
| `is_active` | boolean | No | Default: true. |

**Success response:** `201 Created`

**Error responses:**
- `403` - Insufficient permissions
- `404` - Zone not found
- `422` - Validation errors

---

### 3.7 Tax Settings

#### Admin Tax Endpoint Summary

| Method | Path | Auth Scope | Description | Rate Limit |
|--------|------|------------|-------------|------------|
| GET | `/api/admin/v1/stores/{storeId}/tax/settings` | `read-settings` | Get tax configuration | `api.admin` (60/min) |


Output:
Chunk ID: 80ed87
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 326
Output:
<?php

namespace App\Models;

use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class ShippingZone extends Model
{
    /** @use HasFactory<\Database\Factories\ShippingZoneFactory> */
    use BelongsToStore, HasFactory;

    public $timestamps = false;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'name',
        'countries_json',
        'regions_json',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'countries_json' => '[]',
        'regions_json' => '[]',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return HasMany<ShippingRate, $this>
     */
    public function rates(): HasMany
    {
        return $this->hasMany(ShippingRate::class, 'zone_id');
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'countries_json' => 'array',
            'regions_json' => 'array',
        ];
    }
}


Output:
Chunk ID: 930cb0
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 447
Output:
<?php

namespace App\Models;

use App\Enums\ShippingRateType;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class ShippingRate extends Model
{
    /** @use HasFactory<\Database\Factories\ShippingRateFactory> */
    use HasFactory;

    public $timestamps = false;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'zone_id',
        'name',
        'type',
        'config_json',
        'is_active',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'type' => 'flat',
        'config_json' => '{}',
        'is_active' => true,
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if (! $store instanceof Store) {
                return;
            }

            $builder->whereHas('zone', function (Builder $query) use ($store): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $store->getKey());
            });
        });
    }

    /**
     * @return BelongsTo<ShippingZone, $this>
     */
    public function zone(): BelongsTo
    {
        return $this->belongsTo(ShippingZone::class, 'zone_id');
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'type' => ShippingRateType::class,
            'config_json' => 'array',
            'is_active' => 'bool',
        ];
    }
}


sed -n '1,80p' app/Enums/ShippingRateType.php


sed -n '1,200p' database/factories/ShippingZoneFactory.php


sed -n '1,220p' database/factories/ShippingRateFactory.php


sed -n '1,260p' app/Livewire/Admin/Settings/Shipping.php


sed -n '1,220p' database/seeders/ShippingSeeder.php


sed -n '1,80p' app/Enums/ShippingRateType.php

<?php

namespace App\Enums;

enum ShippingRateType: string
{
    case Flat = 'flat';
    case Weight = 'weight';
    case Price = 'price';
    case Carrier = 'carrier';
}


sed -n '1,200p' database/factories/ShippingZoneFactory.php

<?php

namespace Database\Factories;

use App\Models\Store;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\ShippingZone>
 */
class ShippingZoneFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'store_id' => Store::factory(),
            'name' => fake()->country().' Shipping',
            'countries_json' => ['DE'],
            'regions_json' => [],
        ];
    }
}


sed -n '1,220p' database/factories/ShippingRateFactory.php

<?php

namespace Database\Factories;

use App\Enums\ShippingRateType;
use App\Models\ShippingZone;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\ShippingRate>
 */
class ShippingRateFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'zone_id' => ShippingZone::factory(),
            'name' => 'Standard Shipping',
            'type' => ShippingRateType::Flat,
            'config_json' => ['amount' => 799],
            'is_active' => true,
        ];
    }

    public function weight(): static
    {
        return $this->state(fn (array $attributes): array => [
            'type' => ShippingRateType::Weight,
            'config_json' => [
                'ranges' => [
                    ['min_g' => 0, 'max_g' => 500, 'amount' => 499],
                    ['min_g' => 501, 'max_g' => 2000, 'amount' => 899],
                ],
            ],
        ]);
    }

    public function price(): static
    {
        return $this->state(fn (array $attributes): array => [
            'type' => ShippingRateType::Price,
            'config_json' => [
                'ranges' => [
                    ['min_amount' => 0, 'max_amount' => 7500, 'amount' => 799],
                    ['min_amount' => 7501, 'amount' => 0],
                ],
            ],
        ]);
    }
}


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

namespace App\Enums;

enum ShippingRateType: string
{
    case Flat = 'flat';
    case Weight = 'weight';
    case Price = 'price';
    case Carrier = 'carrier';
}


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

namespace Database\Factories;

use App\Models\Store;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\ShippingZone>
 */
class ShippingZoneFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'store_id' => Store::factory(),
            'name' => fake()->country().' Shipping',
            'countries_json' => ['DE'],
            'regions_json' => [],
        ];
    }
}


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

namespace Database\Factories;

use App\Enums\ShippingRateType;
use App\Models\ShippingZone;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\ShippingRate>
 */
class ShippingRateFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'zone_id' => ShippingZone::factory(),
            'name' => 'Standard Shipping',
            'type' => ShippingRateType::Flat,
            'config_json' => ['amount' => 799],
            'is_active' => true,
        ];
    }

    public function weight(): static
    {
        return $this->state(fn (array $attributes): array => [
            'type' => ShippingRateType::Weight,
            'config_json' => [
                'ranges' => [
                    ['min_g' => 0, 'max_g' => 500, 'amount' => 499],
                    ['min_g' => 501, 'max_g' => 2000, 'amount' => 899],
                ],
            ],
        ]);
    }

    public function price(): static
    {
        return $this->state(fn (array $attributes): array => [
            'type' => ShippingRateType::Price,
            'config_json' => [
                'ranges' => [
                    ['min_amount' => 0, 'max_amount' => 7500, 'amount' => 799],
                    ['min_amount' => 7501, 'amount' => 0],
                ],
            ],
        ]);
    }
}


sed -n '1,260p' app/Livewire/Admin/Settings/Shipping.php

<?php

namespace App\Livewire\Admin\Settings;

use App\Enums\ShippingRateType;
use App\Models\ShippingRate;
use App\Models\ShippingZone;
use App\Models\Store;
use App\Services\ShippingCalculator;
use App\Support\Money;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Collection;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Locked;
use Livewire\Component;

class Shipping extends Component
{
    use AuthorizesRequests;

    #[Locked]
    public int $storeId;

    public ?int $editingZoneId = null;

    public string $zoneName = '';

    public string $zoneCountries = '';

    public ?int $rateZoneId = null;

    public ?int $editingRateId = null;

    public string $rateName = '';

    public string $rateType = 'flat';

    public string $rateAmount = '0.00';

    public string $minimumWeight = '0';

    public string $maximumWeight = '';

    public string $minimumOrderAmount = '0.00';

    public string $maximumOrderAmount = '';

    public bool $rateActive = true;

    public string $testCountry = 'DE';

    public string $testRegion = '';

    /**
     * @var array<string, mixed>|null
     */
    public ?array $testResult = null;

    public function mount(): void
    {
        $store = $this->store();

        $this->authorize('update', $store);

        $this->storeId = $store->getKey();
    }

    public function editZone(int $zoneId): void
    {
        $zone = $this->zone($zoneId);

        $this->authorize('update', $this->scopedStore());

        $this->editingZoneId = $zone->getKey();
        $this->zoneName = $zone->name;
        $this->zoneCountries = implode(', ', $zone->countries_json ?? []);
    }

    public function saveZone(): void
    {
        $store = $this->scopedStore();

        $this->authorize('update', $store);

        $validated = $this->validate([
            'zoneName' => ['required', 'string', 'max:255'],
            'zoneCountries' => ['required', 'string', 'max:255'],
        ], [], [
            'zoneName' => 'zone name',
            'zoneCountries' => 'countries',
        ]);

        $countries = $this->countryCodes($validated['zoneCountries']);

        if ($countries === []) {
            $this->addError('zoneCountries', __('Enter at least one ISO country code.'));

            return;
        }

        $payload = [
            'store_id' => $store->getKey(),
            'name' => $validated['zoneName'],
            'countries_json' => $countries,
            'regions_json' => [],
        ];

        $this->editingZoneId
            ? $this->zone($this->editingZoneId)->update($payload)
            : ShippingZone::withoutGlobalScopes()->create($payload);

        $this->resetZoneForm();

        session()->flash('status', 'Shipping zone saved');
        $this->dispatch('toast', type: 'success', message: __('Shipping zone saved'));
    }

    public function deleteZone(int $zoneId): void
    {
        $this->authorize('update', $this->scopedStore());

        $this->zone($zoneId)->delete();
        $this->resetZoneForm();
        $this->resetRateForm();

        session()->flash('status', 'Shipping zone deleted');
    }

    public function addRate(int $zoneId): void
    {
        $zone = $this->zone($zoneId);

        $this->authorize('update', $this->scopedStore());

        $this->resetRateForm();
        $this->rateZoneId = $zone->getKey();
    }

    public function editRate(int $rateId): void
    {
        $rate = $this->rate($rateId);

        $this->authorize('update', $this->scopedStore());

        $this->editingRateId = $rate->getKey();
        $this->rateZoneId = $rate->zone_id;
        $this->rateName = $rate->name;
        $this->rateType = $rate->type->value;
        $this->rateActive = $rate->is_active;
        $this->fillRateConfig($rate);
    }

    public function saveRate(): void
    {
        $store = $this->scopedStore();

        $this->authorize('update', $store);

        $validated = $this->validate([
            'rateZoneId' => ['required', 'integer'],
            'rateName' => ['required', 'string', 'max:255'],
            'rateType' => ['required', Rule::in(array_column(ShippingRateType::cases(), 'value'))],
            'rateAmount' => ['required', 'numeric', 'min:0'],
            'minimumWeight' => ['nullable', 'integer', 'min:0'],
            'maximumWeight' => ['nullable', 'integer', 'min:1'],
            'minimumOrderAmount' => ['nullable', 'numeric', 'min:0'],
            'maximumOrderAmount' => ['nullable', 'numeric', 'min:0'],
            'rateActive' => ['boolean'],
        ], [], [
            'rateZoneId' => 'shipping zone',
            'rateName' => 'rate name',
            'rateType' => 'rate type',
            'rateAmount' => 'rate amount',
        ]);

        $zone = $this->zone((int) $validated['rateZoneId']);
        $payload = [
            'zone_id' => $zone->getKey(),
            'name' => $validated['rateName'],
            'type' => ShippingRateType::from($validated['rateType']),
            'config_json' => $this->rateConfig(),
            'is_active' => $this->rateActive,
        ];

        $this->editingRateId
            ? $this->rate($this->editingRateId)->update($payload)
            : ShippingRate::withoutGlobalScopes()->create($payload);

        $this->resetRateForm();

        session()->flash('status', 'Shipping rate saved');
        $this->dispatch('toast', type: 'success', message: __('Shipping rate saved'));
    }

    public function deleteRate(int $rateId): void
    {
        $this->authorize('update', $this->scopedStore());

        $this->rate($rateId)->delete();
        $this->resetRateForm();

        session()->flash('status', 'Shipping rate deleted');
    }

    public function toggleRateActive(int $rateId): void
    {
        $this->authorize('update', $this->scopedStore());

        $rate = $this->rate($rateId);
        $rate->forceFill(['is_active' => ! $rate->is_active])->save();
    }

    public function testShippingAddress(ShippingCalculator $shipping): void
    {
        $store = $this->scopedStore();

        $this->authorize('update', $store);

        $address = [
            'country' => strtoupper($this->testCountry),
            'province_code' => strtoupper($this->testRegion),
        ];
        $zone = $shipping->matchingZone($store, $address);

        $this->testResult = [
            'zone' => $zone?->name,
            'rates' => $zone instanceof ShippingZone
                ? ShippingRate::withoutGlobalScopes()
                    ->where('zone_id', $zone->getKey())
                    ->where('is_active', true)
                    ->orderBy('id')
                    ->get()
                    ->map(fn (ShippingRate $rate): string => $rate->name.' - '.$this->rateSummary($rate))
                    ->all()
                : [],
        ];
    }

    /**
     * @return Collection<int, ShippingZone>
     */
    public function zones(): Collection
    {
        return ShippingZone::withoutGlobalScopes()
            ->with(['rates' => fn ($query) => $query->withoutGlobalScopes()->orderBy('id')])
            ->where('store_id', $this->storeId)
            ->orderBy('id')
            ->get();
    }

    public function rateSummary(ShippingRate $rate): string
    {
        if ($rate->type === ShippingRateType::Carrier) {
            return 'Carrier fallback '.Money::format((int) data_get($rate->config_json, 'amount', 0), $this->scopedStore()->default_currency);
        }

        $amount = match ($rate->type) {
            ShippingRateType::Flat => (int) data_get($rate->config_json, 'amount', 0),
            default => (int) data_get($rate->config_json, 'ranges.0.amount', 0),
        };


Output:
Chunk ID: 63fec5
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1925
Output:
<?php

namespace App\Livewire\Admin\Settings;

use App\Enums\ShippingRateType;
use App\Models\ShippingRate;
use App\Models\ShippingZone;
use App\Models\Store;
use App\Services\ShippingCalculator;
use App\Support\Money;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Collection;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Locked;
use Livewire\Component;

class Shipping extends Component
{
    use AuthorizesRequests;

    #[Locked]
    public int $storeId;

    public ?int $editingZoneId = null;

    public string $zoneName = '';

    public string $zoneCountries = '';

    public ?int $rateZoneId = null;

    public ?int $editingRateId = null;

    public string $rateName = '';

    public string $rateType = 'flat';

    public string $rateAmount = '0.00';

    public string $minimumWeight = '0';

    public string $maximumWeight = '';

    public string $minimumOrderAmount = '0.00';

    public string $maximumOrderAmount = '';

    public bool $rateActive = true;

    public string $testCountry = 'DE';

    public string $testRegion = '';

    /**
     * @var array<string, mixed>|null
     */
    public ?array $testResult = null;

    public function mount(): void
    {
        $store = $this->store();

        $this->authorize('update', $store);

        $this->storeId = $store->getKey();
    }

    public function editZone(int $zoneId): void
    {
        $zone = $this->zone($zoneId);

        $this->authorize('update', $this->scopedStore());

        $this->editingZoneId = $zone->getKey();
        $this->zoneName = $zone->name;
        $this->zoneCountries = implode(', ', $zone->countries_json ?? []);
    }

    public function saveZone(): void
    {
        $store = $this->scopedStore();

        $this->authorize('update', $store);

        $validated = $this->validate([
            'zoneName' => ['required', 'string', 'max:255'],
            'zoneCountries' => ['required', 'string', 'max:255'],
        ], [], [
            'zoneName' => 'zone name',
            'zoneCountries' => 'countries',
        ]);

        $countries = $this->countryCodes($validated['zoneCountries']);

        if ($countries === []) {
            $this->addError('zoneCountries', __('Enter at least one ISO country code.'));

            return;
        }

        $payload = [
            'store_id' => $store->getKey(),
            'name' => $validated['zoneName'],
            'countries_json' => $countries,
            'regions_json' => [],
        ];

        $this->editingZoneId
            ? $this->zone($this->editingZoneId)->update($payload)
            : ShippingZone::withoutGlobalScopes()->create($payload);

        $this->resetZoneForm();

        session()->flash('status', 'Shipping zone saved');
        $this->dispatch('toast', type: 'success', message: __('Shipping zone saved'));
    }

    public function deleteZone(int $zoneId): void
    {
        $this->authorize('update', $this->scopedStore());

        $this->zone($zoneId)->delete();
        $this->resetZoneForm();
        $this->resetRateForm();

        session()->flash('status', 'Shipping zone deleted');
    }

    public function addRate(int $zoneId): void
    {
        $zone = $this->zone($zoneId);

        $this->authorize('update', $this->scopedStore());

        $this->resetRateForm();
        $this->rateZoneId = $zone->getKey();
    }

    public function editRate(int $rateId): void
    {
        $rate = $this->rate($rateId);

        $this->authorize('update', $this->scopedStore());

        $this->editingRateId = $rate->getKey();
        $this->rateZoneId = $rate->zone_id;
        $this->rateName = $rate->name;
        $this->rateType = $rate->type->value;
        $this->rateActive = $rate->is_active;
        $this->fillRateConfig($rate);
    }

    public function saveRate(): void
    {
        $store = $this->scopedStore();

        $this->authorize('update', $store);

        $validated = $this->validate([
            'rateZoneId' => ['required', 'integer'],
            'rateName' => ['required', 'string', 'max:255'],
            'rateType' => ['required', Rule::in(array_column(ShippingRateType::cases(), 'value'))],
            'rateAmount' => ['required', 'numeric', 'min:0'],
            'minimumWeight' => ['nullable', 'integer', 'min:0'],
            'maximumWeight' => ['nullable', 'integer', 'min:1'],
            'minimumOrderAmount' => ['nullable', 'numeric', 'min:0'],
            'maximumOrderAmount' => ['nullable', 'numeric', 'min:0'],
            'rateActive' => ['boolean'],
        ], [], [
            'rateZoneId' => 'shipping zone',
            'rateName' => 'rate name',
            'rateType' => 'rate type',
            'rateAmount' => 'rate amount',
        ]);

        $zone = $this->zone((int) $validated['rateZoneId']);
        $payload = [
            'zone_id' => $zone->getKey(),
            'name' => $validated['rateName'],
            'type' => ShippingRateType::from($validated['rateType']),
            'config_json' => $this->rateConfig(),
            'is_active' => $this->rateActive,
        ];

        $this->editingRateId
            ? $this->rate($this->editingRateId)->update($payload)
            : ShippingRate::withoutGlobalScopes()->create($payload);

        $this->resetRateForm();

        session()->flash('status', 'Shipping rate saved');
        $this->dispatch('toast', type: 'success', message: __('Shipping rate saved'));
    }

    public function deleteRate(int $rateId): void
    {
        $this->authorize('update', $this->scopedStore());

        $this->rate($rateId)->delete();
        $this->resetRateForm();

        session()->flash('status', 'Shipping rate deleted');
    }

    public function toggleRateActive(int $rateId): void
    {
        $this->authorize('update', $this->scopedStore());

        $rate = $this->rate($rateId);
        $rate->forceFill(['is_active' => ! $rate->is_active])->save();
    }

    public function testShippingAddress(ShippingCalculator $shipping): void
    {
        $store = $this->scopedStore();

        $this->authorize('update', $store);

        $address = [
            'country' => strtoupper($this->testCountry),
            'province_code' => strtoupper($this->testRegion),
        ];
        $zone = $shipping->matchingZone($store, $address);

        $this->testResult = [
            'zone' => $zone?->name,
            'rates' => $zone instanceof ShippingZone
                ? ShippingRate::withoutGlobalScopes()
                    ->where('zone_id', $zone->getKey())
                    ->where('is_active', true)
                    ->orderBy('id')
                    ->get()
                    ->map(fn (ShippingRate $rate): string => $rate->name.' - '.$this->rateSummary($rate))
                    ->all()
                : [],
        ];
    }

    /**
     * @return Collection<int, ShippingZone>
     */
    public function zones(): Collection
    {
        return ShippingZone::withoutGlobalScopes()
            ->with(['rates' => fn ($query) => $query->withoutGlobalScopes()->orderBy('id')])
            ->where('store_id', $this->storeId)
            ->orderBy('id')
            ->get();
    }

    public function rateSummary(ShippingRate $rate): string
    {
        if ($rate->type === ShippingRateType::Carrier) {
            return 'Carrier fallback '.Money::format((int) data_get($rate->config_json, 'amount', 0), $this->scopedStore()->default_currency);
        }

        $amount = match ($rate->type) {
            ShippingRateType::Flat => (int) data_get($rate->config_json, 'amount', 0),
            default => (int) data_get($rate->config_json, 'ranges.0.amount', 0),
        };


sed -n '1,220p' database/seeders/ShippingSeeder.php

sed: database/seeders/ShippingSeeder.php: No such file or directory


Output:
Chunk ID: b2b07e
Wall time: 0.0000 seconds
Process exited with code 1
Original token count: 17
Output:
sed: database/seeders/ShippingSeeder.php: No such file or directory


sed -n '260,520p' app/Livewire/Admin/Settings/Shipping.php