# Search Results

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

### Polymorphic Relationships

[Polymorphic relationships](/docs/{{version}}/eloquent-relationships#polymorphic-relationships) may also be created using factories. Polymorphic "morph many" relationships are created in the same way as typical "has many" relationships. For example, if an `App\Models\Post` model has a `morphMany` relationship with an `App\Models\Comment` model:

```php
use App\Models\Post;

$post = Post::factory()->hasComments(3)->create();
```

---

## Migrating From Phpunit Guide
Migrating From Phpunit Guide | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/migrating-from-phpunit-guide.md

---
title: Migrating from PHPUnit
description: Migrating from PHPUnit to Pest is a simple process that can be completed in just a few steps.
---

# Migrating from PHPUnit

Pest is built on top of PHPUnit, so migrating from PHPUnit to Pest is a simple process that can be completed in just a few steps. Once you have Pest installed, you should require the `pestphp/pest-plugin-drift` package as a "dev" dependency in your project.

```bash
composer require pestphp/pest-plugin-drift --dev
```

Drift is a simple yet powerful plugin that will automatically convert your PHPUnit tests to Pest, simply by running the `--drift` option.

```bash
./vendor/bin/pest --drift
```

So, typically, a PHPUnit test looks like this:

```php
<?php

namespace Tests\Unit;

use PHPUnit\Framework\TestCase;

class ExampleTest extends TestCase
{
    public function test_that_true_is_true(): void
    {
        $this->assertTrue(true);
    }
}
```

Should look like this after running `--drift`:

```php
test('true is true', function () {
    expect(true)->toBeTrue();
});
```

---
## Convert Phpunit tests only for certain folder

If you want to convert Phpunit tests only for certain folder, you can pass _path_ as first argument when calling `--drift`:

Example call, if you want to run the conversion for folder `/tests/Helpers`:
```console
/vendor/bin/pest --drift tests/Helpers
```

Output:
```console
/vendor/bin/pest --drift tests/Helpers

✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔

INFO  The [tests/Helpers] directory has been migrated to PEST with XY files changed.
```

---

The output will contain a summary of the conversion process, as well as a list of the files that were converted.

While most of your tests should be converted automatically, and you should be able to run them without any issues, there are some cases where you may need to manually convert some of your tests.

---

Of course, this particular chapter is only for those who are migrating from PHPUnit. Next, let's learn how you can contribute to the growth of Pest: [Community Guide](/docs/community-guide)

---

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

#### Eloquent Models

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

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

---

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

## Factory Relationships

---

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

### Belongs To Relationships

Now that we have explored how to build "has many" relationships using factories, let's explore the inverse of the relationship. The `for` method may be used to define the parent model that factory created models belong to. For example, we can create three `App\Models\Post` model instances that belong to a single user:

```php
use App\Models\Post;
use App\Models\User;

$posts = Post::factory()
    ->count(3)
    ->for(User::factory()->state([
        'name' => 'Jessica Archer',
    ]))
    ->create();
```

If you already have a parent model instance that should be associated with the models you are creating, you may pass the model instance to the `for` method:

```php
$user = User::factory()->create();

$posts = Post::factory()
    ->count(3)
    ->for($user)
    ->create();
```

---

## Packages Resources Migrations
Packages | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/packages.md

### Migrations

If your package contains [database migrations](/docs/{{version}}/migrations), you may use the `publishesMigrations` method to inform Laravel that the given directory or file contains migrations. When Laravel publishes the migrations, it will automatically update the timestamp within their filename to reflect the current date and time:

```php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    $this->publishesMigrations([
        __DIR__.'/../database/migrations' => database_path('migrations'),
    ]);
}
```

---

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

#### Models and UUIDv7

**Likelihood Of Impact: Medium**

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

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

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

---

## Migrations Columns Available Column Types Relationship Types
Migrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md

#### Relationship Types

[foreignId](#column-method-foreignId)
[foreignIdFor](#column-method-foreignIdFor)
[foreignUlid](#column-method-foreignUlid)
[foreignUuid](#column-method-foreignUuid)
[morphs](#column-method-morphs)
[nullableMorphs](#column-method-nullableMorphs)

---

## Eloquent Factories Factory Relationships Many to Many Relationships Pivot Table Attributes
Eloquent Factories | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent-factories.md

#### Pivot Table Attributes

If you need to define attributes that should be set on the pivot / intermediate table linking the models, you may use the `hasAttached` method. This method accepts an array of pivot table attribute names and values as its second argument:

```php
use App\Models\Role;
use App\Models\User;

$user = User::factory()
    ->hasAttached(
        Role::factory()->count(3),
        ['active' => true]
    )
    ->create();
```

You may provide a closure-based state transformation if your state change requires access to the related model:

```php
$user = User::factory()
    ->hasAttached(
        Role::factory()
            ->count(3)
            ->state(function (array $attributes, User $user) {
                return ['name' => $user->name.' Role'];
            }),
        ['active' => true]
    )
    ->create();
```

If you already have model instances that you would like to be attached to the models you are creating, you may pass the model instances to the `hasAttached` method. In this example, the same three roles will be attached to all three users:

```php
$roles = Role::factory()->count(3)->create();

$users = User::factory()
    ->count(3)
    ->hasAttached($roles, ['active' => true])
    ->create();
```

---

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

### Defining Relationships Within Factories

To define a relationship within your model factory, you will typically assign a new factory instance to the foreign key of the relationship. This is normally done for the "inverse" relationships such as `belongsTo` and `morphTo` relationships. For example, if you would like to create a new user when creating a post, you may do the following:

```php
use App\Models\User;

/**
 * Define the model's default state.
 *
 * @return array<string, mixed>
 */
public function definition(): array
{
    return [
        'user_id' => User::factory(),
        'title' => fake()->title(),
        'content' => fake()->paragraph(),
    ];
}
```

If the relationship's columns depend on the factory that defines it you may assign a closure to an attribute. The closure will receive the factory's evaluated attribute array:

```php
/**
 * Define the model's default state.
 *
 * @return array<string, mixed>
 */
public function definition(): array
{
    return [
        'user_id' => User::factory(),
        'user_type' => function (array $attributes) {
            return User::find($attributes['user_id'])->type;
        },
        'title' => fake()->title(),
        'content' => fake()->paragraph(),
    ];
}
```

---

## Migrations Events
Migrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md

## Events

For convenience, each migration operation will dispatch an [event](/docs/{{version}}/events). All of the following events extend the base `Illuminate\Database\Events\MigrationEvent` class:

| Class                                            | Description                                      |
| ------------------------------------------------ | ------------------------------------------------ |
| `Illuminate\Database\Events\MigrationsStarted`   | A batch of migrations is about to be executed.   |
| `Illuminate\Database\Events\MigrationsEnded`     | A batch of migrations has finished executing.    |
| `Illuminate\Database\Events\MigrationStarted`    | A single migration is about to be executed.      |
| `Illuminate\Database\Events\MigrationEnded`      | A single migration has finished executing.       |
| `Illuminate\Database\Events\NoPendingMigrations` | A migration command found no pending migrations. |
| `Illuminate\Database\Events\SchemaDumped`        | A database schema dump has completed.            |
| `Illuminate\Database\Events\SchemaLoaded`        | An existing database schema dump has loaded.     |

---

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

#### Customizing the Eloquent Results Query

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

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

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

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

---

## Migrations Indexes Foreign Key Constraints Toggling Foreign Key Constraints
Migrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md

#### Toggling Foreign Key Constraints

You may enable or disable foreign key constraints within your migrations by using the following methods:

```php
Schema::enableForeignKeyConstraints();

Schema::disableForeignKeyConstraints();

Schema::withoutForeignKeyConstraints(function () {
    // Constraints disabled within this closure...
});
```

> [!WARNING]
> SQLite disables foreign key constraints by default. When using SQLite, make sure to [enable foreign key support](/docs/{{version}}/database#configuration) in your database configuration before attempting to create them in your migrations.

---

## Responses Redirects Redirecting to Named Routes Populating Parameters via Eloquent Models
Responses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md

#### Populating Parameters via Eloquent Models

If you are redirecting to a route with an "ID" parameter that is being populated from an Eloquent model, you may pass the model itself. The ID will be extracted automatically:

```php
// For a route with the following URI: /profile/{id}

return redirect()->route('profile', [$user]);
```

If you would like to customize the value that is placed in the route parameter, you can specify the column in the route parameter definition (`/profile/{id:slug}`) or you can override the `getRouteKey` method on your Eloquent model:

```php
/**
 * Get the value of the model's route key.
 */
public function getRouteKey(): mixed
{
    return $this->slug;
}
```

---

## Migrations Columns Available Column Types `foreignId()` {.collection-method}
Migrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md

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

The `foreignId` method creates an `UNSIGNED BIGINT` equivalent column:

```php
$table->foreignId('user_id');
```

---

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

## Model Factories

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

To learn more about creating and utilizing model factories to create models, please consult the complete [model factory documentation](/docs/{{version}}/eloquent-factories). Once you have defined a model factory, you may utilize the factory within your test to create models:

```php tab=Pest
use App\Models\User;

test('models can be instantiated', function () {
    $user = User::factory()->create();

    // ...
});
```

```php tab=PHPUnit
use App\Models\User;

public function test_models_can_be_instantiated(): void
{
    $user = User::factory()->create();

    // ...
}
```

---

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

---

## Migrations Indexes Foreign Key Constraints Dropping Foreign Keys
Migrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md

#### Dropping Foreign Keys

To drop a foreign key, you may use the `dropForeign` method, passing the name of the foreign key constraint to be deleted as an argument. Foreign key constraints use the same naming convention as indexes. In other words, the foreign key constraint name is based on the name of the table and the columns in the constraint, followed by a "\_foreign" suffix:

```php
$table->dropForeign('posts_user_id_foreign');
```

Alternatively, you may pass an array containing the column name that holds the foreign key to the `dropForeign` method. The array will be converted to a foreign key constraint name using Laravel's constraint naming conventions:

```php
$table->dropForeign(['user_id']);
```

---

## Responses Creating Responses Eloquent Models and Collections
Responses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md

#### Eloquent Models and Collections

You may also return [Eloquent ORM](/docs/{{version}}/eloquent) models and collections directly from your routes and controllers. When you do, Laravel will automatically convert the models and collections to JSON responses while respecting the model's [hidden attributes](/docs/{{version}}/eloquent-serialization#hiding-attributes-from-json):

```php
use App\Models\User;

Route::get('/user/{user}', function (User $user) {
    return $user;
});
```

---

## Migrations Indexes Foreign Key Constraints
Migrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md

### Foreign Key Constraints

Laravel also provides support for creating foreign key constraints, which are used to force referential integrity at the database level. For example, let's define a `user_id` column on the `posts` table that references the `id` column on a `users` table:

```php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::table('posts', function (Blueprint $table) {
    $table->unsignedBigInteger('user_id');

    $table->foreign('user_id')->references('id')->on('users');
});
```

Since this syntax is rather verbose, Laravel provides additional, terser methods that use conventions to provide a better developer experience. When using the `foreignId` method to create your column, the example above can be rewritten like so:

```php
Schema::table('posts', function (Blueprint $table) {
    $table->foreignId('user_id')->constrained();
});
```

The `foreignId` method creates an `UNSIGNED BIGINT` equivalent column, while the `constrained` method will use conventions to determine the table and column being referenced. If your table name does not match Laravel's conventions, you may manually provide it to the `constrained` method. In addition, the name that should be assigned to the generated index may be specified as well:

```php
Schema::table('posts', function (Blueprint $table) {
    $table->foreignId('user_id')->constrained(
        table: 'users', indexName: 'posts_user_id'
    );
});
```

You may also specify the desired action for the "on delete" and "on update" properties of the constraint:

```php
$table->foreignId('user_id')
    ->constrained()
    ->onUpdate('cascade')
    ->onDelete('cascade');
```

An alternative, expressive syntax is also provided for these actions:

| Method                        | Description                                       |
| ----------------------------- | ------------------------------------------------- |
| `$table->cascadeOnUpdate();`  | Updates should cascade.                           |
| `$table->restrictOnUpdate();` | Updates should be restricted.                     |
| `$table->nullOnUpdate();`     | Updates should set the foreign key value to null. |
| `$table->noActionOnUpdate();` | No action on updates.                             |
| `$table->cascadeOnDelete();`  | Deletes should cascade.                           |
| `$table->restrictOnDelete();` | Deletes should be restricted.                     |
| `$table->nullOnDelete();`     | Deletes should set the foreign key value to null. |
| `$table->noActionOnDelete();` | Prevents deletes if child records exist.          |

Any additional [column modifiers](#column-modifiers) must be called before the `constrained` method:

```php
$table->foreignId('user_id')
    ->nullable()
    ->constrained();
```

---

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

#### Database Constructor Signature Changes

**Likelihood Of Impact: Very Low**

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

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

`Illuminate\Database\Schema\Blueprint`

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

`Illuminate\Database\Grammar`

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

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

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

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

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

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

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

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

---

## Pagination Basic Usage Paginating Eloquent Results
Pagination | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/pagination.md

### Paginating Eloquent Results

You may also paginate [Eloquent](/docs/{{version}}/eloquent) queries. In this example, we will paginate the `App\Models\User` model and indicate that we plan to display 15 records per page. As you can see, the syntax is nearly identical to paginating query builder results:

```php
use App\Models\User;

$users = User::paginate(15);
```

Of course, you may call the `paginate` method after setting other constraints on the query, such as `where` clauses:

```php
$users = User::where('votes', '>', 100)->paginate(15);
```

You may also use the `simplePaginate` method when paginating Eloquent models:

```php
$users = User::where('votes', '>', 100)->simplePaginate(15);
```

Similarly, you may use the `cursorPaginate` method to cursor paginate Eloquent models:

```php
$users = User::where('votes', '>', 100)->cursorPaginate(15);
```

---

## Migrations Columns Available Column Types `enum()` {.collection-method}
Migrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md

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

The `enum` method creates a `ENUM` equivalent column with the given valid values:

```php
$table->enum('difficulty', ['easy', 'hard']);
```

Of course, you may use the `Enum::cases()` method instead of manually defining an array of allowed values:

```php
use App\Enums\Difficulty;

$table->enum('difficulty', Difficulty::cases());
```

---

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

---

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

#### Multi-Schema Database Inspecting

**Likelihood Of Impact: Low**

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

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

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

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

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

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

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

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

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

---

## Migrations Indexes Dropping Indexes
Migrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md

### Dropping Indexes

To drop an index, you must specify the index's name. By default, Laravel automatically assigns an index name based on the table name, the name of the indexed column, and the index type. Here are some examples:

| Command                                                  | Description                                                 |
| -------------------------------------------------------- | ----------------------------------------------------------- |
| `$table->dropPrimary('users_id_primary');`               | Drop a primary key from the "users" table.                  |
| `$table->dropUnique('users_email_unique');`              | Drop a unique index from the "users" table.                 |
| `$table->dropIndex('geo_state_index');`                  | Drop a basic index from the "geo" table.                    |
| `$table->dropFullText('posts_body_fulltext');`           | Drop a full text index from the "posts" table.              |
| `$table->dropSpatialIndex('geo_location_spatialindex');` | Drop a spatial index from the "geo" table  (except SQLite). |

If you pass an array of columns into a method that drops indexes, the conventional index name will be generated based on the table name, columns, and index type:

```php
Schema::table('geo', function (Blueprint $table) {
    $table->dropIndex(['state']); // Drops index 'geo_state_index'
});
```

---

## Pagination Basic Usage Paginating Eloquent Results Multiple Paginator Instances per Page
Pagination | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/pagination.md

#### Multiple Paginator Instances per Page

Sometimes you may need to render two separate paginators on a single screen that is rendered by your application. However, if both paginator instances use the `page` query string parameter to store the current page, the two paginators will conflict. To resolve this conflict, you may pass the name of the query string parameter you wish to use to store the paginator's current page via the third argument provided to the `paginate`, `simplePaginate`, and `cursorPaginate` methods:

```php
use App\Models\User;

$users = User::where('votes', '>', 100)->paginate(
    $perPage = 15, $columns = ['*'], $pageName = 'users'
);
```

---

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

#### Parallel Testing and Databases

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

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

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

---

## Migrations Indexes Creating Indexes Online Index Creation
Migrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md

#### Online Index Creation

By default, creating an index on a large table can lock the table and block reads or writes while the index is being built. When using PostgreSQL or SQL Server, you may chain the `online` method onto an index definition to create the index without locking the table, allowing your application to continue reading and writing data during index creation:

```php
$table->string('email')->unique()->online();
```

When using PostgreSQL, this adds the `CONCURRENTLY` option to the index creation statement. When using SQL Server, this adds the `WITH (online = on)` option.

---

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

#### Updated `DatabaseTokenRepository` Constructor Signature

**Likelihood Of Impact: Very Low**

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

---

## Eloquent Events Muting Events Saving a Single Model Without Events
Eloquent | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent.md

#### Saving a Single Model Without Events

Sometimes you may wish to "save" a given model without dispatching any events. You may accomplish this using the `saveQuietly` method:

```php
$user = User::findOrFail(1);

$user->name = 'Victoria Faith';

$user->saveQuietly();
```

You may also "update", "delete", "soft delete", "restore", and "replicate" a given model without dispatching any events:

```php
$user->deleteQuietly();
$user->forceDeleteQuietly();
$user->restoreQuietly();
```

---

## Migrations Columns Available Column Types `foreignUlid()` {.collection-method}
Migrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md

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

The `foreignUlid` method creates a `ULID` equivalent column:

```php
$table->foreignUlid('user_id');
```

---

## Migrations Indexes Renaming Indexes
Migrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md

### Renaming Indexes

To rename an index, you may use the `renameIndex` method provided by the schema builder blueprint. This method accepts the current index name as its first argument and the desired name as its second argument:

```php
$table->renameIndex('from', 'to')
```

---

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

### The Database Directory

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

---

## Eloquent Events Muting Events
Eloquent | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent.md

### Muting Events

You may occasionally need to temporarily "mute" all events fired by a model. You may achieve this using the `withoutEvents` method. The `withoutEvents` method accepts a closure as its only argument. Any code executed within this closure will not dispatch model events, and any value returned by the closure will be returned by the `withoutEvents` method:

```php
use App\Models\User;

$user = User::withoutEvents(function () {
    User::findOrFail(1)->delete();

    return User::find(2);
});
```

---

## Migrations Indexes Creating Indexes Available Index Types
Migrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md

#### Available Index Types

Laravel's schema builder blueprint class provides methods for creating each type of index supported by Laravel. Each index method accepts an optional second argument to specify the name of the index. If omitted, the name will be derived from the names of the table and column(s) used for the index, as well as the index type. Each of the available index methods is described in the table below:

| Command                                          | Description                                                    |
| ------------------------------------------------ | -------------------------------------------------------------- |
| `$table->primary('id');`                         | Adds a primary key.                                            |
| `$table->primary(['id', 'parent_id']);`          | Adds composite keys.                                           |
| `$table->unique('email');`                       | Adds a unique index.                                           |
| `$table->index('state');`                        | Adds an index.                                                 |
| `$table->fullText('body');`                      | Adds a full text index (MariaDB / MySQL / PostgreSQL).         |
| `$table->fullText('body')->language('english');` | Adds a full text index of the specified language (PostgreSQL). |
| `$table->spatialIndex('location');`              | Adds a spatial index (except SQLite).                          |

---

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

#### Database

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

```shell
php artisan make:session-table

php artisan migrate
```

---

## Migrations Indexes Creating Indexes
Migrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md

### Creating Indexes

The Laravel schema builder supports several types of indexes. The following example creates a new `email` column and specifies that its values should be unique. To create the index, we can chain the `unique` method onto the column definition:

```php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::table('users', function (Blueprint $table) {
    $table->string('email')->unique();
});
```

Alternatively, you may create the index after defining the column. To do so, you should call the `unique` method on the schema builder blueprint. This method accepts the name of the column that should receive a unique index:

```php
$table->unique('email');
```

You may even pass an array of columns to an index method to create a compound (or composite) index:

```php
$table->index(['account_id', 'created_at']);
```

When creating an index, Laravel will automatically generate an index name based on the table, column names, and the index type, but you may pass a second argument to the method to specify the index name yourself:

```php
$table->unique('email', 'unique_email');
```

---

## Eloquent Events Observers Observers and Database Transactions
Eloquent | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent.md

#### Observers and Database Transactions

When models are being created within a database transaction, you may want to instruct an observer to only execute its event handlers after the database transaction is committed. You may accomplish this by implementing the `ShouldHandleEventsAfterCommit` interface on your observer. If a database transaction is not in progress, the event handlers will execute immediately:

```php
<?php

namespace App\Observers;

use App\Models\User;
use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit;

class UserObserver implements ShouldHandleEventsAfterCommit
{
    /**
     * Handle the User "created" event.
     */
    public function created(User $user): void
    {
        // ...
    }
}
```

---

## Migrations Columns Available Column Types `tinyIncrements()` {.collection-method}
Migrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md

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

The `tinyIncrements` method creates an auto-incrementing `UNSIGNED TINYINT` equivalent column as a primary key:

```php
$table->tinyIncrements('id');
```

---

## Eloquent Events Observers Defining Observers
Eloquent | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent.md

#### Defining Observers

If you are listening for many events on a given model, you may use observers to group all of your listeners into a single class. Observer classes have method names which reflect the Eloquent events you wish to listen for. Each of these methods receives the affected model as their only argument. The `make:observer` Artisan command is the easiest way to create a new observer class:

```shell
php artisan make:observer UserObserver --model=User
```

This command will place the new observer in your `app/Observers` directory. If this directory does not exist, Artisan will create it for you. Your fresh observer will look like the following:

```php
<?php

namespace App\Observers;

use App\Models\User;

class UserObserver
{
    /**
     * Handle the User "created" event.
     */
    public function created(User $user): void
    {
        // ...
    }

    /**
     * Handle the User "updated" event.
     */
    public function updated(User $user): void
    {
        // ...
    }

    /**
     * Handle the User "deleted" event.
     */
    public function deleted(User $user): void
    {
        // ...
    }

    /**
     * Handle the User "restored" event.
     */
    public function restored(User $user): void
    {
        // ...
    }

    /**
     * Handle the User "forceDeleted" event.
     */
    public function forceDeleted(User $user): void
    {
        // ...
    }
}
```

To register an observer, you may place the `ObservedBy` attribute on the corresponding model:

```php
use App\Observers\UserObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;

#[ObservedBy([UserObserver::class])]
class User extends Authenticatable
{
    //
}
```

Or, you may manually register an observer by invoking the `observe` method on the model you wish to observe. You may register observers in the `boot` method of your application's `AppServiceProvider` class:

```php
use App\Models\User;
use App\Observers\UserObserver;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    User::observe(UserObserver::class);
}
```

> [!NOTE]
> There are additional events an observer can listen to, such as `saving` and `retrieved`. These events are described within the [events](#events) documentation.

_[Truncated]_


Output:
Wall time: 3.5967 seconds
Output:
[{"type":"text","text":"# Search Results\n\n## Eloquent Factories Factory Relationships Polymorphic Relationships\nEloquent Factories | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent-factories.md\n\n\n### Polymorphic Relationships\n\n[Polymorphic relationships](/docs/{{version}}/eloquent-relationships#polymorphic-relationships) may also be created using factories. Polymorphic \"morph many\" relationships are created in the same way as typical \"has many\" relationships. For example, if an `App\\Models\\Post` model has a `morphMany` relationship with an `App\\Models\\Comment` model:\n\n```php\nuse App\\Models\\Post;\n\n$post = Post::factory()->hasComments(3)->create();\n```\n\n---\n\n## Migrating From Phpunit Guide\nMigrating From Phpunit Guide | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/migrating-from-phpunit-guide.md\n\n---\ntitle: Migrating from PHPUnit\ndescription: Migrating from PHPUnit to Pest is a simple process that can be completed in just a few steps.\n---\n\n# Migrating from PHPUnit\n\nPest is built on top of PHPUnit, so migrating from PHPUnit to Pest is a simple process that can be completed in just a few steps. Once you have Pest installed, you should require the `pestphp/pest-plugin-drift` package as a \"dev\" dependency in your project.\n\n```bash\ncomposer require pestphp/pest-plugin-drift --dev\n```\n\nDrift is a simple yet powerful plugin that will automatically convert your PHPUnit tests to Pest, simply by running the `--drift` option.\n\n```bash\n./vendor/bin/pest --drift\n```\n\nSo, typically, a PHPUnit test looks like this:\n\n```php\n<?php\n\nnamespace Tests\\Unit;\n\nuse PHPUnit\\Framework\\TestCase;\n\nclass ExampleTest extends TestCase\n{\n    public function test_that_true_is_true(): void\n    {\n        $this->assertTrue(true);\n    }\n}\n```\n\nShould look like this after running `--drift`:\n\n```php\ntest('true is true', function () {\n    expect(true)->toBeTrue();\n});\n```\n\n---\n## Convert Phpunit tests only for certain folder\n\nIf you want to convert Phpunit tests only for certain folder, you can pass _path_ as first argument when calling `--drift`:\n\nExample call, if you want to run the conversion for folder `/tests/Helpers`: \n```console \n/vendor/bin/pest --drift tests/Helpers\n```\n\nOutput:\n```console\n/vendor/bin/pest --drift tests/Helpers\n\n✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔\n\nINFO  The [tests/Helpers] directory has been migrated to PEST with XY files changed.\n```\n\n---\n\nThe output will contain a summary of the conversion process, as well as a list of the files that were converted.\n\nWhile most of your tests should be converted automatically, and you should be able to run them without any issues, there are some cases where you may need to manually convert some of your tests.\n\n---\n\nOf course, this particular chapter is only for those who are migrating from PHPUnit. Next, let's learn how you can contribute to the growth of Pest: [Community Guide](/docs/community-guide)\n\n\n---\n\n## Urls URLs for Named Routes Eloquent Models\nUrls | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/urls.md\n\n\n#### Eloquent Models\n\nYou will often be generating URLs using the route key (typically the primary key) of [Eloquent models](/docs/{{version}}/eloquent). For this reason, you may pass Eloquent models as parameter values. The `route` helper will automatically extract the model's route key:\n\n```php\necho route('post.show', ['post' => $post]);\n```\n\n---\n\n## Eloquent Factories Factory Relationships\nEloquent Factories | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent-factories.md\n\n\n## Factory Relationships\n\n---\n\n## Eloquent Factories Factory Relationships Belongs To Relationships\nEloquent Factories | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent-factories.md\n\n\n### Belongs To Relationships\n\nNow that we have explored how to build \"has many\" relationships using factories, let's explore the inverse of the relationship. The `for` method may be used to define the parent model that factory created models belong to. For example, we can create three `App\\Models\\Post` model instances that belong to a single user:\n\n```php\nuse App\\Models\\Post;\nuse App\\Models\\User;\n\n$posts = Post::factory()\n    ->count(3)\n    ->for(User::factory()->state([\n        'name' => 'Jessica Archer',\n    ]))\n    ->create();\n```\n\nIf you already have a parent model instance that should be associated with the models you are creating, you may pass the model instance to the `for` method:\n\n```php\n$user = User::factory()->create();\n\n$posts = Post::factory()\n    ->count(3)\n    ->for($user)\n    ->create();\n```\n\n---\n\n## Packages Resources Migrations\nPackages | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/packages.md\n\n\n### Migrations\n\nIf your package contains [database migrations](/docs/{{version}}/migrations), you may use the `publishesMigrations` method to inform Laravel that the given directory or file contains migrations. When Laravel publishes the migrations, it will automatically update the timestamp within their filename to reflect the current date and time:\n\n```php\n/**\n * Bootstrap any package services.\n */\npublic function boot(): void\n{\n    $this->publishesMigrations([\n        __DIR__.'/../database/migrations' => database_path('migrations'),\n    ]);\n}\n```\n\n---\n\n## Upgrade Upgrading To 12.0 From 11.x Eloquent Models and UUIDv7\nUpgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md\n\n\n#### Models and UUIDv7\n\n**Likelihood Of Impact: Medium**\n\nThe `HasUuids` trait now returns UUIDs that are compatible with version 7 of the UUID spec (ordered UUIDs). If you would like to continue using ordered UUIDv4 strings for your model's IDs, you should now use the `HasVersion4Uuids` trait:\n\n```php\nuse Illuminate\\Database\\Eloquent\\Concerns\\HasUuids; // [tl! remove]\nuse Illuminate\\Database\\Eloquent\\Concerns\\HasVersion4Uuids as HasUuids; // [tl! add]\n```\n\nThe `HasVersion7Uuids` trait has been removed. If you were previously using this trait, you should use the `HasUuids` trait instead, which now provides the same behavior.\n\n---\n\n## Migrations Columns Available Column Types Relationship Types\nMigrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md\n\n\n#### Relationship Types\n\n
\n\n[foreignId](#column-method-foreignId)\n[foreignIdFor](#column-method-foreignIdFor)\n[foreignUlid](#column-method-foreignUlid)\n[foreignUuid](#column-method-foreignUuid)\n[morphs](#column-method-morphs)\n[nullableMorphs](#column-method-nullableMorphs)\n\n
\n\n---\n\n## Eloquent Factories Factory Relationships Many to Many Relationships Pivot Table Attributes\nEloquent Factories | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent-factories.md\n\n\n#### Pivot Table Attributes\n\nIf you need to define attributes that should be set on the pivot / intermediate table linking the models, you may use the `hasAttached` method. This method accepts an array of pivot table attribute names and values as its second argument:\n\n```php\nuse App\\Models\\Role;\nuse App\\Models\\User;\n\n$user = User::factory()\n    ->hasAttached(\n        Role::factory()->count(3),\n        ['active' => true]\n    )\n    ->create();\n```\n\nYou may provide a closure-based state transformation if your state change requires access to the related model:\n\n```php\n$user = User::factory()\n    ->hasAttached(\n        Role::factory()\n            ->count(3)\n            ->state(function (array $attributes, User $user) {\n                return ['name' => $user->name.' Role'];\n            }),\n        ['active' => true]\n    )\n    ->create();\n```\n\nIf you already have model instances that you would like to be attached to the models you are creating, you may pass the model instances to the `hasAttached` method. In this example, the same three roles will be attached to all three users:\n\n```php\n$roles = Role::factory()->count(3)->create();\n\n$users = User::factory()\n    ->count(3)\n    ->hasAttached($roles, ['active' => true])\n    ->create();\n```\n\n---\n\n## Eloquent Factories Factory Relationships Defining Relationships Within Factories\nEloquent Factories | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent-factories.md\n\n\n### Defining Relationships Within Factories\n\nTo define a relationship within your model factory, you will typically assign a new factory instance to the foreign key of the relationship. This is normally done for the \"inverse\" relationships such as `belongsTo` and `morphTo` relationships. For example, if you would like to create a new user when creating a post, you may do the following:\n\n```php\nuse App\\Models\\User;\n\n/**\n * Define the model's default state.\n *\n * @return array<string, mixed>\n */\npublic function definition(): array\n{\n    return [\n        'user_id' => User::factory(),\n        'title' => fake()->title(),\n        'content' => fake()->paragraph(),\n    ];\n}\n```\n\nIf the relationship's columns depend on the factory that defines it you may assign a closure to an attribute. The closure will receive the factory's evaluated attribute array:\n\n```php\n/**\n * Define the model's default state.\n *\n * @return array<string, mixed>\n */\npublic function definition(): array\n{\n    return [\n        'user_id' => User::factory(),\n        'user_type' => function (array $attributes) {\n            return User::find($attributes['user_id'])->type;\n        },\n        'title' => fake()->title(),\n        'content' => fake()->paragraph(),\n    ];\n}\n```\n\n---\n\n## Migrations Events\nMigrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md\n\n\n## Events\n\nFor convenience, each migration operation will dispatch an [event](/docs/{{version}}/events). All of the following events extend the base `Illuminate\\Database\\Events\\MigrationEvent` class:\n\n
\n\n| Class                                            | Description                                      |\n| ------------------------------------------------ | ------------------------------------------------ |\n| `Illuminate\\Database\\Events\\MigrationsStarted`   | A batch of migrations is about to be executed.   |\n| `Illuminate\\Database\\Events\\MigrationsEnded`     | A batch of migrations has finished executing.    |\n| `Illuminate\\Database\\Events\\MigrationStarted`    | A single migration is about to be executed.      |\n| `Illuminate\\Database\\Events\\MigrationEnded`      | A single migration has finished executing.       |\n| `Illuminate\\Database\\Events\\NoPendingMigrations` | A migration command found no pending migrations. |\n| `Illuminate\\Database\\Events\\SchemaDumped`        | A database schema dump has completed.            |\n| `Illuminate\\Database\\Events\\SchemaLoaded`        | An existing database schema dump has loaded.     |\n\n
\n\n---\n\n## Scout Searching Where Clauses Customizing the Eloquent Results Query\nScout | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/scout.md\n\n\n#### Customizing the Eloquent Results Query\n\nAfter Scout retrieves a list of matching Eloquent models from your application's search engine, Eloquent is used to retrieve all of the matching models by their primary keys. You may customize this query by invoking the `query` method. The `query` method accepts a closure that will receive the Eloquent query builder instance as an argument:\n\n```php\nuse App\\Models\\Order;\nuse Illuminate\\Database\\Eloquent\\Builder;\n\n$orders = Order::search('Star Trek')\n    ->query(fn (Builder $query) => $query->with('invoices'))\n    ->get();\n```\n\nWhen using a third-party engine, this callback is invoked after the relevant models have already been retrieved from the search engine, so it should not be used for \"filtering\" results — use [Scout where clauses](#where-clauses) instead. However, when using the database engine, the `query` method's constraints are applied directly to the database query, so you may use it for filtering as well.\n\n---\n\n## Migrations Indexes Foreign Key Constraints Toggling Foreign Key Constraints\nMigrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md\n\n\n#### Toggling Foreign Key Constraints\n\nYou may enable or disable foreign key constraints within your migrations by using the following methods:\n\n```php\nSchema::enableForeignKeyConstraints();\n\nSchema::disableForeignKeyConstraints();\n\nSchema::withoutForeignKeyConstraints(function () {\n    // Constraints disabled within this closure...\n});\n```\n\n> [!WARNING]\n> SQLite disables foreign key constraints by default. When using SQLite, make sure to [enable foreign key support](/docs/{{version}}/database#configuration) in your database configuration before attempting to create them in your migrations.\n\n---\n\n## Responses Redirects Redirecting to Named Routes Populating Parameters via Eloquent Models\nResponses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md\n\n\n#### Populating Parameters via Eloquent Models\n\nIf you are redirecting to a route with an \"ID\" parameter that is being populated from an Eloquent model, you may pass the model itself. The ID will be extracted automatically:\n\n```php\n// For a route with the following URI: /profile/{id}\n\nreturn redirect()->route('profile', [$user]);\n```\n\nIf you would like to customize the value that is placed in the route parameter, you can specify the column in the route parameter definition (`/profile/{id:slug}`) or you can override the `getRouteKey` method on your Eloquent model:\n\n```php\n/**\n * Get the value of the model's route key.\n */\npublic function getRouteKey(): mixed\n{\n    return $this->slug;\n}\n```\n\n---\n\n## Migrations Columns Available Column Types `foreignId()` {.collection-method}\nMigrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md\n\n\n#### `foreignId()` {.collection-method}\n\nThe `foreignId` method creates an `UNSIGNED BIGINT` equivalent column:\n\n```php\n$table->foreignId('user_id');\n```\n\n---\n\n## Database Testing Model Factories\nDatabase Testing | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/database-testing.md\n\n\n## Model Factories\n\nWhen testing, you may need to insert a few records into your database before executing your test. Instead of manually specifying the value of each column when you create this test data, Laravel allows you to define a set of default attributes for each of your [Eloquent models](/docs/{{version}}/eloquent) using [model factories](/docs/{{version}}/eloquent-factories).\n\nTo learn more about creating and utilizing model factories to create models, please consult the complete [model factory documentation](/docs/{{version}}/eloquent-factories). Once you have defined a model factory, you may utilize the factory within your test to create models:\n\n```php tab=Pest\nuse App\\Models\\User;\n\ntest('models can be instantiated', function () {\n    $user = User::factory()->create();\n\n    // ...\n});\n```\n\n```php tab=PHPUnit\nuse App\\Models\\User;\n\npublic function test_models_can_be_instantiated(): void\n{\n    $user = User::factory()->create();\n\n    // ...\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## Migrations Indexes Foreign Key Constraints Dropping Foreign Keys\nMigrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md\n\n\n#### Dropping Foreign Keys\n\nTo drop a foreign key, you may use the `dropForeign` method, passing the name of the foreign key constraint to be deleted as an argument. Foreign key constraints use the same naming convention as indexes. In other words, the foreign key constraint name is based on the name of the table and the columns in the constraint, followed by a \"\\_foreign\" suffix:\n\n```php\n$table->dropForeign('posts_user_id_foreign');\n```\n\nAlternatively, you may pass an array containing the column name that holds the foreign key to the `dropForeign` method. The array will be converted to a foreign key constraint name using Laravel's constraint naming conventions:\n\n```php\n$table->dropForeign(['user_id']);\n```\n\n---\n\n## Responses Creating Responses Eloquent Models and Collections\nResponses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md\n\n\n#### Eloquent Models and Collections\n\nYou may also return [Eloquent ORM](/docs/{{version}}/eloquent) models and collections directly from your routes and controllers. When you do, Laravel will automatically convert the models and collections to JSON responses while respecting the model's [hidden attributes](/docs/{{version}}/eloquent-serialization#hiding-attributes-from-json):\n\n```php\nuse App\\Models\\User;\n\nRoute::get('/user/{user}', function (User $user) {\n    return $user;\n});\n```\n\n---\n\n## Migrations Indexes Foreign Key Constraints\nMigrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md\n\n\n### Foreign Key Constraints\n\nLaravel also provides support for creating foreign key constraints, which are used to force referential integrity at the database level. For example, let's define a `user_id` column on the `posts` table that references the `id` column on a `users` table:\n\n```php\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nSchema::table('posts', function (Blueprint $table) {\n    $table->unsignedBigInteger('user_id');\n\n    $table->foreign('user_id')->references('id')->on('users');\n});\n```\n\nSince this syntax is rather verbose, Laravel provides additional, terser methods that use conventions to provide a better developer experience. When using the `foreignId` method to create your column, the example above can be rewritten like so:\n\n```php\nSchema::table('posts', function (Blueprint $table) {\n    $table->foreignId('user_id')->constrained();\n});\n```\n\nThe `foreignId` method creates an `UNSIGNED BIGINT` equivalent column, while the `constrained` method will use conventions to determine the table and column being referenced. If your table name does not match Laravel's conventions, you may manually provide it to the `constrained` method. In addition, the name that should be assigned to the generated index may be specified as well:\n\n```php\nSchema::table('posts', function (Blueprint $table) {\n    $table->foreignId('user_id')->constrained(\n        table: 'users', indexName: 'posts_user_id'\n    );\n});\n```\n\nYou may also specify the desired action for the \"on delete\" and \"on update\" properties of the constraint:\n\n```php\n$table->foreignId('user_id')\n    ->constrained()\n    ->onUpdate('cascade')\n    ->onDelete('cascade');\n```\n\nAn alternative, expressive syntax is also provided for these actions:\n\n
\n\n| Method                        | Description                                       |\n| ----------------------------- | ------------------------------------------------- |\n| `$table->cascadeOnUpdate();`  | Updates should cascade.                           |\n| `$table->restrictOnUpdate();` | Updates should be restricted.                     |\n| `$table->nullOnUpdate();`     | Updates should set the foreign key value to null. |\n| `$table->noActionOnUpdate();` | No action on updates.                             |\n| `$table->cascadeOnDelete();`  | Deletes should cascade.                           |\n| `$table->restrictOnDelete();` | Deletes should be restricted.                     |\n| `$table->nullOnDelete();`     | Deletes should set the foreign key value to null. |\n| `$table->noActionOnDelete();` | Prevents deletes if child records exist.          |\n\n
\n\nAny additional [column modifiers](#column-modifiers) must be called before the `constrained` method:\n\n```php\n$table->foreignId('user_id')\n    ->nullable()\n    ->constrained();\n```\n\n---\n\n## Upgrade Upgrading To 12.0 From 11.x Database Database Constructor Signature Changes\nUpgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md\n\n\n#### Database Constructor Signature Changes\n\n**Likelihood Of Impact: Very Low**\n\nIn Laravel 12, several low-level database classes now require an `Illuminate\\Database\\Connection` instance to be provided via their constructors.\n\n**These changes are primarily applicable to database package maintainers - it is extremely unlikely any of these changes affect normal application development.**\n\n`Illuminate\\Database\\Schema\\Blueprint`\n\nThe constructor of the `Illuminate\\Database\\Schema\\Blueprint` class now expects a `Connection` instance as its first argument. This primarily affects applications or packages that manually instantiate `Blueprint` instances.\n\n`Illuminate\\Database\\Grammar`\n\nThe constructor of the `Illuminate\\Database\\Grammar` class also now requires a `Connection` instance. In previous versions, the connection was assigned after construction using the `setConnection()` method. This method has been removed in Laravel 12:\n\n```php\n// Laravel <= 11.x\n$grammar = new MySqlGrammar;\n$grammar->setConnection($connection);\n\n// Laravel >= 12.x\n$grammar = new MySqlGrammar($connection);\n````\n\nIn addition, the following APIs have been removed or deprecated:\n\n
\n\n- The `Blueprint::getPrefix()` method is deprecated.\n- The `Connection::withTablePrefix()` method has been removed.\n- The `Grammar::getTablePrefix()` and `setTablePrefix()` methods are deprecated.\n- The `Grammar::setConnection()` method has been removed.\n\n
\n\nWhen working with table prefixes, you should now retrieve them directly from the database connection:\n\n```php\n$prefix = $connection->getTablePrefix();\n```\n\nIf you maintain custom database drivers, schema builders, or grammar implementations, you should review their constructors and ensure a `Connection` instance is provided.\n\n---\n\n## Pagination Basic Usage Paginating Eloquent Results\nPagination | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/pagination.md\n\n\n### Paginating Eloquent Results\n\nYou may also paginate [Eloquent](/docs/{{version}}/eloquent) queries. In this example, we will paginate the `App\\Models\\User` model and indicate that we plan to display 15 records per page. As you can see, the syntax is nearly identical to paginating query builder results:\n\n```php\nuse App\\Models\\User;\n\n$users = User::paginate(15);\n```\n\nOf course, you may call the `paginate` method after setting other constraints on the query, such as `where` clauses:\n\n```php\n$users = User::where('votes', '>', 100)->paginate(15);\n```\n\nYou may also use the `simplePaginate` method when paginating Eloquent models:\n\n```php\n$users = User::where('votes', '>', 100)->simplePaginate(15);\n```\n\nSimilarly, you may use the `cursorPaginate` method to cursor paginate Eloquent models:\n\n```php\n$users = User::where('votes', '>', 100)->cursorPaginate(15);\n```\n\n---\n\n## Migrations Columns Available Column Types `enum()` {.collection-method}\nMigrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md\n\n\n#### `enum()` {.collection-method}\n\nThe `enum` method creates a `ENUM` equivalent column with the given valid values:\n\n```php\n$table->enum('difficulty', ['easy', 'hard']);\n```\n\nOf course, you may use the `Enum::cases()` method instead of manually defining an array of allowed values:\n\n```php\nuse App\\Enums\\Difficulty;\n\n$table->enum('difficulty', Difficulty::cases());\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## Upgrade Upgrading To 12.0 From 11.x Database Multi-Schema Database Inspecting\nUpgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md\n\n\n#### Multi-Schema Database Inspecting\n\n**Likelihood Of Impact: Low**\n\nThe `Schema::getTables()`, `Schema::getViews()`, and `Schema::getTypes()` methods now include the results from all schemas by default. You may pass the `schema` argument to retrieve the result for the given schema only:\n\n```php\n// All tables on all schemas...\n$tables = Schema::getTables();\n\n// All tables on the 'main' schema...\n$tables = Schema::getTables(schema: 'main');\n\n// All tables on the 'main' and 'blog' schemas...\n$tables = Schema::getTables(schema: ['main', 'blog']);\n```\n\nThe `Schema::getTableListing()` method now returns schema-qualified table names by default. You may pass the `schemaQualified` argument to change the behavior as desired:\n\n```php\n$tables = Schema::getTableListing();\n// ['main.migrations', 'main.users', 'blog.posts']\n\n$tables = Schema::getTableListing(schema: 'main');\n// ['main.migrations', 'main.users']\n\n$tables = Schema::getTableListing(schema: 'main', schemaQualified: false);\n// ['migrations', 'users']\n```\n\nThe `db:table` and `db:show` commands now output the results of all schemas on MySQL, MariaDB, and SQLite, just like PostgreSQL and SQL Server.\n\n---\n\n## Migrations Indexes Dropping Indexes\nMigrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md\n\n\n### Dropping Indexes\n\nTo drop an index, you must specify the index's name. By default, Laravel automatically assigns an index name based on the table name, the name of the indexed column, and the index type. Here are some examples:\n\n
\n\n| Command                                                  | Description                                                 |\n| -------------------------------------------------------- | ----------------------------------------------------------- |\n| `$table->dropPrimary('users_id_primary');`               | Drop a primary key from the \"users\" table.                  |\n| `$table->dropUnique('users_email_unique');`              | Drop a unique index from the \"users\" table.                 |\n| `$table->dropIndex('geo_state_index');`                  | Drop a basic index from the \"geo\" table.                    |\n| `$table->dropFullText('posts_body_fulltext');`           | Drop a full text index from the \"posts\" table.              |\n| `$table->dropSpatialIndex('geo_location_spatialindex');` | Drop a spatial index from the \"geo\" table  (except SQLite). |\n\n
\n\nIf you pass an array of columns into a method that drops indexes, the conventional index name will be generated based on the table name, columns, and index type:\n\n```php\nSchema::table('geo', function (Blueprint $table) {\n    $table->dropIndex(['state']); // Drops index 'geo_state_index'\n});\n```\n\n---\n\n## Pagination Basic Usage Paginating Eloquent Results Multiple Paginator Instances per Page\nPagination | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/pagination.md\n\n\n#### Multiple Paginator Instances per Page\n\nSometimes you may need to render two separate paginators on a single screen that is rendered by your application. However, if both paginator instances use the `page` query string parameter to store the current page, the two paginators will conflict. To resolve this conflict, you may pass the name of the query string parameter you wish to use to store the paginator's current page via the third argument provided to the `paginate`, `simplePaginate`, and `cursorPaginate` methods:\n\n```php\nuse App\\Models\\User;\n\n$users = User::where('votes', '>', 100)->paginate(\n    $perPage = 15, $columns = ['*'], $pageName = 'users'\n);\n```\n\n---\n\n## Testing Running Tests Running Tests in Parallel Parallel Testing and Databases\nTesting | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/testing.md\n\n\n#### Parallel Testing and Databases\n\nAs long as you have configured a primary database connection, Laravel automatically handles creating and migrating a test database for each parallel process that is running your tests. The test databases will be suffixed with a process token which is unique per process. For example, if you have two parallel test processes, Laravel will create and use `your_db_test_1` and `your_db_test_2` test databases.\n\nBy default, test databases persist between calls to the `test` Artisan command so that they can be used again by subsequent `test` invocations. However, you may re-create them using the `--recreate-databases` option:\n\n```shell\nphp artisan test --parallel --recreate-databases\n```\n\n---\n\n## Migrations Indexes Creating Indexes Online Index Creation\nMigrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md\n\n\n#### Online Index Creation\n\nBy default, creating an index on a large table can lock the table and block reads or writes while the index is being built. When using PostgreSQL or SQL Server, you may chain the `online` method onto an index definition to create the index without locking the table, allowing your application to continue reading and writing data during index creation:\n\n```php\n$table->string('email')->unique()->online();\n```\n\nWhen using PostgreSQL, this adds the `CONCURRENTLY` option to the index creation statement. When using SQL Server, this adds the `WITH (online = on)` option.\n\n---\n\n## Upgrade Upgrading To 12.0 From 11.x Authentication Updated `DatabaseTokenRepository` Constructor Signature\nUpgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md\n\n\n#### Updated `DatabaseTokenRepository` Constructor Signature\n\n**Likelihood Of Impact: Very Low**\n\nThe constructor of the `Illuminate\\Auth\\Passwords\\DatabaseTokenRepository` class now expects the `$expires` parameter to be given in seconds, rather than minutes.\n\n---\n\n## Eloquent Events Muting Events Saving a Single Model Without Events\nEloquent | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent.md\n\n\n#### Saving a Single Model Without Events\n\nSometimes you may wish to \"save\" a given model without dispatching any events. You may accomplish this using the `saveQuietly` method:\n\n```php\n$user = User::findOrFail(1);\n\n$user->name = 'Victoria Faith';\n\n$user->saveQuietly();\n```\n\nYou may also \"update\", \"delete\", \"soft delete\", \"restore\", and \"replicate\" a given model without dispatching any events:\n\n```php\n$user->deleteQuietly();\n$user->forceDeleteQuietly();\n$user->restoreQuietly();\n```\n\n---\n\n## Migrations Columns Available Column Types `foreignUlid()` {.collection-method}\nMigrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md\n\n\n#### `foreignUlid()` {.collection-method}\n\nThe `foreignUlid` method creates a `ULID` equivalent column:\n\n```php\n$table->foreignUlid('user_id');\n```\n\n---\n\n## Migrations Indexes Renaming Indexes\nMigrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md\n\n\n### Renaming Indexes\n\nTo rename an index, you may use the `renameIndex` method provided by the schema builder blueprint. This method accepts the current index name as its first argument and the desired name as its second argument:\n\n```php\n$table->renameIndex('from', 'to')\n```\n\n---\n\n## Structure The Root Directory The Database Directory\nStructure | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/structure.md\n\n\n### The Database Directory\n\nThe `database` directory contains your database migrations, model factories, and seeds. If you wish, you may also use this directory to hold an SQLite database.\n\n---\n\n## Eloquent Events Muting Events\nEloquent | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent.md\n\n\n### Muting Events\n\nYou may occasionally need to temporarily \"mute\" all events fired by a model. You may achieve this using the `withoutEvents` method. The `withoutEvents` method accepts a closure as its only argument. Any code executed within this closure will not dispatch model events, and any value returned by the closure will be returned by the `withoutEvents` method:\n\n```php\nuse App\\Models\\User;\n\n$user = User::withoutEvents(function () {\n    User::findOrFail(1)->delete();\n\n    return User::find(2);\n});\n```\n\n---\n\n## Migrations Indexes Creating Indexes Available Index Types\nMigrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md\n\n\n#### Available Index Types\n\nLaravel's schema builder blueprint class provides methods for creating each type of index supported by Laravel. Each index method accepts an optional second argument to specify the name of the index. If omitted, the name will be derived from the names of the table and column(s) used for the index, as well as the index type. Each of the available index methods is described in the table below:\n\n
\n\n| Command                                          | Description                                                    |\n| ------------------------------------------------ | -------------------------------------------------------------- |\n| `$table->primary('id');`                         | Adds a primary key.                                            |\n| `$table->primary(['id', 'parent_id']);`          | Adds composite keys.                                           |\n| `$table->unique('email');`                       | Adds a unique index.                                           |\n| `$table->index('state');`                        | Adds an index.                                                 |\n| `$table->fullText('body');`                      | Adds a full text index (MariaDB / MySQL / PostgreSQL).         |\n| `$table->fullText('body')->language('english');` | Adds a full text index of the specified language (PostgreSQL). |\n| `$table->spatialIndex('location');`              | Adds a spatial index (except SQLite).                          |\n\n
\n\n---\n\n## Session Introduction Driver Prerequisites Database\nSession | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/session.md\n\n\n#### Database\n\nWhen using the `database` session driver, you will need to ensure that you have a database table to contain the session data. Typically, this is included in Laravel's default `0001_01_01_000000_create_users_table.php` [database migration](/docs/{{version}}/migrations); however, if for any reason you do not have a `sessions` table, you may use the `make:session-table` Artisan command to generate this migration:\n\n```shell\nphp artisan make:session-table\n\nphp artisan migrate\n```\n\n---\n\n## Migrations Indexes Creating Indexes\nMigrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md\n\n\n### Creating Indexes\n\nThe Laravel schema builder supports several types of indexes. The following example creates a new `email` column and specifies that its values should be unique. To create the index, we can chain the `unique` method onto the column definition:\n\n```php\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nSchema::table('users', function (Blueprint $table) {\n    $table->string('email')->unique();\n});\n```\n\nAlternatively, you may create the index after defining the column. To do so, you should call the `unique` method on the schema builder blueprint. This method accepts the name of the column that should receive a unique index:\n\n```php\n$table->unique('email');\n```\n\nYou may even pass an array of columns to an index method to create a compound (or composite) index:\n\n```php\n$table->index(['account_id', 'created_at']);\n```\n\nWhen creating an index, Laravel will automatically generate an index name based on the table, column names, and the index type, but you may pass a second argument to the method to specify the index name yourself:\n\n```php\n$table->unique('email', 'unique_email');\n```\n\n---\n\n## Eloquent Events Observers Observers and Database Transactions\nEloquent | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent.md\n\n\n#### Observers and Database Transactions\n\nWhen models are being created within a database transaction, you may want to instruct an observer to only execute its event handlers after the database transaction is committed. You may accomplish this by implementing the `ShouldHandleEventsAfterCommit` interface on your observer. If a database transaction is not in progress, the event handlers will execute immediately:\n\n```php\n<?php\n\nnamespace App\\Observers;\n\nuse App\\Models\\User;\nuse Illuminate\\Contracts\\Events\\ShouldHandleEventsAfterCommit;\n\nclass UserObserver implements ShouldHandleEventsAfterCommit\n{\n    /**\n     * Handle the User \"created\" event.\n     */\n    public function created(User $user): void\n    {\n        // ...\n    }\n}\n```\n\n---\n\n## Migrations Columns Available Column Types `tinyIncrements()` {.collection-method}\nMigrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md\n\n\n#### `tinyIncrements()` {.collection-method}\n\nThe `tinyIncrements` method creates an auto-incrementing `UNSIGNED TINYINT` equivalent column as a primary key:\n\n```php\n$table->tinyIncrements('id');\n```\n\n---\n\n## Eloquent Events Observers Defining Observers\nEloquent | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent.md\n\n\n#### Defining Observers\n\nIf you are listening for many events on a given model, you may use observers to group all of your listeners into a single class. Observer classes have method names which reflect the Eloquent events you wish to listen for. Each of these methods receives the affected model as their only argument. The `make:observer` Artisan command is the easiest way to create a new observer class:\n\n```shell\nphp artisan make:observer UserObserver --model=User\n```\n\nThis command will place the new observer in your `app/Observers` directory. If this directory does not exist, Artisan will create it for you. Your fresh observer will look like the following:\n\n```php\n<?php\n\nnamespace App\\Observers;\n\nuse App\\Models\\User;\n\nclass UserObserver\n{\n    /**\n     * Handle the User \"created\" event.\n     */\n    public function created(User $user): void\n    {\n        // ...\n    }\n\n    /**\n     * Handle the User \"updated\" event.\n     */\n    public function updated(User $user): void\n    {\n        // ...\n    }\n\n    /**\n     * Handle the User \"deleted\" event.\n     */\n    public function deleted(User $user): void\n    {\n        // ...\n    }\n\n    /**\n     * Handle the User \"restored\" event.\n     */\n    public function restored(User $user): void\n    {\n        // ...\n    }\n\n    /**\n     * Handle the User \"forceDeleted\" event.\n     */\n    public function forceDeleted(User $user): void\n    {\n        // ...\n    }\n}\n```\n\nTo register an observer, you may place the `ObservedBy` attribute on the corresponding model:\n\n```php\nuse App\\Observers\\UserObserver;\nuse Illuminate\\Database\\Eloquent\\Attributes\\ObservedBy;\n\n#[ObservedBy([UserObserver::class])]\nclass User extends Authenticatable\n{\n    //\n}\n```\n\nOr, you may manually register an observer by invoking the `observe` method on the model you wish to observe. You may register observers in the `boot` method of your application's `AppServiceProvider` class:\n\n```php\nuse App\\Models\\User;\nuse App\\Observers\\UserObserver;\n\n/**\n * Bootstrap any application services.\n */\npublic function boot(): void\n{\n    User::observe(UserObserver::class);\n}\n```\n\n> [!NOTE]\n> There are additional events an observer can listen to, such as `saving` and `retrieved`. These events are described within the [events](#events) documentation.\n\n_[Truncated]_"}]


You are Worker A for Phase 2 catalog in /Users/fabianwesner/Herd/shop. You are not alone in the codebase; do not revert or overwrite edits made by others. Own only this write scope: app/Enums catalog enum files, app/Models catalog model files, database/migrations catalog migration files, database/factories catalog factories, database/seeders catalog seeders. Implement the catalog foundation from specs/01-DATABASE-SCHEMA.md Epic 2 and specs/09-IMPLEMENTATION-ROADMAP.md Phase 2: products, product_options, product_option_values, product_variants, variant_option_values, inventory_items, collections, collection_products, product_media. Include Eloquent relationships, casts, BelongsToStore where required, factories with useful states/hooks, and seeders for deterministic sample catalog data attached to the existing Acme Fashion store. Use Laravel conventions and existing patterns from the Phase 1 code. Do not edit routes, services, tests, specs/progress.md, or UI files. Run focused checks if feasible. Return a summary and the exact file paths changed.


Prompt: You are Worker A for Phase 2 catalog in /Users/fabianwesner/Herd/shop. You are not alone in the codebase; do not revert or overwrite edits made by others. Own only this write scope: app/Enums catalog enum files, app/Models catalog model files, database/migrations catalog migration files, database/factories catalog factories, database/seeders catalog seeders. Implement the catalog foundation from specs/01-DATABASE-SCHEMA.md Epic 2 and specs/09-IMPLEMENTATION-ROADMAP.md Phase 2: products, product_options, product_option_values, product_variants, variant_option_values, inventory_items, collections, collection_products, product_media. Include Eloquent relationships, casts, BelongsToStore where required, factories with useful states/hooks, and seeders for deterministic sample catalog data attached to the existing Acme Fashion store. Use Laravel conventions and existing patterns from the Phase 1 code. Do not edit routes, services, tests, specs/progress.md, or UI files. Run focused checks if feasible. Return a summary and the exact file paths changed.


Worker A is handling the catalog schema/model/factory/seed layer in a disjoint write scope. While that runs, I’m extracting the Phase 2 service/test acceptance details locally so I can integrate and verify instead of duplicating the same files.


sed -n '245,442p' specs/09-IMPLEMENTATION-ROADMAP.md


sed -n '111,260p' specs/05-BUSINESS-LOGIC.md


sed -n '260,348p' specs/05-BUSINESS-LOGIC.md


sed -n '184,360p' specs/01-DATABASE-SCHEMA.md


sed -n '360,470p' specs/01-DATABASE-SCHEMA.md


sed -n '260,348p' specs/05-BUSINESS-LOGIC.md

    active --> converted : Checkout completed successfully
    active --> abandoned : Cleanup job (14 days inactive)
```

**Cart identification:**
- Guest users: cart ID stored in session cookie.
- Authenticated customers: cart linked via `customer_id` foreign key.

The guest cart is bound to the session using the key `cart_id`. When a guest visits the storefront, a cart is created on first add-to-cart and the cart's ID is stored in `session('cart_id')`. On customer login, if a session cart exists, it is merged with any existing customer cart (the session cart's items are added to the customer cart, preferring the higher quantity for duplicate variants). After merge, the session key is cleared.

**Cart merge on login:**

```
PROCEDURE mergeGuestCart(guestCart, customerCart):
    FOR EACH line IN guestCart.lines:
        existingLine = customerCart.findLineByVariant(line.variant_id)
        IF existingLine EXISTS:
            existingLine.quantity = MAX(existingLine.quantity, line.quantity)
            RECALCULATE existingLine amounts
        ELSE:
            MOVE line to customerCart
    MARK guestCart as abandoned
    RECALCULATE all line amounts on customerCart
```

### 4.2 Cart Operations

**Create cart:**
- Accepts: store, optional customer
- Sets: store_id, currency from store's default_currency, initial cart_version = 1

**Add line - validations (in order):**

1. Variant exists and belongs to the current store (through its product).
2. Product has status = `active`.
3. Variant has status = `active`.
4. Inventory policy check: if policy = `deny`, verify available >= requested quantity.
5. If the variant already exists in the cart, increment existing line quantity instead of creating a duplicate.
6. Set `unit_price_amount` from the variant's `price_amount`.
7. Calculate: `line_subtotal_amount = unit_price_amount * quantity`.
8. Set `line_discount_amount = 0` (recalculated by discount engine during checkout).
9. Calculate: `line_total_amount = line_subtotal_amount - line_discount_amount`.
10. Increment `cart_version`.

**Update quantity:**
- Validate new quantity > 0. If 0, remove the line instead.
- Re-check inventory policy for the new quantity.
- Recalculate line amounts.
- Increment `cart_version`.

**Remove line:**
- Delete the `cart_lines` record.
- Increment `cart_version`.

### 4.3 Cart Version (Optimistic Concurrency)

Every cart mutation increments `cart_version` by 1.

**For API clients:**
- Client sends `expected_version` with each mutation request.
- If cart's current version does not match the expected version, return HTTP 409 Conflict with the current cart state in the response body.
- This prevents race conditions when multiple browser tabs or devices modify the same cart.

**For Livewire/session-based flows:**
- Still implemented for consistency, but less critical since requests are serialized per session.

### 4.4 Line Amount Calculation

```
line_subtotal_amount = unit_price_amount * quantity
line_discount_amount = 0  (updated by discount engine during checkout)
line_total_amount    = line_subtotal_amount - line_discount_amount
```

All values are integers in minor currency units.

### 4.5 Cart Expiration

**Job:** `App\Jobs\CleanupAbandonedCarts`

- Schedule: daily
- Finds carts where status = `active` AND updated_at < (now - 14 days). The 14-day threshold is configurable via `store_settings`.
- Marks them as `abandoned`.
- Releases any reserved inventory associated with active checkouts for these carts.

---

## 5. Pricing Engine


sed -n '245,442p' specs/09-IMPLEMENTATION-ROADMAP.md

### Phase 2: Catalog (Products, Variants, Inventory, Collections, Media)

**Priority:** HIGH - storefront and orders depend on this.

#### Step 2.1: Migrations (Batch 3-5)

Create in dependency order:

1. `create_products_table` - FK: `store_id`
2. `create_product_options_table` - FK: `product_id`
3. `create_product_option_values_table` - FK: `product_option_id`
4. `create_product_variants_table` - FK: `product_id`
5. `create_variant_option_values_table` - composite PK: `variant_id` + `product_option_value_id`
6. `create_inventory_items_table` - FK: `store_id`, `variant_id`
7. `create_collections_table` - FK: `store_id`
8. `create_collection_products_table` - composite PK: `collection_id` + `product_id`
9. `create_product_media_table` - FK: `product_id`

#### Step 2.2: Models with Relationships

**Files to create:**

| Type | Name | Purpose |
|------|------|---------|
| Model (with migration, factory, seeder) | `Product` | Product entity |
| Model (with migration, factory, seeder) | `ProductOption` | Product option (e.g., Size, Color) |
| Model (with migration, factory, seeder) | `ProductOptionValue` | Option values (e.g., S, M, L) |
| Model (with migration, factory, seeder) | `ProductVariant` | Product variant (SKU-level) |
| Model (with migration, factory, seeder) | `InventoryItem` | Inventory tracking per variant |
| Model (with migration, factory, seeder) | `Collection` | Product collection/category |
| Model (with migration, factory, seeder) | `ProductMedia` | Product images and videos |

**Relationship definitions:**

| Model | Key Relationships |
|-------|-------------------|
| `Product` | `hasMany(ProductVariant)`, `hasMany(ProductOption)`, `hasMany(ProductMedia)`, `belongsToMany(Collection)` via `collection_products` |
| `ProductOption` | `belongsTo(Product)`, `hasMany(ProductOptionValue)` |
| `ProductOptionValue` | `belongsTo(ProductOption)` |
| `ProductVariant` | `belongsTo(Product)`, `hasOne(InventoryItem)`, `belongsToMany(ProductOptionValue)` via `variant_option_values` |
| `InventoryItem` | `belongsTo(ProductVariant)` |
| `Collection` | `belongsToMany(Product)` via `collection_products` |
| `ProductMedia` | `belongsTo(Product)` |

**Enums to create:**

| Enum | Values |
|------|--------|
| `ProductStatus` | `Draft`, `Active`, `Archived` |
| `VariantStatus` | `Active`, `Archived` |
| `CollectionStatus` | `Draft`, `Active`, `Archived` |
| `MediaType` | `Image`, `Video` |
| `MediaStatus` | `Processing`, `Ready`, `Failed` |
| `InventoryPolicy` | `Deny`, `Continue` |

#### Step 2.3: Product Service

**File to create:** `App\Services\ProductService`

| Method | Description |
|--------|-------------|
| `create(Store $store, array $data): Product` | Creates product with nested variants/options |
| `update(Product $product, array $data): Product` | Updates product |
| `transitionStatus(Product $product, ProductStatus $newStatus): void` | Validates transitions per state machine |
| `delete(Product $product): void` | Hard delete only if draft with no order references; otherwise throws |

**File to create:** `App\Services\VariantMatrixService`

| Method | Description |
|--------|-------------|
| `rebuildMatrix(Product $product): void` | Computes cartesian product, creates missing variants, archives orphaned ones |

**File to create:** `App\Support\HandleGenerator`

| Method | Description |
|--------|-------------|
| `generate(string $title, string $table, int $storeId, ?int $excludeId = null): string` | Generates unique slugs |

#### Step 2.4: Inventory Service

**File to create:** `App\Services\InventoryService`

| Method | Description |
|--------|-------------|
| `checkAvailability(InventoryItem $item, int $quantity): bool` | Checks if sufficient available inventory exists |
| `reserve(InventoryItem $item, int $quantity): void` | Increments `quantity_reserved` |
| `release(InventoryItem $item, int $quantity): void` | Decrements `quantity_reserved` |
| `commit(InventoryItem $item, int $quantity): void` | Decrements both `on_hand` and `reserved` |
| `restock(InventoryItem $item, int $quantity): void` | Increments `quantity_on_hand` |

All operations wrapped in database transactions. Throws `InsufficientInventoryException` when policy is `deny` and available < quantity.

#### Step 2.5: Media Upload

**File to create:** `App\Jobs\ProcessMediaUpload`

Requirements:
- Resizes images to standard sizes (thumbnail 150x150, medium 600x600, large 1200x1200)
- Updates `ProductMedia` status from `processing` to `ready` (or `failed`)

Livewire file upload via `WithFileUploads` trait. Stored on local `public` disk.

---

### Phase 3: Themes, Pages, Navigation, Storefront Layout

**Priority:** HIGH - storefront rendering depends on this.

#### Step 3.1: Migrations (Batch 3)

1. `create_themes_table` - FK: `store_id`
2. `create_theme_files_table` - FK: `theme_id`
3. `create_theme_settings_table` - PK: `theme_id`
4. `create_pages_table` - FK: `store_id`
5. `create_navigation_menus_table` - FK: `store_id`
6. `create_navigation_items_table` - FK: `menu_id`

#### Step 3.2: Models

**Files to create:**

| Type | Name | Purpose |
|------|------|---------|
| Model (with migration, factory, seeder) | `Theme` | Store theme entity |
| Model (with migration, factory, seeder) | `ThemeFile` | Individual theme file |
| Model (with migration, factory, seeder) | `ThemeSettings` | Theme configuration |
| Model (with migration, factory, seeder) | `Page` | CMS page |
| Model (with migration, factory, seeder) | `NavigationMenu` | Navigation menu entity |
| Model (with migration, factory, seeder) | `NavigationItem` | Individual navigation item |

**Relationship definitions:**

| Model | Key Relationships |
|-------|-------------------|
| `Theme` | `belongsTo(Store)`, `hasMany(ThemeFile)`, `hasOne(ThemeSettings)` |
| `ThemeFile` | `belongsTo(Theme)` |
| `ThemeSettings` | `belongsTo(Theme)` |
| `Page` | `belongsTo(Store)` |
| `NavigationMenu` | `belongsTo(Store)`, `hasMany(NavigationItem)` |
| `NavigationItem` | `belongsTo(NavigationMenu)` |

**Enums to create:**

| Enum | Values |
|------|--------|
| `ThemeStatus` | `Draft`, `Published` |
| `PageStatus` | `Draft`, `Published`, `Archived` |
| `NavigationItemType` | `Link`, `Page`, `Collection`, `Product` |

#### Step 3.3: Storefront Blade Layout

**Directory structure for `resources/views/storefront/`:**

| Directory | Files |
|-----------|-------|
| `layouts/` | `app.blade.php` |
| `pages/` | `home.blade.php`, `collection.blade.php`, `product.blade.php`, `cart.blade.php`, `search.blade.php`, `page.blade.php` |
| `checkout/` | `index.blade.php`, `confirmation.blade.php` |
| `account/` | `login.blade.php`, `register.blade.php`, `dashboard.blade.php` |
| `account/orders/` | `index.blade.php`, `show.blade.php` |
| `account/addresses/` | `index.blade.php` |
| `errors/` | `404.blade.php`, `503.blade.php` |
| `components/` | `product-card.blade.php`, `price.blade.php`, `badge.blade.php`, `quantity-selector.blade.php`, `address-form.blade.php`, `order-summary.blade.php`, `breadcrumbs.blade.php`, `pagination.blade.php` |

Base layout (`app.blade.php`): header with nav, announcement bar, main content, footer, cart drawer. All with dark mode support via `dark:` prefix.

**ThemeSettings Service:** Singleton registered in `AppServiceProvider`, loads and caches active theme settings for the current store.

#### Step 3.4: Storefront Livewire Components

**Files to create:**

| Component | Purpose |
|-----------|---------|
| `Storefront\Home` | Dynamic sections based on theme settings |
| `Storefront\Collections\Index` | All collections grid |
| `Storefront\Collections\Show` | Product grid with filters/sort/pagination |
| `Storefront\Products\Show` | Variant selection, image gallery, add-to-cart |
| `Storefront\Cart\Show` | Full cart page with line items and totals |
| `Storefront\CartDrawer` | Slide-out cart drawer (always in layout) |
| `Storefront\Search\Index` | Search results with filters |
| `Storefront\Search\Modal` | Search modal with autocomplete |
| `Storefront\Pages\Show` | Static CMS page rendering |

#### Step 3.5: Navigation Service

**File to create:** `App\Services\NavigationService`

| Method | Description |
|--------|-------------|
| `buildTree(NavigationMenu $menu): array` | Builds navigation trees from navigation_menus / navigation_items |
| `resolveUrl(NavigationItem $item): string` | Resolves URLs for page, collection, product type items |

Caches per store with 5-minute TTL.

---

### Phase 4: Cart, Checkout, Discounts, Shipping, Taxes