skip to content

In a Laravel model factory, how do states and sequence() vary generated records, and how do you define a reusable named state?

level: middleimportance: should knowfreq 46%

answer

  1. layers merged over definition()
  2. method returning $this->state(...)
  3. closure receives attributes so far
  4. round-robin: index modulo count
  5. create() arguments applied last

basics

~20 s

A state is an attribute override layered on top of definition(); a named state is a factory method that returns $this->state(...). sequence() adds a state that cycles through its values one record at a time.

solid answer

~40 s

`definition()` gives the defaults; `state([...])` or `state(fn (array $attributes) => [...])` merges overrides on top, and later states win. A **named state** is a method on the factory, such as `cancelled()`, that returns `$this->state(...)` so it chains: `Appointment::factory()->count(5)->cancelled()->create()`. The closure receives the attributes computed so far and, when built through `has()`, the parent model. `sequence(['status' => 'booked'], ['status' => 'no_show'])` wraps a `Sequence` whose values are used round-robin, record by record; a closure value receives the `Sequence`, whose `$index` counts records so far. `forEachSequence()` also sets the count to the number of values. Attributes passed to `create()` or `make()` are applied last, so they override every state. Soft-deletable models get a built-in `trashed()` state.

code

php · 24 lines
php
<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

class AppointmentFactory extends Factory
{
    public function definition(): array
    {
        return [
            'starts_at' => fake()->dateTimeBetween('now', '+1 month'),
            'status' => 'booked',
        ];
    }

    public function cancelled(): static
    {
        return $this->state(fn (array $attributes) => [
            'status' => 'cancelled',
            'cancelled_at' => now(),
        ]);
    }
}

go deeper

for a junior

Recall that state() overrides definition(), that a named state is a method returning $this->state(...), and that sequence() alternates values.

for a middle

Explain the merge order: definition, for() keys, chained states, then call-site attributes, and how Sequence cycles with its index.

for a senior

Show that you keep definition() valid and minimal and move variants into composable named states, so seeders stay readable as the model grows.

for a principal

Weigh how many named states a shared factory should carry against scenario-specific overrides, so the factory stays a vocabulary rather than a dumping ground.

## Defaults, then layers A Laravel **model factory** starts from `definition()`, which returns the model's default attributes, typically generated with `fake()` so each record differs. A **state** is a transformation layered on top of those defaults. Internally the factory keeps an ordered list of states and reduces them over the definition with `array_merge`, so: - each state sees the attributes produced so far; - a later state overrides an earlier one for the same key; - attributes passed to `make([...])` or `create([...])` are added as the final state, so they win over everything. Because each factory call returns a new factory instance, states compose freely: `Appointment::factory()->cancelled()->virtual()` applies both. ## Named states A **named state** is a public method on the factory that returns `$this->state(...)`. The skeleton's `UserFactory::unverified()` is the canonical example. For a hospital-appointments app: 1. Add `public function cancelled(): static` to `AppointmentFactory`. 2. Return `$this->state(fn (array $attributes) => ['status' => 'cancelled', 'cancelled_at' => now()])`. 3. Call it anywhere: `Appointment::factory()->count(20)->cancelled()->create()`. A state closure receives two arguments: the attribute array so far and, when the factory is running as a child through `has()`, the parent model. That makes derived values easy, such as setting `ends_at` from the `starts_at` the definition chose. A named state can also chain `afterMaking()` or `afterCreating()` callbacks that apply only when that state is used. Soft-deletable models get a built-in **`trashed()`** state that fills the deleted-at column, with no method to write. ## Sequences A **sequence** is a state that returns a different value for each record. `sequence(...$values)` wraps them in `Illuminate\Database\Eloquent\Factories\Sequence` and adds it as a state. Each time a record is built, the sequence returns the value at `index % count` and then increments `index`: - `count(6)` with three statuses yields each status twice, cycling in order; - a value can be a closure, called with the `Sequence` instance, so `fn (Sequence $s) => ['slot' => $s->index + 1]` numbers records; - `forEachSequence(...)` adds the sequence and sets the count to the number of values, producing exactly one record per value. | Tool | Changes | Typical use | |---|---|---| | `state([...])` | fixed values for every record | one-off override | | named state method | a reusable, named override | `cancelled()`, `unverified()` | | `sequence(...)` | values cycling per record | alternating statuses or slots | | `forEachSequence(...)` | cycling values and the count | one record per listed value | | `create([...])` arguments | final override | pin one attribute in a call | ## How the pieces combine The evaluation order for one record is: `definition()`, then any parent keys from `for()`, then each state in the order it was chained, then the attributes passed to the terminal call. Nested factories and closures in the result are resolved after this merge. So `Appointment::factory()->cancelled()->create(['status' => 'booked'])` stores `booked`: the call-site attributes come last. ## Writing definition() well States only stay simple when the base is sound. A good `definition()`: - returns a **valid** record on its own, so `Appointment::factory()->create()` works with no states; - calls `fake()` inside the returned array, so each record gets fresh values; `fake()` returns a shared Faker generator per locale, and the locale comes from `config('app.faker_locale')`, set by `APP_FAKER_LOCALE` and defaulting to `en_US`; - avoids expensive or surprising work, such as creating many related rows, because every record pays for it; - uses a closure for a value derived from another attribute, for example an `ends_at` computed from `starts_at`. Anything that describes a *variant* (cancelled, virtual, overdue, soft-deleted) belongs in a named state rather than in the defaults. The skeleton's `UserFactory` follows this split: verified users by default, `unverified()` as the variant. ## Why interviewers ask States separate *what a valid record looks like* from *which variant a scenario needs*. A good answer shows that you keep `definition()` minimal and valid, push variants into named states rather than copy-pasting arrays, and use sequences when a realistic dataset needs a spread of values instead of one repeated value.

  • What is the difference between sequence() and forEachSequence()?
    `sequence()` only adds the cycling state; the number of records still comes from `count()`, so values repeat or are cut short. `forEachSequence()` adds the same state and sets the count to the number of values, so each listed value produces exactly one record.
  • Can a named state register a callback that runs only for that variant?
    Yes. Chain `afterMaking()` or `afterCreating()` onto the `$this->state(...)` call inside the state method. The callback is part of the returned factory, so it runs only when that state is used, for example creating a cancellation note after a cancelled appointment is saved.

saying these in an interview costs you the question

  • Named states override attributes passed to create()
  • sequence() uses only its first value unless count matches
  • A state method should mutate $this and return nothing
  • definition() is evaluated once and shared by every record
  • trashed() must be written by hand on each factory