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

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

# Pest Testing 4

## Documentation

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

## Basic Usage

### Creating Tests

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

### Test Organization

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

### Basic Test Structure

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

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

### Running Tests

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

## Assertions

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

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

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

## Mocking

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

## Datasets

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

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

## Pest 4 Features

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

### Browser Test Example

Browser tests run in real browsers for full integration testing:

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

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

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

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

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

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

### Smoke Testing

Quickly validate multiple pages have no JavaScript errors:

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

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

### Visual Regression Testing

Capture and compare screenshots to detect visual changes.

### Test Sharding

Split tests across parallel processes for faster CI runs.

### Architecture Testing

Pest 4 includes architecture testing (from Pest 3):

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

## Common Pitfalls

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


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

# Pest Testing 4

## Documentation

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

## Basic Usage

### Creating Tests

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

### Test Organization

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

### Basic Test Structure

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

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

### Running Tests

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

## Assertions

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

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

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

## Mocking

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

## Datasets

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

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

## Pest 4 Features

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

### Browser Test Example

Browser tests run in real browsers for full integration testing:

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

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

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

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

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

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

### Smoke Testing

Quickly validate multiple pages have no JavaScript errors:

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

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

### Visual Regression Testing

Capture and compare screenshots to detect visual changes.

### Test Sharding

Split tests across parallel processes for faster CI runs.

### Architecture Testing

Pest 4 includes architecture testing (from Pest 3):

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

## Common Pitfalls

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


server: laravel-boost

tool: application-info

type: text

database_engine: sqlite

roster_name: FORTIFY

package_name: laravel/fortify

roster_name: LARAVEL

package_name: laravel/framework

roster_name: PROMPTS

package_name: laravel/prompts

package_name: livewire/flux

roster_name: LIVEWIRE

package_name: livewire/livewire

roster_name: BOOST

package_name: laravel/boost

roster_name: MCP

package_name: laravel/mcp

roster_name: PAIL

package_name: laravel/pail

roster_name: PINT

package_name: laravel/pint

roster_name: SAIL

package_name: laravel/sail

roster_name: PEST

package_name: pestphp/pest

roster_name: PHPUNIT

package_name: phpunit/phpunit

roster_name: TAILWINDCSS

package_name: tailwindcss


packages: laravel/framework pestphp/pest

queries: database seeders update or create testing

queries: pest laravel feature tests database seeding


tool: search-docs

text:
# Search Results

## Database Testing Running Seeders
Database Testing | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/database-testing.md

## Running Seeders

If you would like to use [database seeders](/docs/{{version}}/seeding) to populate your database during a feature test, you may invoke the `seed` method. By default, the `seed` method will execute the `DatabaseSeeder`, which should execute all of your other seeders. Alternatively, you pass a specific seeder class name to the `seed` method:

```php tab=Pest
<?php

use Database\Seeders\OrderStatusSeeder;
use Database\Seeders\TransactionStatusSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;

pest()->use(RefreshDatabase::class);

test('orders can be created', function () {
    // Run the DatabaseSeeder...
    $this->seed();

    // Run a specific seeder...
    $this->seed(OrderStatusSeeder::class);

    // ...

    // Run an array of specific seeders...
    $this->seed([
        OrderStatusSeeder::class,
        TransactionStatusSeeder::class,
        // ...
    ]);
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Database\Seeders\OrderStatusSeeder;
use Database\Seeders\TransactionStatusSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    use RefreshDatabase;

    /**
     * Test creating a new order.
     */
    public function test_orders_can_be_created(): void
    {
        // Run the DatabaseSeeder...
        $this->seed();

        // Run a specific seeder...
        $this->seed(OrderStatusSeeder::class);

        // ...

        // Run an array of specific seeders...
        $this->seed([
            OrderStatusSeeder::class,
            TransactionStatusSeeder::class,
            // ...
        ]);
    }
}
```

Alternatively, you may instruct Laravel to automatically seed the database before each test that uses the `RefreshDatabase` trait. You may accomplish this by defining a `$seed` property on your base test class:

```php
<?php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
    /**
     * Indicates whether the default seeder should run before each test.
     *
     * @var bool
     */
    protected $seed = true;
}
```

When the `$seed` property is `true`, the test will run the `Database\Seeders\DatabaseSeeder` class before each test that uses the `RefreshDatabase` trait. However, you may specify a specific seeder that should be executed by defining a `$seeder` property on your test class:

```php
use Database\Seeders\OrderStatusSeeder;

/**
 * Run a specific seeder before each test.
 *
 * @var string
 */
protected $seeder = OrderStatusSeeder::class;
```

---

## Seeding Writing Seeders Using Model Factories
Seeding | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/seeding.md

### Using Model Factories

Of course, manually specifying the attributes for each model seed is cumbersome. Instead, you can use [model factories](/docs/{{version}}/eloquent-factories) to conveniently generate large amounts of database records. First, review the [model factory documentation](/docs/{{version}}/eloquent-factories) to learn how to define your factories.

For example, let's create 50 users that each has one related post:

```php
use App\Models\User;

/**
 * Run the database seeders.
 */
public function run(): void
{
    User::factory()
        ->count(50)
        ->hasPosts(1)
        ->create();
}
```

---

## Helpers Miscellaneous `fake()` {.collection-method}
Helpers | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/helpers.md

#### `fake()` {.collection-method}

The `fake` function resolves a [Faker](https://github.com/FakerPHP/Faker) singleton from the container, which can be useful when creating fake data in model factories, database seeding, tests, and prototyping views:

```blade
@for ($i = 0; $i < 10; $i++)
    <dl>
        <dt>Name</dt>
        <dd>{{ fake()->name() }}</dd>

        <dt>Email</dt>
        <dd>{{ fake()->unique()->safeEmail() }}</dd>
    </dl>
@endfor
```

By default, the `fake` function will utilize the `app.faker_locale` configuration option in your `config/app.php` configuration. Typically, this configuration option is set via the `APP_FAKER_LOCALE` environment variable. You may also specify the locale by passing it to the `fake` function. Each locale will resolve an individual singleton:

```php
fake('nl_NL')->name()
```

---

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

# Database Testing

- [Introduction](#introduction)
    - [Resetting the Database After Each Test](#resetting-the-database-after-each-test)
- [Model Factories](#model-factories)
- [Running Seeders](#running-seeders)
- [Available Assertions](#available-assertions)

---

## Seeding Writing Seeders
Seeding | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/seeding.md

## Writing Seeders

To generate a seeder, execute the `make:seeder` [Artisan command](/docs/{{version}}/artisan). All seeders generated by the framework will be placed in the `database/seeders` directory:

```shell
php artisan make:seeder UserSeeder
```

A seeder class only contains one method by default: `run`. This method is called when the `db:seed` [Artisan command](/docs/{{version}}/artisan) is executed. Within the `run` method, you may insert data into your database however you wish. You may use the [query builder](/docs/{{version}}/queries) to manually insert data or you may use [Eloquent model factories](/docs/{{version}}/eloquent-factories).

As an example, let's modify the default `DatabaseSeeder` class and add a database insert statement to the `run` method:

```php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;

class DatabaseSeeder extends Seeder
{
    /**
     * Run the database seeders.
     */
    public function run(): void
    {
        DB::table('users')->insert([
            'name' => Str::random(10),
            'email' => Str::random(10).'@example.com',
            'password' => Hash::make('password'),
        ]);
    }
}
```

> [!NOTE]
> You may type-hint any dependencies you need within the `run` method's signature. They will automatically be resolved via the Laravel [service container](/docs/{{version}}/container).

---

## Eloquent Factories Introduction
Eloquent Factories | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent-factories.md

## Introduction

When testing your application or seeding your database, you may need to insert a few records into your database. Instead of manually specifying the value of each column, Laravel allows you to define a set of default attributes for each of your [Eloquent models](/docs/{{version}}/eloquent) using model factories.

To see an example of how to write a factory, take a look at the `database/factories/UserFactory.php` file in your application. This factory is included with all new Laravel applications and contains the following factory definition:

```php
namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
 */
class UserFactory extends Factory
{
    /**
     * The current password being used by the factory.
     */
    protected static ?string $password;

    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'name' => fake()->name(),
            'email' => fake()->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => static::$password ??= Hash::make('password'),
            'remember_token' => Str::random(10),
        ];
    }

    /**
     * Indicate that the model's email address should be unverified.
     */
    public function unverified(): static
    {
        return $this->state(fn (array $attributes) => [
            'email_verified_at' => null,
        ]);
    }
}
```

As you can see, in their most basic form, factories are classes that extend Laravel's base factory class and define a `definition` method. The `definition` method returns the default set of attribute values that should be applied when creating a model using the factory.

Via the `fake` helper, factories have access to the [Faker](https://github.com/FakerPHP/Faker) PHP library, which allows you to conveniently generate various kinds of random data for testing and seeding.

> [!NOTE]
> You can change your application's Faker locale by updating the `faker_locale` option in your `config/app.php` configuration file.

---

## Scout Database / Collection Engines Collection Engine
Scout | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/scout.md

### Collection Engine

The "collection" engine is intended for quick prototypes, extremely small datasets (a few hundred records), or running tests. It retrieves all possible records from your database and uses Laravel's `Str::is` helper to filter them in PHP, so it does not require any indexing or database-specific features. For anything beyond trivial use cases, you should use the [database engine](#database-engine) instead.

To use the collection engine, you may simply set the value of the `SCOUT_DRIVER` environment variable to `collection`, or specify the `collection` driver directly in your application's `scout` configuration file:

```ini
SCOUT_DRIVER=collection
```

Once you have specified the collection driver as your preferred driver, you may start [executing search queries](#searching) against your models. Search engine indexing, such as the indexing needed to seed Algolia, Meilisearch, or Typesense indexes, is unnecessary when using the collection engine.

#### Differences From Database Engine

While the database engine uses full-text indexes and `LIKE` clauses to find matching records efficiently, the collection engine pulls all records and filters them in PHP. The collection engine is the most portable option as it works across all relational databases supported by Laravel (including SQLite and SQL Server); however, it is significantly less efficient than the database engine and should not be used with large datasets.

---

## Seeding Running Seeders Forcing Seeders to Run in Production
Seeding | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/seeding.md

#### Forcing Seeders to Run in Production

Some seeding operations may cause you to alter or lose data. In order to protect you from running seeding commands against your production database, you will be prompted for confirmation before the seeders are executed in the `production` environment. To force the seeders to run without a prompt, use the `--force` flag:

```shell
php artisan db:seed --force
```

---

## Seeding
Seeding | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/seeding.md

# Database: Seeding

- [Introduction](#introduction)
- [Writing Seeders](#writing-seeders)
    - [Using Model Factories](#using-model-factories)
    - [Calling Additional Seeders](#calling-additional-seeders)
    - [Muting Model Events](#muting-model-events)
- [Running Seeders](#running-seeders)

---

## Seeding Running Seeders
Seeding | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/seeding.md

## Running Seeders

You may execute the `db:seed` Artisan command to seed your database. By default, the `db:seed` command runs the `Database\Seeders\DatabaseSeeder` class, which may in turn invoke other seed classes. However, you may use the `--class` option to specify a specific seeder class to run individually:

```shell
php artisan db:seed

php artisan db:seed --class=UserSeeder
```

You may also seed your database using the `migrate:fresh` command in combination with the `--seed` option, which will drop all tables and re-run all of your migrations. This command is useful for completely re-building your database. The `--seeder` option may be used to specify a specific seeder to run:

```shell
php artisan migrate:fresh --seed

php artisan migrate:fresh --seed --seeder=UserSeeder
```

---

## Seeding Writing Seeders Calling Additional Seeders
Seeding | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/seeding.md

### Calling Additional Seeders

Within the `DatabaseSeeder` class, you may use the `call` method to execute additional seed classes. Using the `call` method allows you to break up your database seeding into multiple files so that no single seeder class becomes too large. The `call` method accepts an array of seeder classes that should be executed:

```php
/**
 * Run the database seeders.
 */
public function run(): void
{
    $this->call([
        UserSeeder::class,
        PostSeeder::class,
        CommentSeeder::class,
    ]);
}
```

---

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

### Defining Features Externally

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

```php
<?php

namespace App\Extensions;

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

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

    /* ... */
}
```

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

---

## Pennant Updating Values Purging Features
Pennant | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/pennant.md

### Purging Features

Sometimes, it can be useful to purge an entire feature from storage. This is typically necessary if you have removed the feature from your application or you have made adjustments to the feature's definition that you would like to rollout to all users.

You may remove all stored values for a feature using the `purge` method:

```php
// Purging a single feature...
Feature::purge('new-api');

// Purging multiple features...
Feature::purge(['new-api', 'purchase-button']);
```

If you would like to purge _all_ features from storage, you may invoke the `purge` method without any arguments:

```php
Feature::purge();
```

As it can be useful to purge features as part of your application's deployment pipeline, Pennant includes a `pennant:purge` Artisan command which will purge the provided features from storage:

```shell
php artisan pennant:purge new-api

php artisan pennant:purge new-api purchase-button
```

It is also possible to purge all features _except_ those in a given feature list. For example, imagine you wanted to purge all features but keep the values for the "new-api" and "purchase-button" features in storage. To accomplish this, you can pass those feature names to the `--except` option:

```shell
php artisan pennant:purge --except=new-api --except=purchase-button
```

For convenience, the `pennant:purge` command also supports an `--except-registered` flag. This flag indicates that all features except those explicitly registered in a service provider should be purged:

```shell
php artisan pennant:purge --except-registered
```

---

## Seeding Introduction
Seeding | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/seeding.md

## Introduction

Laravel includes the ability to seed your database with data using seed classes. All seed classes are stored in the `database/seeders` directory. By default, a `DatabaseSeeder` class is defined for you. From this class, you may use the `call` method to run other seed classes, allowing you to control the seeding order.

> [!NOTE]
> [Mass assignment protection](/docs/{{version}}/eloquent#mass-assignment) is automatically disabled during database seeding.

---

## Pennant Checking Features In-Memory Cache
Pennant | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/pennant.md

### In-Memory Cache

When checking a feature, Pennant will create an in-memory cache of the result. If you are using the `database` driver, this means that re-checking the same feature flag within a single request will not trigger additional database queries. This also ensures that the feature has a consistent result for the duration of the request.

If you need to manually flush the in-memory cache, you may use the `flushCache` method offered by the `Feature` facade:

```php
Feature::flushCache();
```

---

## Seeding Writing Seeders Muting Model Events
Seeding | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/seeding.md

### Muting Model Events

While running seeds, you may want to prevent models from dispatching events. You may achieve this using the `WithoutModelEvents` trait. When used, the `WithoutModelEvents` trait ensures no model events are dispatched, even if additional seed classes are executed via the `call` method:

```php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;

class DatabaseSeeder extends Seeder
{
    use WithoutModelEvents;

    /**
     * Run the database seeders.
     */
    public function run(): void
    {
        $this->call([
            UserSeeder::class,
        ]);
    }
}
```

---

## Pennant Retrieving Multiple Features
Pennant | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/pennant.md

## Retrieving Multiple Features

The `values` method allows the retrieval of multiple features for a given scope:

```php
Feature::values(['billing-v2', 'purchase-button']);

// [
//     'billing-v2' => false,
//     'purchase-button' => 'blue-sapphire',
// ]
```

Or, you may use the `all` method to retrieve the values of all defined features for a given scope:

```php
Feature::all();

// [
//     'billing-v2' => false,
//     'purchase-button' => 'blue-sapphire',
//     'site-redesign' => true,
// ]
```

However, class-based features are dynamically registered and are not known by Pennant until they are explicitly checked. This means your application's class-based features may not appear in the results returned by the `all` method if they have not already been checked during the current request.

If you would like to ensure that feature classes are always included when using the `all` method, you may use Pennant's feature discovery capabilities. To get started, invoke the `discover` method in one of your application's service providers:

```php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Feature::discover();

        // ...
    }
}
```

The `discover` method will register all of the feature classes in your application's `app/Features` directory. The `all` method will now include these classes in its results, regardless of whether they have been checked during the current request:

```php
Feature::all();

// [
//     'App\Features\NewApi' => true,
//     'billing-v2' => false,
//     'purchase-button' => 'blue-sapphire',
//     'site-redesign' => true,
// ]
```

---

## Pennant Rich Feature Values
Pennant | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/pennant.md

## Rich Feature Values

Until now, we have primarily shown features as being in a binary state, meaning they are either "active" or "inactive", but Pennant also allows you to store rich values as well.

For example, imagine you are testing three new colors for the "Buy now" button of your application. Instead of returning `true` or `false` from the feature definition, you may instead return a string:

```php
use Illuminate\Support\Arr;
use Laravel\Pennant\Feature;

Feature::define('purchase-button', fn (User $user) => Arr::random([
    'blue-sapphire',
    'seafoam-green',
    'tart-orange',
]));
```

You may retrieve the value of the `purchase-button` feature using the `value` method:

```php
$color = Feature::value('purchase-button');
```

Pennant's included Blade directive also makes it easy to conditionally render content based on the current value of the feature:

```blade
@feature('purchase-button', 'blue-sapphire')
    <!-- 'blue-sapphire' is active -->
@elsefeature('purchase-button', 'seafoam-green')
    <!-- 'seafoam-green' is active -->
@elsefeature('purchase-button', 'tart-orange')
    <!-- 'tart-orange' is active -->
@endfeature
```

> [!NOTE]
> When using rich values, it is important to know that a feature is considered "active" when it has any value other than `false`.

When calling the [conditional `when`](#conditional-execution) method, the feature's rich value will be provided to the first closure:

```php
Feature::when('purchase-button',
    fn ($color) => /* ... */,
    fn () => /* ... */,
);
```

Likewise, when calling the conditional `unless` method, the feature's rich value will be provided to the optional second closure:

```php
Feature::unless('purchase-button',
    fn () => /* ... */,
    fn ($color) => /* ... */,
);
```

---

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

---

## Pennant Checking Features Intercepting Feature Checks
Pennant | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/pennant.md

### Intercepting Feature Checks

Sometimes it can be useful to perform some in-memory checks before retrieving the stored value of a given feature. Imagine you are developing a new API behind a feature flag and want the ability to disable the new API without losing any of the resolved feature values in storage. If you notice a bug in the new API, you could easily disable it for everyone except internal team members, fix the bug, and then re-enable the new API for the users that previously had access to the feature.

You can achieve this with a [class-based feature's](#class-based-features) `before` method. When present, the `before` method is always run in-memory before retrieving the value from storage. If a non-`null` value is returned from the method, it will be used in place of the feature's stored value for the duration of the request:

```php
<?php

namespace App\Features;

use App\Models\User;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Lottery;

class NewApi
{
    /**
     * Run an always-in-memory check before the stored value is retrieved.
     */
    public function before(User $user): mixed
    {
        if (Config::get('features.new-api.disabled')) {
            return $user->isInternalTeamMember();
        }
    }

    /**
     * Resolve the feature's initial value.
     */
    public function resolve(User $user): mixed
    {
        return match (true) {
            $user->isInternalTeamMember() => true,
            $user->isHighTrafficCustomer() => false,
            default => Lottery::odds(1 / 100),
        };
    }
}
```

You could also use this feature to schedule the global rollout of a feature that was previously behind a feature flag:

```php
<?php

namespace App\Features;

use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Config;

class NewApi
{
    /**
     * Run an always-in-memory check before the stored value is retrieved.
     */
    public function before(User $user): mixed
    {
        if (Config::get('features.new-api.disabled')) {
            return $user->isInternalTeamMember();
        }

        if (Carbon::parse(Config::get('features.new-api.rollout-date'))->isPast()) {
            return true;
        }
    }

    // ...
}
```

_[Truncated]_


output:
Wall time: 3.2716 seconds
Output:
[{"type":"text","text":"# Search Results\n\n## Database Testing Running Seeders\nDatabase Testing | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/database-testing.md\n\n\n## Running Seeders\n\nIf you would like to use [database seeders](/docs/{{version}}/seeding) to populate your database during a feature test, you may invoke the `seed` method. By default, the `seed` method will execute the `DatabaseSeeder`, which should execute all of your other seeders. Alternatively, you pass a specific seeder class name to the `seed` method:\n\n```php tab=Pest\n<?php\n\nuse Database\\Seeders\\OrderStatusSeeder;\nuse Database\\Seeders\\TransactionStatusSeeder;\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\n\npest()->use(RefreshDatabase::class);\n\ntest('orders can be created', function () {\n    // Run the DatabaseSeeder...\n    $this->seed();\n\n    // Run a specific seeder...\n    $this->seed(OrderStatusSeeder::class);\n\n    // ...\n\n    // Run an array of specific seeders...\n    $this->seed([\n        OrderStatusSeeder::class,\n        TransactionStatusSeeder::class,\n        // ...\n    ]);\n});\n```\n\n```php tab=PHPUnit\n<?php\n\nnamespace Tests\\Feature;\n\nuse Database\\Seeders\\OrderStatusSeeder;\nuse Database\\Seeders\\TransactionStatusSeeder;\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse Tests\\TestCase;\n\nclass ExampleTest extends TestCase\n{\n    use RefreshDatabase;\n\n    /**\n     * Test creating a new order.\n     */\n    public function test_orders_can_be_created(): void\n    {\n        // Run the DatabaseSeeder...\n        $this->seed();\n\n        // Run a specific seeder...\n        $this->seed(OrderStatusSeeder::class);\n\n        // ...\n\n        // Run an array of specific seeders...\n        $this->seed([\n            OrderStatusSeeder::class,\n            TransactionStatusSeeder::class,\n            // ...\n        ]);\n    }\n}\n```\n\nAlternatively, you may instruct Laravel to automatically seed the database before each test that uses the `RefreshDatabase` trait. You may accomplish this by defining a `$seed` property on your base test class:\n\n```php\n<?php\n\nnamespace Tests;\n\nuse Illuminate\\Foundation\\Testing\\TestCase as BaseTestCase;\n\nabstract class TestCase extends BaseTestCase\n{\n    /**\n     * Indicates whether the default seeder should run before each test.\n     *\n     * @var bool\n     */\n    protected $seed = true;\n}\n```\n\nWhen the `$seed` property is `true`, the test will run the `Database\\Seeders\\DatabaseSeeder` class before each test that uses the `RefreshDatabase` trait. However, you may specify a specific seeder that should be executed by defining a `$seeder` property on your test class:\n\n```php\nuse Database\\Seeders\\OrderStatusSeeder;\n\n/**\n * Run a specific seeder before each test.\n *\n * @var string\n */\nprotected $seeder = OrderStatusSeeder::class;\n```\n\n---\n\n## Seeding Writing Seeders Using Model Factories\nSeeding | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/seeding.md\n\n\n### Using Model Factories\n\nOf course, manually specifying the attributes for each model seed is cumbersome. Instead, you can use [model factories](/docs/{{version}}/eloquent-factories) to conveniently generate large amounts of database records. First, review the [model factory documentation](/docs/{{version}}/eloquent-factories) to learn how to define your factories.\n\nFor example, let's create 50 users that each has one related post:\n\n```php\nuse App\\Models\\User;\n\n/**\n * Run the database seeders.\n */\npublic function run(): void\n{\n    User::factory()\n        ->count(50)\n        ->hasPosts(1)\n        ->create();\n}\n```\n\n---\n\n## Helpers Miscellaneous `fake()` {.collection-method}\nHelpers | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/helpers.md\n\n\n#### `fake()` {.collection-method}\n\nThe `fake` function resolves a [Faker](https://github.com/FakerPHP/Faker) singleton from the container, which can be useful when creating fake data in model factories, database seeding, tests, and prototyping views:\n\n```blade\n@for ($i = 0; $i < 10; $i++)\n    <dl>\n        <dt>Name</dt>\n        <dd>{{ fake()->name() }}</dd>\n\n        <dt>Email</dt>\n        <dd>{{ fake()->unique()->safeEmail() }}</dd>\n    </dl>\n@endfor\n```\n\nBy default, the `fake` function will utilize the `app.faker_locale` configuration option in your `config/app.php` configuration. Typically, this configuration option is set via the `APP_FAKER_LOCALE` environment variable. You may also specify the locale by passing it to the `fake` function. Each locale will resolve an individual singleton:\n\n```php\nfake('nl_NL')->name()\n```\n\n---\n\n## Database Testing\nDatabase Testing | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/database-testing.md\n\n# Database Testing\n\n- [Introduction](#introduction)\n    - [Resetting the Database After Each Test](#resetting-the-database-after-each-test)\n- [Model Factories](#model-factories)\n- [Running Seeders](#running-seeders)\n- [Available Assertions](#available-assertions)\n\n---\n\n## Seeding Writing Seeders\nSeeding | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/seeding.md\n\n\n## Writing Seeders\n\nTo generate a seeder, execute the `make:seeder` [Artisan command](/docs/{{version}}/artisan). All seeders generated by the framework will be placed in the `database/seeders` directory:\n\n```shell\nphp artisan make:seeder UserSeeder\n```\n\nA seeder class only contains one method by default: `run`. This method is called when the `db:seed` [Artisan command](/docs/{{version}}/artisan) is executed. Within the `run` method, you may insert data into your database however you wish. You may use the [query builder](/docs/{{version}}/queries) to manually insert data or you may use [Eloquent model factories](/docs/{{version}}/eloquent-factories).\n\nAs an example, let's modify the default `DatabaseSeeder` class and add a database insert statement to the `run` method:\n\n```php\n<?php\n\nnamespace Database\\Seeders;\n\nuse Illuminate\\Database\\Seeder;\nuse Illuminate\\Support\\Facades\\DB;\nuse Illuminate\\Support\\Facades\\Hash;\nuse Illuminate\\Support\\Str;\n\nclass DatabaseSeeder extends Seeder\n{\n    /**\n     * Run the database seeders.\n     */\n    public function run(): void\n    {\n        DB::table('users')->insert([\n            'name' => Str::random(10),\n            'email' => Str::random(10).'@example.com',\n            'password' => Hash::make('password'),\n        ]);\n    }\n}\n```\n\n> [!NOTE]\n> You may type-hint any dependencies you need within the `run` method's signature. They will automatically be resolved via the Laravel [service container](/docs/{{version}}/container).\n\n---\n\n## Eloquent Factories Introduction\nEloquent Factories | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent-factories.md\n\n\n## Introduction\n\nWhen testing your application or seeding your database, you may need to insert a few records into your database. Instead of manually specifying the value of each column, Laravel allows you to define a set of default attributes for each of your [Eloquent models](/docs/{{version}}/eloquent) using model factories.\n\nTo see an example of how to write a factory, take a look at the `database/factories/UserFactory.php` file in your application. This factory is included with all new Laravel applications and contains the following factory definition:\n\n```php\nnamespace Database\\Factories;\n\nuse Illuminate\\Database\\Eloquent\\Factories\\Factory;\nuse Illuminate\\Support\\Facades\\Hash;\nuse Illuminate\\Support\\Str;\n\n/**\n * @extends \\Illuminate\\Database\\Eloquent\\Factories\\Factory<\\App\\Models\\User>\n */\nclass UserFactory extends Factory\n{\n    /**\n     * The current password being used by the factory.\n     */\n    protected static ?string $password;\n\n    /**\n     * Define the model's default state.\n     *\n     * @return array<string, mixed>\n     */\n    public function definition(): array\n    {\n        return [\n            'name' => fake()->name(),\n            'email' => fake()->unique()->safeEmail(),\n            'email_verified_at' => now(),\n            'password' => static::$password ??= Hash::make('password'),\n            'remember_token' => Str::random(10),\n        ];\n    }\n\n    /**\n     * Indicate that the model's email address should be unverified.\n     */\n    public function unverified(): static\n    {\n        return $this->state(fn (array $attributes) => [\n            'email_verified_at' => null,\n        ]);\n    }\n}\n```\n\nAs you can see, in their most basic form, factories are classes that extend Laravel's base factory class and define a `definition` method. The `definition` method returns the default set of attribute values that should be applied when creating a model using the factory.\n\nVia the `fake` helper, factories have access to the [Faker](https://github.com/FakerPHP/Faker) PHP library, which allows you to conveniently generate various kinds of random data for testing and seeding.\n\n> [!NOTE]\n> You can change your application's Faker locale by updating the `faker_locale` option in your `config/app.php` configuration file.\n\n---\n\n## Scout Database / Collection Engines Collection Engine\nScout | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/scout.md\n\n\n### Collection Engine\n\nThe \"collection\" engine is intended for quick prototypes, extremely small datasets (a few hundred records), or running tests. It retrieves all possible records from your database and uses Laravel's `Str::is` helper to filter them in PHP, so it does not require any indexing or database-specific features. For anything beyond trivial use cases, you should use the [database engine](#database-engine) instead.\n\nTo use the collection engine, you may simply set the value of the `SCOUT_DRIVER` environment variable to `collection`, or specify the `collection` driver directly in your application's `scout` configuration file:\n\n```ini\nSCOUT_DRIVER=collection\n```\n\nOnce you have specified the collection driver as your preferred driver, you may start [executing search queries](#searching) against your models. Search engine indexing, such as the indexing needed to seed Algolia, Meilisearch, or Typesense indexes, is unnecessary when using the collection engine.\n\n#### Differences From Database Engine\n\nWhile the database engine uses full-text indexes and `LIKE` clauses to find matching records efficiently, the collection engine pulls all records and filters them in PHP. The collection engine is the most portable option as it works across all relational databases supported by Laravel (including SQLite and SQL Server); however, it is significantly less efficient than the database engine and should not be used with large datasets.\n\n---\n\n## Seeding Running Seeders Forcing Seeders to Run in Production\nSeeding | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/seeding.md\n\n\n#### Forcing Seeders to Run in Production\n\nSome seeding operations may cause you to alter or lose data. In order to protect you from running seeding commands against your production database, you will be prompted for confirmation before the seeders are executed in the `production` environment. To force the seeders to run without a prompt, use the `--force` flag:\n\n```shell\nphp artisan db:seed --force\n```\n\n---\n\n## Seeding\nSeeding | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/seeding.md\n\n# Database: Seeding\n\n- [Introduction](#introduction)\n- [Writing Seeders](#writing-seeders)\n    - [Using Model Factories](#using-model-factories)\n    - [Calling Additional Seeders](#calling-additional-seeders)\n    - [Muting Model Events](#muting-model-events)\n- [Running Seeders](#running-seeders)\n\n---\n\n## Seeding Running Seeders\nSeeding | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/seeding.md\n\n\n## Running Seeders\n\nYou may execute the `db:seed` Artisan command to seed your database. By default, the `db:seed` command runs the `Database\\Seeders\\DatabaseSeeder` class, which may in turn invoke other seed classes. However, you may use the `--class` option to specify a specific seeder class to run individually:\n\n```shell\nphp artisan db:seed\n\nphp artisan db:seed --class=UserSeeder\n```\n\nYou may also seed your database using the `migrate:fresh` command in combination with the `--seed` option, which will drop all tables and re-run all of your migrations. This command is useful for completely re-building your database. The `--seeder` option may be used to specify a specific seeder to run:\n\n```shell\nphp artisan migrate:fresh --seed\n\nphp artisan migrate:fresh --seed --seeder=UserSeeder\n```\n\n---\n\n## Seeding Writing Seeders Calling Additional Seeders\nSeeding | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/seeding.md\n\n\n### Calling Additional Seeders\n\nWithin the `DatabaseSeeder` class, you may use the `call` method to execute additional seed classes. Using the `call` method allows you to break up your database seeding into multiple files so that no single seeder class becomes too large. The `call` method accepts an array of seeder classes that should be executed:\n\n```php\n/**\n * Run the database seeders.\n */\npublic function run(): void\n{\n    $this->call([\n        UserSeeder::class,\n        PostSeeder::class,\n        CommentSeeder::class,\n    ]);\n}\n```\n\n---\n\n## Pennant Adding Custom Pennant Drivers Defining Features Externally\nPennant | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/pennant.md\n\n\n### Defining Features Externally\n\nIf your driver is a wrapper around a third-party feature flag platform, you will likely define features on the platform rather than using Pennant's `Feature::define` method. If that is the case, your custom driver should also implement the `Laravel\\Pennant\\Contracts\\DefinesFeaturesExternally` interface:\n\n```php\n<?php\n\nnamespace App\\Extensions;\n\nuse Laravel\\Pennant\\Contracts\\Driver;\nuse Laravel\\Pennant\\Contracts\\DefinesFeaturesExternally;\n\nclass FeatureFlagServiceDriver implements Driver, DefinesFeaturesExternally\n{\n    /**\n     * Get the features defined for the given scope.\n     */\n    public function definedFeaturesForScope(mixed $scope): array {}\n\n    /* ... */\n}\n```\n\nThe `definedFeaturesForScope` method should return a list of feature names defined for the provided scope.\n\n---\n\n## Pennant Updating Values Purging Features\nPennant | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/pennant.md\n\n\n### Purging Features\n\nSometimes, it can be useful to purge an entire feature from storage. This is typically necessary if you have removed the feature from your application or you have made adjustments to the feature's definition that you would like to rollout to all users.\n\nYou may remove all stored values for a feature using the `purge` method:\n\n```php\n// Purging a single feature...\nFeature::purge('new-api');\n\n// Purging multiple features...\nFeature::purge(['new-api', 'purchase-button']);\n```\n\nIf you would like to purge _all_ features from storage, you may invoke the `purge` method without any arguments:\n\n```php\nFeature::purge();\n```\n\nAs it can be useful to purge features as part of your application's deployment pipeline, Pennant includes a `pennant:purge` Artisan command which will purge the provided features from storage:\n\n```shell\nphp artisan pennant:purge new-api\n\nphp artisan pennant:purge new-api purchase-button\n```\n\nIt is also possible to purge all features _except_ those in a given feature list. For example, imagine you wanted to purge all features but keep the values for the \"new-api\" and \"purchase-button\" features in storage. To accomplish this, you can pass those feature names to the `--except` option:\n\n```shell\nphp artisan pennant:purge --except=new-api --except=purchase-button\n```\n\nFor convenience, the `pennant:purge` command also supports an `--except-registered` flag. This flag indicates that all features except those explicitly registered in a service provider should be purged:\n\n```shell\nphp artisan pennant:purge --except-registered\n```\n\n---\n\n## Seeding Introduction\nSeeding | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/seeding.md\n\n\n## Introduction\n\nLaravel includes the ability to seed your database with data using seed classes. All seed classes are stored in the `database/seeders` directory. By default, a `DatabaseSeeder` class is defined for you. From this class, you may use the `call` method to run other seed classes, allowing you to control the seeding order.\n\n> [!NOTE]\n> [Mass assignment protection](/docs/{{version}}/eloquent#mass-assignment) is automatically disabled during database seeding.\n\n---\n\n## Pennant Checking Features In-Memory Cache\nPennant | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/pennant.md\n\n\n### In-Memory Cache\n\nWhen checking a feature, Pennant will create an in-memory cache of the result. If you are using the `database` driver, this means that re-checking the same feature flag within a single request will not trigger additional database queries. This also ensures that the feature has a consistent result for the duration of the request.\n\nIf you need to manually flush the in-memory cache, you may use the `flushCache` method offered by the `Feature` facade:\n\n```php\nFeature::flushCache();\n```\n\n---\n\n## Seeding Writing Seeders Muting Model Events\nSeeding | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/seeding.md\n\n\n### Muting Model Events\n\nWhile running seeds, you may want to prevent models from dispatching events. You may achieve this using the `WithoutModelEvents` trait. When used, the `WithoutModelEvents` trait ensures no model events are dispatched, even if additional seed classes are executed via the `call` method:\n\n```php\n<?php\n\nnamespace Database\\Seeders;\n\nuse Illuminate\\Database\\Seeder;\nuse Illuminate\\Database\\Console\\Seeds\\WithoutModelEvents;\n\nclass DatabaseSeeder extends Seeder\n{\n    use WithoutModelEvents;\n\n    /**\n     * Run the database seeders.\n     */\n    public function run(): void\n    {\n        $this->call([\n            UserSeeder::class,\n        ]);\n    }\n}\n```\n\n---\n\n## Pennant Retrieving Multiple Features\nPennant | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/pennant.md\n\n\n## Retrieving Multiple Features\n\nThe `values` method allows the retrieval of multiple features for a given scope:\n\n```php\nFeature::values(['billing-v2', 'purchase-button']);\n\n// [\n//     'billing-v2' => false,\n//     'purchase-button' => 'blue-sapphire',\n// ]\n```\n\nOr, you may use the `all` method to retrieve the values of all defined features for a given scope:\n\n```php\nFeature::all();\n\n// [\n//     'billing-v2' => false,\n//     'purchase-button' => 'blue-sapphire',\n//     'site-redesign' => true,\n// ]\n```\n\nHowever, class-based features are dynamically registered and are not known by Pennant until they are explicitly checked. This means your application's class-based features may not appear in the results returned by the `all` method if they have not already been checked during the current request.\n\nIf you would like to ensure that feature classes are always included when using the `all` method, you may use Pennant's feature discovery capabilities. To get started, invoke the `discover` method in one of your application's service providers:\n\n```php\n<?php\n\nnamespace App\\Providers;\n\nuse Illuminate\\Support\\ServiceProvider;\nuse Laravel\\Pennant\\Feature;\n\nclass AppServiceProvider extends ServiceProvider\n{\n    /**\n     * Bootstrap any application services.\n     */\n    public function boot(): void\n    {\n        Feature::discover();\n\n        // ...\n    }\n}\n```\n\nThe `discover` method will register all of the feature classes in your application's `app/Features` directory. The `all` method will now include these classes in its results, regardless of whether they have been checked during the current request:\n\n```php\nFeature::all();\n\n// [\n//     'App\\Features\\NewApi' => true,\n//     'billing-v2' => false,\n//     'purchase-button' => 'blue-sapphire',\n//     'site-redesign' => true,\n// ]\n```\n\n---\n\n## Pennant Rich Feature Values\nPennant | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/pennant.md\n\n\n## Rich Feature Values\n\nUntil now, we have primarily shown features as being in a binary state, meaning they are either \"active\" or \"inactive\", but Pennant also allows you to store rich values as well.\n\nFor example, imagine you are testing three new colors for the \"Buy now\" button of your application. Instead of returning `true` or `false` from the feature definition, you may instead return a string:\n\n```php\nuse Illuminate\\Support\\Arr;\nuse Laravel\\Pennant\\Feature;\n\nFeature::define('purchase-button', fn (User $user) => Arr::random([\n    'blue-sapphire',\n    'seafoam-green',\n    'tart-orange',\n]));\n```\n\nYou may retrieve the value of the `purchase-button` feature using the `value` method:\n\n```php\n$color = Feature::value('purchase-button');\n```\n\nPennant's included Blade directive also makes it easy to conditionally render content based on the current value of the feature:\n\n```blade\n@feature('purchase-button', 'blue-sapphire')\n    <!-- 'blue-sapphire' is active -->\n@elsefeature('purchase-button', 'seafoam-green')\n    <!-- 'seafoam-green' is active -->\n@elsefeature('purchase-button', 'tart-orange')\n    <!-- 'tart-orange' is active -->\n@endfeature\n```\n\n> [!NOTE]\n> When using rich values, it is important to know that a feature is considered \"active\" when it has any value other than `false`.\n\nWhen calling the [conditional `when`](#conditional-execution) method, the feature's rich value will be provided to the first closure:\n\n```php\nFeature::when('purchase-button',\n    fn ($color) => /* ... */,\n    fn () => /* ... */,\n);\n```\n\nLikewise, when calling the conditional `unless` method, the feature's rich value will be provided to the optional second closure:\n\n```php\nFeature::unless('purchase-button',\n    fn () => /* ... */,\n    fn ($color) => /* ... */,\n);\n```\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## Pennant Checking Features Intercepting Feature Checks\nPennant | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/pennant.md\n\n\n### Intercepting Feature Checks\n\nSometimes it can be useful to perform some in-memory checks before retrieving the stored value of a given feature. Imagine you are developing a new API behind a feature flag and want the ability to disable the new API without losing any of the resolved feature values in storage. If you notice a bug in the new API, you could easily disable it for everyone except internal team members, fix the bug, and then re-enable the new API for the users that previously had access to the feature.\n\nYou can achieve this with a [class-based feature's](#class-based-features) `before` method. When present, the `before` method is always run in-memory before retrieving the value from storage. If a non-`null` value is returned from the method, it will be used in place of the feature's stored value for the duration of the request:\n\n```php\n<?php\n\nnamespace App\\Features;\n\nuse App\\Models\\User;\nuse Illuminate\\Support\\Facades\\Config;\nuse Illuminate\\Support\\Lottery;\n\nclass NewApi\n{\n    /**\n     * Run an always-in-memory check before the stored value is retrieved.\n     */\n    public function before(User $user): mixed\n    {\n        if (Config::get('features.new-api.disabled')) {\n            return $user->isInternalTeamMember();\n        }\n    }\n\n    /**\n     * Resolve the feature's initial value.\n     */\n    public function resolve(User $user): mixed\n    {\n        return match (true) {\n            $user->isInternalTeamMember() => true,\n            $user->isHighTrafficCustomer() => false,\n            default => Lottery::odds(1 / 100),\n        };\n    }\n}\n```\n\nYou could also use this feature to schedule the global rollout of a feature that was previously behind a feature flag:\n\n```php\n<?php\n\nnamespace App\\Features;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Support\\Facades\\Config;\n\nclass NewApi\n{\n    /**\n     * Run an always-in-memory check before the stored value is retrieved.\n     */\n    public function before(User $user): mixed\n    {\n        if (Config::get('features.new-api.disabled')) {\n            return $user->isInternalTeamMember();\n        }\n\n        if (Carbon::parse(Config::get('features.new-api.rollout-date'))->isPast()) {\n            return true;\n        }\n    }\n\n    // ...\n}\n```\n\n_[Truncated]_"}]


message: The docs confirm the normal Laravel path here: keep seeders composable through `DatabaseSeeder`, and assert seeded data with regular feature tests. I’m checking the existing order schema, enums, and fixtures now so the seed data lands on real model relationships and doesn’t break tenant scoping.

phase: commentary


type: message

role: assistant

text: The docs confirm the normal Laravel path here: keep seeders composable through `DatabaseSeeder`, and assert seeded data with regular feature tests. I’m checking the existing order schema, enums, and fixtures now so the seed data lands on real model relationships and doesn’t break tenant scoping.


cmd: rg --files app database tests specs | rg '(Order|Payment|Fulfillment|Refund|Customer|Address|DatabaseSeeder|Seeder|Factory|Admin/Order|CustomerAccount|TenantIsolation|Dashboard)'


tool: database-schema

filter: order

engine: sqlite

order_id: integer

product_id: integer

variant_id: integer

title_snapshot: varchar

sku_snapshot: varchar

quantity: integer

unit_price_amount: integer

total_amount: integer

tax_lines_json: text

discount_allocations_json: text

store_id: integer

checkout_id: integer

customer_id: integer

order_number: varchar

payment_method: varchar

status: varchar

financial_status: varchar

fulfillment_status: varchar

currency: varchar

subtotal_amount: integer

discount_amount: integer

shipping_amount: integer

tax_amount: integer

email: varchar

billing_address_json: text

shipping_address_json: text

placed_at: datetime

created_at: datetime

updated_at: datetime


filter: payment

provider: varchar

method: varchar

provider_payment_id: varchar

amount: integer

raw_json_encrypted: text


filter: fulfillment

fulfillment_id: integer

order_line_id: integer

tracking_company: varchar

tracking_number: varchar

tracking_url: varchar

shipped_at: datetime

delivered_at: datetime


filter: refund

payment_id: integer

reason: varchar

provider_refund_id: varchar


command: rg --files app database tests specs | rg '(Order|Payment|Fulfillment|Refund|Customer|Address|DatabaseSeeder|Seeder|Factory|Admin/Order|CustomerAccount|TenantIsolation|Dashboard)'

cmd: rg --files app database tests specs

path: app

type: search

query: (Order|Payment|Fulfillment|Refund|Customer|Address|DatabaseSeeder|Seeder|Factory|Admin/Order|CustomerAccount|TenantIsolation|Dashboard)

status: completed


output:
Chunk ID: 0d3199
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1799
Output:
app/ValueObjects/RefundResult.php
app/ValueObjects/PaymentResult.php
database/factories/FulfillmentLineFactory.php
database/factories/OauthTokenFactory.php
database/factories/ShippingZoneFactory.php
database/factories/ProductVariantFactory.php
database/factories/SearchSettingsFactory.php
database/factories/ProductFactory.php
database/factories/AnalyticsDailyFactory.php
database/factories/DataExportFactory.php
database/factories/CustomerAddressFactory.php
database/factories/RefundFactory.php
database/factories/NavigationMenuFactory.php
database/factories/ShippingRateFactory.php
database/factories/StoreSettingsFactory.php
database/factories/SearchQueryFactory.php
database/factories/UserFactory.php
database/factories/DiscountFactory.php
database/factories/AnalyticsEventFactory.php
database/factories/TaxSettingsFactory.php
database/factories/ThemeFileFactory.php
database/factories/WebhookSubscriptionFactory.php
database/factories/CustomerFactory.php
database/factories/CartFactory.php
database/factories/AppInstallationFactory.php
database/factories/CollectionFactory.php
database/factories/StoreDomainFactory.php
database/factories/PageFactory.php
database/factories/CheckoutFactory.php
database/factories/NavigationItemFactory.php
database/factories/FulfillmentFactory.php
database/factories/AppFactory.php
database/factories/ProductOptionFactory.php
database/factories/CartLineFactory.php
database/factories/InventoryItemFactory.php
database/factories/PaymentFactory.php
database/factories/ProductOptionValueFactory.php
database/factories/OrderFactory.php
database/factories/ProductMediaFactory.php
database/factories/WebhookDeliveryFactory.php
database/factories/OauthClientFactory.php
database/factories/ThemeFactory.php
database/factories/OrganizationFactory.php
database/factories/OrderLineFactory.php
database/factories/StoreFactory.php
database/factories/ThemeSettingsFactory.php
tests/Feature/Orders/OrderServiceTest.php
tests/Feature/Orders/BankTransferOrderTest.php
tests/Feature/Orders/RefundServiceTest.php
tests/Feature/Orders/FulfillmentServiceTest.php
database/seeders/DatabaseSeeder.php
database/seeders/DiscountSeeder.php
database/seeders/OrderSeeder.php
database/seeders/PageSeeder.php
database/seeders/CustomerAddressSeeder.php
database/seeders/ProductOptionValueSeeder.php
database/seeders/SearchSettingsSeeder.php
database/seeders/FulfillmentLineSeeder.php
database/seeders/CartLineSeeder.php
database/seeders/ThemeSettingsSeeder.php
database/seeders/CartSeeder.php
database/seeders/ShippingZoneSeeder.php
database/seeders/OrganizationSeeder.php
database/seeders/ProductMediaSeeder.php
database/seeders/NavigationItemSeeder.php
database/seeders/InventoryItemSeeder.php
database/seeders/RefundSeeder.php
database/seeders/StoreSettingsSeeder.php
database/seeders/ProductVariantSeeder.php
database/seeders/ThemeFileSeeder.php
database/seeders/CustomerSeeder.php
database/seeders/AnalyticsSeeder.php
database/seeders/OrderLineSeeder.php
database/seeders/PaymentSeeder.php
database/seeders/ThemeSeeder.php
database/seeders/UserSeeder.php
database/seeders/NavigationMenuSeeder.php
database/seeders/ProductSeeder.php
database/seeders/FulfillmentSeeder.php
database/seeders/StoreSeeder.php
database/seeders/AppSeeder.php
database/seeders/CheckoutSeeder.php
database/seeders/CollectionSeeder.php
database/seeders/ShippingRateSeeder.php
database/seeders/StoreUserSeeder.php
database/seeders/ProductOptionSeeder.php
database/seeders/StoreDomainSeeder.php
database/seeders/TaxSettingsSeeder.php
tests/Feature/Auth/CustomerPasswordResetTest.php
tests/Feature/Api/AdminOrderExportApiTest.php
tests/Feature/Api/StorefrontOrderApiTest.php
tests/Feature/Api/AdminOrderApiTest.php
tests/Feature/Storefront/CustomerAccountTest.php
tests/Feature/Storefront/OrderViewsTest.php
tests/Feature/DashboardTest.php
tests/Feature/Payments/MockPaymentProviderTest.php
app/Services/OrderExportService.php
app/Services/CustomerPasswordResetService.php
app/Services/RefundService.php
tests/Browser/Admin/CustomerManagementTest.php
tests/Browser/Admin/OrderManagementTest.php
tests/Feature/Admin/CustomerManagementTest.php
tests/Feature/Admin/OrderManagementTest.php
tests/Feature/Admin/DashboardTest.php
tests/Feature/Admin/AnalyticsDashboardTest.php
app/Exceptions/PaymentFailedException.php
app/Exceptions/UnserviceableShippingAddressException.php
app/Exceptions/InvalidOrderOperationException.php
app/Exceptions/InvalidRefundOperationException.php
app/Exceptions/InvalidFulfillmentOperationException.php
app/Contracts/PaymentProvider.php
app/Policies/OrderPolicy.php
app/Policies/CustomerPolicy.php
app/Policies/RefundPolicy.php
app/Policies/FulfillmentPolicy.php
app/Auth/CustomerUserProvider.php
tests/Feature/Foundation/CustomerAuthTest.php
app/Livewire/Admin/Dashboard.php
app/Models/FulfillmentLine.php
app/Models/CustomerAddress.php
app/Models/Customer.php
app/Models/Payment.php
app/Models/Fulfillment.php
app/Models/Order.php
app/Models/Refund.php
app/Models/OrderLine.php
tests/Browser/Storefront/TenantIsolationTest.php
tests/Browser/Storefront/CustomerAccountTest.php
app/Notifications/CustomerResetPassword.php
app/Services/FulfillmentService.php
app/Services/PaymentService.php
app/Livewire/Admin/Customers/Show.php
app/Livewire/Admin/Customers/Index.php
app/Livewire/Admin/Orders/Show.php
app/Livewire/Admin/Orders/Index.php
app/Services/Payments/MockPaymentProvider.php
app/Services/OrderService.php
app/Enums/OrderStatus.php
app/Enums/FulfillmentShipmentStatus.php
app/Enums/RefundStatus.php
app/Enums/PaymentMethod.php
app/Enums/FulfillmentStatus.php
app/Enums/PaymentStatus.php
app/Livewire/Storefront/Account/Orders/Show.php
app/Livewire/Storefront/Account/Orders/Index.php
app/Support/OrderAccessToken.php
app/Livewire/Storefront/Account/Addresses/Index.php
app/Events/OrderCancelled.php
app/Events/OrderRefunded.php
app/Events/OrderCreated.php
app/Events/FulfillmentCreated.php
app/Events/FulfillmentDelivered.php
app/Events/FulfillmentShipped.php
app/Events/OrderPaid.php
app/Jobs/CancelUnpaidBankTransferOrders.php
app/Http/Requests/Api/Admin/V1/CreateOrderFulfillmentRequest.php
app/Http/Requests/Api/Admin/V1/CreateOrderRefundRequest.php
app/Http/Controllers/Storefront/Account/Auth/CustomerPasswordResetController.php
app/Http/Resources/Admin/V1/CustomerResource.php
app/Http/Resources/Admin/V1/RefundResource.php
app/Http/Resources/Admin/V1/OrderResource.php
app/Http/Resources/Admin/V1/FulfillmentResource.php
app/Http/Controllers/Api/Admin/V1/CustomerController.php
app/Http/Controllers/Api/Admin/V1/OrderRefundController.php
app/Http/Controllers/Api/Admin/V1/OrderFulfillmentController.php
app/Http/Controllers/Api/Admin/V1/OrderController.php
app/Http/Controllers/Api/Admin/V1/OrderExportController.php
app/Http/Requests/Api/Storefront/V1/SelectCheckoutPaymentRequest.php
app/Http/Requests/Api/Storefront/V1/CompleteCheckoutPaymentRequest.php
app/Http/Requests/Api/Storefront/V1/SetCheckoutAddressRequest.php
app/Http/Requests/Storefront/Auth/ResetCustomerPasswordRequest.php
app/Http/Requests/Storefront/Auth/RequestCustomerPasswordResetRequest.php
app/Http/Controllers/Api/Storefront/V1/OrderController.php
app/Http/Resources/Storefront/V1/OrderResource.php


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


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


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


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


cmd: sed -n '1,240p' database/seeders/ProductVariantSeeder.php


cmd: sed -n '1,240p' database/seeders/InventoryItemSeeder.php


command: sed -n '1,260p' database/seeders/CustomerSeeder.php

CustomerSeeder.php

type: read

aggregated_output:
<?php

namespace Database\Seeders;

use App\Models\Customer;
use App\Models\Store;
use Illuminate\Database\Seeder;

class CustomerSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        $store = Store::query()->where('handle', 'acme-fashion')->firstOrFail();

        Customer::withoutGlobalScopes()->updateOrCreate(
            [
                'store_id' => $store->getKey(),
                'email' => 'customer@acme.test',
            ],
            [
                'name' => 'John Doe',
                'password' => 'password',
                'marketing_opt_in' => true,
            ],
        );
    }
}


command: sed -n '1,240p' database/seeders/DatabaseSeeder.php

aggregated_output:
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     */
    public function run(): void
    {
        $this->call([
            OrganizationSeeder::class,
            StoreSeeder::class,
            StoreDomainSeeder::class,
            AppSeeder::class,
            UserSeeder::class,
            StoreUserSeeder::class,
            StoreSettingsSeeder::class,
            SearchSettingsSeeder::class,
            CollectionSeeder::class,
            ProductSeeder::class,
            ThemeSeeder::class,
            ThemeFileSeeder::class,
            ThemeSettingsSeeder::class,
            PageSeeder::class,
            NavigationMenuSeeder::class,
            NavigationItemSeeder::class,
            TaxSettingsSeeder::class,
            ShippingZoneSeeder::class,
            ShippingRateSeeder::class,
            DiscountSeeder::class,
            CustomerSeeder::class,
            CustomerAddressSeeder::class,
            AnalyticsSeeder::class,
        ]);
    }
}


command: sed -n '1,260p' database/seeders/CustomerAddressSeeder.php

aggregated_output:
<?php

namespace Database\Seeders;

use App\Models\Customer;
use App\Models\CustomerAddress;
use App\Models\Store;
use Illuminate\Database\Seeder;

class CustomerAddressSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        $store = Store::query()->where('handle', 'acme-fashion')->firstOrFail();
        $customer = Customer::withoutGlobalScopes()
            ->where('store_id', $store->getKey())
            ->where('email', 'customer@acme.test')
            ->firstOrFail();

        CustomerAddress::query()->updateOrCreate(
            [
                'customer_id' => $customer->getKey(),
                'label' => 'Home',
            ],
            [
                'address_json' => [
                    'first_name' => 'John',
                    'last_name' => 'Doe',
                    'address1' => 'Main Street 1',
                    'address2' => null,
                    'city' => 'Berlin',
                    'province_code' => null,
                    'country' => 'DE',
                    'postal_code' => '10115',
                ],
                'is_default' => true,
            ],
        );
    }
}


command: sed -n '1,260p' database/seeders/ProductSeeder.php

aggregated_output:
<?php

namespace Database\Seeders;

use App\Models\Collection as ProductCollection;
use App\Models\InventoryItem;
use App\Models\Product;
use App\Models\ProductMedia;
use App\Models\ProductOption;
use App\Models\ProductOptionValue;
use App\Models\ProductVariant;
use App\Models\Store;
use Illuminate\Database\Seeder;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class ProductSeeder extends Seeder
{
    public function run(): void
    {
        DB::transaction(function (): void {
            $this->seedProducts('acme-fashion', $this->fashionProducts());
            $this->seedProducts('acme-electronics', $this->electronicsProducts());
        });
    }

    /**
     * @param  array<int, array<string, mixed>>  $products
     */
    private function seedProducts(string $storeHandle, array $products): void
    {
        $store = Store::query()->where('handle', $storeHandle)->firstOrFail();
        $collections = ProductCollection::withoutGlobalScopes()
            ->where('store_id', $store->getKey())
            ->get()
            ->keyBy('handle');

        foreach ($products as $index => $data) {
            $product = Product::withoutGlobalScopes()->updateOrCreate(
                [
                    'store_id' => $store->getKey(),
                    'handle' => $data['handle'],
                ],
                [
                    ...Arr::only($data, ['title', 'status', 'description_html', 'vendor', 'product_type', 'tags', 'published_at']),
                    'store_id' => $store->getKey(),
                ],
            );

            $this->replaceCatalogChildren($product);

            $optionValueIds = $this->createOptions($product, $data['options'] ?? []);
            $this->createVariants($store, $product, $data, $optionValueIds);

            $collectionIds = collect($data['collections'] ?? [])
                ->map(fn (string $handle): ?int => $collections->get($handle)?->getKey())
                ->filter()
                ->mapWithKeys(fn (int $collectionId): array => [$collectionId => ['position' => $index]])
                ->all();

            $product->collections()->sync($collectionIds);
        }
    }

    private function replaceCatalogChildren(Product $product): void
    {
        ProductMedia::withoutGlobalScopes()
            ->where('product_id', $product->getKey())
            ->get()
            ->each
            ->delete();

        ProductVariant::withoutGlobalScopes()
            ->where('product_id', $product->getKey())
            ->get()
            ->each
            ->delete();

        ProductOption::withoutGlobalScopes()
            ->where('product_id', $product->getKey())
            ->delete();
    }

    /**
     * @param  array<string, array<int, string>>  $options
     * @return array<string, array<string, int>>
     */
    private function createOptions(Product $product, array $options): array
    {
        $optionValueIds = [];

        foreach ($options as $optionPosition => $valuesByName) {
            $optionName = (string) array_key_first($valuesByName);
            $option = ProductOption::withoutGlobalScopes()->create([
                'product_id' => $product->getKey(),
                'name' => $optionName,
                'position' => $optionPosition,
            ]);

            foreach (array_values($valuesByName[$optionName]) as $valuePosition => $value) {
                $optionValue = ProductOptionValue::withoutGlobalScopes()->create([
                    'product_option_id' => $option->getKey(),
                    'value' => $value,
                    'position' => $valuePosition,
                ]);

                $optionValueIds[$optionName][$value] = $optionValue->getKey();
            }
        }

        return $optionValueIds;
    }

    /**
     * @param  array<string, mixed>  $data
     * @param  array<string, array<string, int>>  $optionValueIds
     */
    private function createVariants(Store $store, Product $product, array $data, array $optionValueIds): void
    {
        $variants = $data['variants'] ?? $this->variantDefinitions($data);

        foreach ($variants as $position => $variantData) {
            $variant = ProductVariant::withoutGlobalScopes()->create([
                'product_id' => $product->getKey(),
                'sku' => $variantData['sku'] ?? $this->sku($data['sku_prefix'] ?? Str::upper(Str::slug($data['handle'], '-')), $variantData['options'] ?? []),
                'barcode' => $variantData['barcode'] ?? null,
                'price_amount' => $variantData['price_amount'],
                'compare_at_amount' => $variantData['compare_at_amount'] ?? null,
                'currency' => $store->default_currency,
                'weight_g' => $variantData['weight_g'] ?? 250,
                'requires_shipping' => $variantData['requires_shipping'] ?? true,
                'is_default' => $position === 0,
                'position' => $position,
                'status' => $variantData['status'] ?? 'active',
            ]);

            InventoryItem::withoutGlobalScopes()->updateOrCreate(
                ['variant_id' => $variant->getKey()],
                [
                    'store_id' => $store->getKey(),
                    'quantity_on_hand' => $variantData['quantity_on_hand'] ?? 0,
                    'quantity_reserved' => 0,
                    'policy' => $variantData['policy'] ?? 'deny',
                ],
            );

            $selectedOptions = $variantData['options'] ?? [];

            if ($selectedOptions !== []) {
                $variant->optionValues()->sync($this->selectedOptionValueIds($selectedOptions, $optionValueIds));
            }
        }
    }

    /**
     * @param  array<string, mixed>  $data
     * @return array<int, array<string, mixed>>
     */
    private function variantDefinitions(array $data): array
    {
        $options = $data['options'] ?? [];
        $defaults = $data['variant_defaults'];

        if ($options === []) {
            return [[
                ...$defaults,
                'options' => [],
                'sku' => $data['sku'] ?? $this->sku($data['sku_prefix'] ?? Str::upper(Str::slug($data['handle'], '-')), []),
            ]];
        }

        return collect($this->optionCombinations($options))
            ->map(fn (array $selection): array => [
                ...$defaults,
                'options' => $selection,
            ])
            ->all();
    }

    /**
     * @param  array<int, array<string, array<int, string>>>  $options
     * @return array<int, array<string, string>>
     */
    private function optionCombinations(array $options): array
    {
        $combinations = [[]];

        foreach ($options as $valuesByName) {
            $optionName = (string) array_key_first($valuesByName);
            $next = [];

            foreach ($combinations as $combination) {
                foreach ($valuesByName[$optionName] as $value) {
                    $next[] = [
                        ...$combination,
                        $optionName => $value,
                    ];
                }
            }

            $combinations = $next;
        }

        return $combinations;
    }

    /**
     * @param  array<string, string>  $selectedOptions
     * @param  array<string, array<string, int>>  $optionValueIds
     * @return array<int, int>
     */
    private function selectedOptionValueIds(array $selectedOptions, array $optionValueIds): array
    {
        return collect($selectedOptions)
            ->map(fn (string $value, string $optionName): int => $optionValueIds[$optionName][$value])
            ->values()
            ->all();
    }

    /**
     * @param  array<string, string>  $selectedOptions
     */
    private function sku(string $prefix, array $selectedOptions): string
    {
        $parts = collect($selectedOptions)
            ->values()
            ->map(fn (string $value): string => $this->skuToken($value))
            ->all();

        return collect([$prefix, ...$parts])
            ->filter()
            ->implode('-');
    }

    private function skuToken(string $value): string
    {
        return [
            'White' => 'WHT',
            'Black' => 'BLK',
            'Navy' => 'NVY',
            'Blue' => 'BLU',
            'Brown' => 'BRN',
            'Olive' => 'OLV',
            'Sky Blue' => 'SKY',
            'Beige' => 'BGE',
            'Khaki' => 'KHK',
            'Sand' => 'SND',
            'Burgundy' => 'BUR',
            'Natural' => 'NAT',
            'Grey' => 'GRY',
            'Camel' => 'CAM',
            'Charcoal' => 'CHA',
            'Silver' => 'SLV',
            'Red' => 'RED',
            '25 EUR' => '25',
            '50 EUR' => '50',
            '100 EUR' => '100',
            '256GB' => '256',
            '512GB' => '512',


command: sed -n '1,240p' database/seeders/InventoryItemSeeder.php

aggregated_output:
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class InventoryItemSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        //
    }
}


command: sed -n '1,240p' database/seeders/ProductVariantSeeder.php

aggregated_output:
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class ProductVariantSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        //
    }
}


output:
Chunk ID: d348b9
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 272
Output:
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     */
    public function run(): void
    {
        $this->call([
            OrganizationSeeder::class,
            StoreSeeder::class,
            StoreDomainSeeder::class,
            AppSeeder::class,
            UserSeeder::class,
            StoreUserSeeder::class,
            StoreSettingsSeeder::class,
            SearchSettingsSeeder::class,
            CollectionSeeder::class,
            ProductSeeder::class,
            ThemeSeeder::class,
            ThemeFileSeeder::class,
            ThemeSettingsSeeder::class,
            PageSeeder::class,
            NavigationMenuSeeder::class,
            NavigationItemSeeder::class,
            TaxSettingsSeeder::class,
            ShippingZoneSeeder::class,
            ShippingRateSeeder::class,
            DiscountSeeder::class,
            CustomerSeeder::class,
            CustomerAddressSeeder::class,
            AnalyticsSeeder::class,
        ]);
    }
}


output:
Chunk ID: ae5fb1
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 169
Output:
<?php

namespace Database\Seeders;

use App\Models\Customer;
use App\Models\Store;
use Illuminate\Database\Seeder;

class CustomerSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        $store = Store::query()->where('handle', 'acme-fashion')->firstOrFail();

        Customer::withoutGlobalScopes()->updateOrCreate(
            [
                'store_id' => $store->getKey(),
                'email' => 'customer@acme.test',
            ],
            [
                'name' => 'John Doe',
                'password' => 'password',
                'marketing_opt_in' => true,
            ],
        );
    }
}


output:
Chunk ID: 9fc695
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 300
Output:
<?php

namespace Database\Seeders;

use App\Models\Customer;
use App\Models\CustomerAddress;
use App\Models\Store;
use Illuminate\Database\Seeder;

class CustomerAddressSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        $store = Store::query()->where('handle', 'acme-fashion')->firstOrFail();
        $customer = Customer::withoutGlobalScopes()
            ->where('store_id', $store->getKey())
            ->where('email', 'customer@acme.test')
            ->firstOrFail();

        CustomerAddress::query()->updateOrCreate(
            [
                'customer_id' => $customer->getKey(),
                'label' => 'Home',
            ],
            [
                'address_json' => [
                    'first_name' => 'John',
                    'last_name' => 'Doe',
                    'address1' => 'Main Street 1',
                    'address2' => null,
                    'city' => 'Berlin',
                    'province_code' => null,
                    'country' => 'DE',
                    'postal_code' => '10115',
                ],
                'is_default' => true,
            ],
        );
    }
}


output:
Chunk ID: fa612e
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2217
Output:
<?php

namespace Database\Seeders;

use App\Models\Collection as ProductCollection;
use App\Models\InventoryItem;
use App\Models\Product;
use App\Models\ProductMedia;
use App\Models\ProductOption;
use App\Models\ProductOptionValue;
use App\Models\ProductVariant;
use App\Models\Store;
use Illuminate\Database\Seeder;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class ProductSeeder extends Seeder
{
    public function run(): void
    {
        DB::transaction(function (): void {
            $this->seedProducts('acme-fashion', $this->fashionProducts());
            $this->seedProducts('acme-electronics', $this->electronicsProducts());
        });
    }

    /**
     * @param  array<int, array<string, mixed>>  $products
     */
    private function seedProducts(string $storeHandle, array $products): void
    {
        $store = Store::query()->where('handle', $storeHandle)->firstOrFail();
        $collections = ProductCollection::withoutGlobalScopes()
            ->where('store_id', $store->getKey())
            ->get()
            ->keyBy('handle');

        foreach ($products as $index => $data) {
            $product = Product::withoutGlobalScopes()->updateOrCreate(
                [
                    'store_id' => $store->getKey(),
                    'handle' => $data['handle'],
                ],
                [
                    ...Arr::only($data, ['title', 'status', 'description_html', 'vendor', 'product_type', 'tags', 'published_at']),
                    'store_id' => $store->getKey(),
                ],
            );

            $this->replaceCatalogChildren($product);

            $optionValueIds = $this->createOptions($product, $data['options'] ?? []);
            $this->createVariants($store, $product, $data, $optionValueIds);

            $collectionIds = collect($data['collections'] ?? [])
                ->map(fn (string $handle): ?int => $collections->get($handle)?->getKey())
                ->filter()
                ->mapWithKeys(fn (int $collectionId): array => [$collectionId => ['position' => $index]])
                ->all();

            $product->collections()->sync($collectionIds);
        }
    }

    private function replaceCatalogChildren(Product $product): void
    {
        ProductMedia::withoutGlobalScopes()
            ->where('product_id', $product->getKey())
            ->get()
            ->each
            ->delete();

        ProductVariant::withoutGlobalScopes()
            ->where('product_id', $product->getKey())
            ->get()
            ->each
            ->delete();

        ProductOption::withoutGlobalScopes()
            ->where('product_id', $product->getKey())
            ->delete();
    }

    /**
     * @param  array<string, array<int, string>>  $options
     * @return array<string, array<string, int>>
     */
    private function createOptions(Product $product, array $options): array
    {
        $optionValueIds = [];

        foreach ($options as $optionPosition => $valuesByName) {
            $optionName = (string) array_key_first($valuesByName);
            $option = ProductOption::withoutGlobalScopes()->create([
                'product_id' => $product->getKey(),
                'name' => $optionName,
                'position' => $optionPosition,
            ]);

            foreach (array_values($valuesByName[$optionName]) as $valuePosition => $value) {
                $optionValue = ProductOptionValue::withoutGlobalScopes()->create([
                    'product_option_id' => $option->getKey(),
                    'value' => $value,
                    'position' => $valuePosition,
                ]);

                $optionValueIds[$optionName][$value] = $optionValue->getKey();
            }
        }

        return $optionValueIds;
    }

    /**
     * @param  array<string, mixed>  $data
     * @param  array<string, array<string, int>>  $optionValueIds
     */
    private function createVariants(Store $store, Product $product, array $data, array $optionValueIds): void
    {
        $variants = $data['variants'] ?? $this->variantDefinitions($data);

        foreach ($variants as $position => $variantData) {
            $variant = ProductVariant::withoutGlobalScopes()->create([
                'product_id' => $product->getKey(),
                'sku' => $variantData['sku'] ?? $this->sku($data['sku_prefix'] ?? Str::upper(Str::slug($data['handle'], '-')), $variantData['options'] ?? []),
                'barcode' => $variantData['barcode'] ?? null,
                'price_amount' => $variantData['price_amount'],
                'compare_at_amount' => $variantData['compare_at_amount'] ?? null,
                'currency' => $store->default_currency,
                'weight_g' => $variantData['weight_g'] ?? 250,
                'requires_shipping' => $variantData['requires_shipping'] ?? true,
                'is_default' => $position === 0,
                'position' => $position,
                'status' => $variantData['status'] ?? 'active',
            ]);

            InventoryItem::withoutGlobalScopes()->updateOrCreate(
                ['variant_id' => $variant->getKey()],
                [
                    'store_id' => $store->getKey(),
                    'quantity_on_hand' => $variantData['quantity_on_hand'] ?? 0,
                    'quantity_reserved' => 0,
                    'policy' => $variantData['policy'] ?? 'deny',
                ],
            );

            $selectedOptions = $variantData['options'] ?? [];

            if ($selectedOptions !== []) {
                $variant->optionValues()->sync($this->selectedOptionValueIds($selectedOptions, $optionValueIds));
            }
        }
    }

    /**
     * @param  array<string, mixed>  $data
     * @return array<int, array<string, mixed>>
     */
    private function variantDefinitions(array $data): array
    {
        $options = $data['options'] ?? [];
        $defaults = $data['variant_defaults'];

        if ($options === []) {
            return [[
                ...$defaults,
                'options' => [],
                'sku' => $data['sku'] ?? $this->sku($data['sku_prefix'] ?? Str::upper(Str::slug($data['handle'], '-')), []),
            ]];
        }

        return collect($this->optionCombinations($options))
            ->map(fn (array $selection): array => [
                ...$defaults,
                'options' => $selection,
            ])
            ->all();
    }

    /**
     * @param  array<int, array<string, array<int, string>>>  $options
     * @return array<int, array<string, string>>
     */
    private function optionCombinations(array $options): array
    {
        $combinations = [[]];

        foreach ($options as $valuesByName) {
            $optionName = (string) array_key_first($valuesByName);
            $next = [];

            foreach ($combinations as $combination) {
                foreach ($valuesByName[$optionName] as $value) {
                    $next[] = [
                        ...$combination,
                        $optionName => $value,
                    ];
                }
            }

            $combinations = $next;
        }

        return $combinations;
    }

    /**
     * @param  array<string, string>  $selectedOptions
     * @param  array<string, array<string, int>>  $optionValueIds
     * @return array<int, int>
     */
    private function selectedOptionValueIds(array $selectedOptions, array $optionValueIds): array
    {
        return collect($selectedOptions)
            ->map(fn (string $value, string $optionName): int => $optionValueIds[$optionName][$value])
            ->values()
            ->all();
    }

    /**
     * @param  array<string, string>  $selectedOptions
     */
    private function sku(string $prefix, array $selectedOptions): string
    {
        $parts = collect($selectedOptions)
            ->values()
            ->map(fn (string $value): string => $this->skuToken($value))
            ->all();

        return collect([$prefix, ...$parts])
            ->filter()
            ->implode('-');
    }

    private function skuToken(string $value): string
    {
        return [
            'White' => 'WHT',
            'Black' => 'BLK',
            'Navy' => 'NVY',
            'Blue' => 'BLU',
            'Brown' => 'BRN',
            'Olive' => 'OLV',
            'Sky Blue' => 'SKY',
            'Beige' => 'BGE',
            'Khaki' => 'KHK',
            'Sand' => 'SND',
            'Burgundy' => 'BUR',
            'Natural' => 'NAT',
            'Grey' => 'GRY',
            'Camel' => 'CAM',
            'Charcoal' => 'CHA',
            'Silver' => 'SLV',
            'Red' => 'RED',
            '25 EUR' => '25',
            '50 EUR' => '50',
            '100 EUR' => '100',
            '256GB' => '256',
            '512GB' => '512',


output:
Chunk ID: 6df863
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 55
Output:
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class ProductVariantSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        //
    }
}


output:
Chunk ID: 941409
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 54
Output:
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class InventoryItemSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        //
    }
}


cmd: sed -n '240,620p' database/seeders/ProductSeeder.php


cmd: sed -n '1,220p' app/Models/OrderLine.php


cmd: sed -n '1,220p' app/Models/Fulfillment.php