Factory States

Factory States, Sequences, and Relationships

A state is a named method that overrides attributes: OrderFactory::shipped() returns $this->state(['status' => 'shipped']). afterCreating() callbacks run once the model exists; withItems() attaches products through the order_items pivot (Many to Many):

A state method with an afterCreating callback, from OrderFactoryPHP
public function withItems(int $lines = 2): static
{
    return $this->afterCreating(function (Order $order) use ($lines) {
        foreach (Product::inRandomOrder()->limit($lines)->get() as $product) {
            $order->products()->attach($product, [
                'quantity' => fake()->numberBetween(1, 3), 'unit_price' => $product->price,
            ]);
        }
    });
}

In DemoShopSeeder, sequence() cycles values or calls a closure with $index, and recycle() hands existing models to any definition that would call another factory:

The run() method of DemoShopSeederPHP
$categories = Category::whereNotNull('parent_id')->get();
$customers = Customer::factory()->count(8)->create();
$products = Product::factory()->count(12)->recycle($categories)
    ->sequence(fn (Sequence $s) => ['sku' => sprintf('BK-%04d', $s->index + 1)])
    ->create();
Product::factory()->count(2)->outOfStock()->recycle($categories)->create();
Order::factory()->count(20)->recycle($customers)->withItems(2)
    ->sequence(['status' => 'paid'], ['status' => 'shipped'], ['status' => 'pending'])
    ->create();
Review::factory()->count(15)->recycle($customers)->recycle($products)->create();

The result, counted in MySQL 524 : 8 customers, 14 products, 20 orders (7 paid, 7 shipped, 6 pending), 40 order lines and 15 reviews. Without recycle(), the orders and reviews would have added 35 customers and 15 products. States, for() and callbacks combine; in tinker:

A state, a parent and a callback togetherSQL
use App\Models\{Customer, Order};
$o = Order::factory()->shipped()->for(Customer::first())->withItems(3)->create();
echo $o->status, ' | ', $o->customer->name, ' | ', $o->products()->count(), " lines\n";
Output
shipped | Jermaine Doyle | 3 lines

The other direction is has(Order::factory()->count(3)) or the magic hasOrders(3); hasAttached() fills pivot columns, and a built-in trashed() state makes soft-deleted models.