This title is a lie, I actually did not learn this today, but came across it a few weeks ago. Nonetheless, I wanted to share it since I feel it’s incredibly valuable, but is not included in the Laravel documentation.

Drake Knows

Factory::sequence()

When I need to create a series of Models of the same type, the tool to reach for is Laravel factories. Specifically, the sequence functionality.

For instance, say I have Payment model. Here is an example of the factory:

/**
 * @extends Factory<Payment>
 */
class PaymentFactory extends Factory
{
    protected $model = Payment::class;

    public function definition(): array
    {
        return [
            'gateway' => $this->faker->randomElement(['stripe', 'ach']),
            'amount' => $this->faker->numberBetween(1_00, 25_000_00),
            'status' => $this->faker->randomElement(['paid', 'pending', 'refunded']),
            'user_id' => User::factory(),
            'created_at' => Carbon::now(),
            'updated_at' => Carbon::now(),
        ];
    }
}

Say I want to create 4 payments belonging to a user. I might reach for something like this in my test case.

$user = User::factory()->create([
    'id' => 102,
    'first_name' => 'Luke',
    'last_name' => 'Kuzmish'
]);

$payments = Payment::factory()
    ->for($user)
    ->sequence(
        ['id' => 5000, 'amount' => 100_00, 'status' => 'paid'],
        ['id' => 5002, 'amount' => 200_00, 'status' => 'refunded'],
        ['id' => 7000, 'amount' => 1_000_00, 'status' => 'pending'],
        ['id' => 7002, 'amount' => 1_050_00, 'status' => 'pending'],
    )
    ->create(['gateway' => 'stripe']);

What is the value of $payments? You may be frustrated to learn that it’s actually just a single Payment model, not a Collection of four Payments.

So what did I forget? I forgot to specify the count of models to create.

$payments = Payment::factory()
    ->for($user)
    ->sequence(
        ['id' => 5000, 'amount' => 100_00, 'status' => 'paid'],
        ['id' => 5002, 'amount' => 200_00, 'status' => 'refunded'],
        ['id' => 7000, 'amount' => 1_000_00, 'status' => 'pending'],
        ['id' => 7002, 'amount' => 1_050_00, 'status' => 'pending'],
    )
++  ->count(4)
    ->create(['gateway' => 'stripe']);

I’ve personally made this mistake countless times. Worse still is I may remember to chain count() but later modify the test to add a new sequence entry.

$payments = Payment::factory()
    ->for($user)
    ->sequence(
        ['id' => 5000, 'amount' => 100_00, 'status' => 'paid'],
        ['id' => 5002, 'amount' => 200_00, 'status' => 'refunded'],
        ['id' => 7000, 'amount' => 1_000_00, 'status' => 'pending'],
        ['id' => 7002, 'amount' => 1_050_00, 'status' => 'pending'],
++      ['id' => 9999, 'amount' => 2_999_99, 'status' => 'refunded'],
    )
    ->count(4)
    ->create(['gateway' => 'stripe']);

Here I end up with only four Payment models again.

A Better Solution

Enter forEachSequence. Using this method, the factory will create a Payment model for each sequence entry.

$payments = Payment::factory()
    ->for($user)
    ->forEachSequence(
        ['id' => 5000, 'amount' => 100_00, 'status' => 'paid'],
        ['id' => 5002, 'amount' => 200_00, 'status' => 'refunded'],
        ['id' => 7000, 'amount' => 1_000_00, 'status' => 'pending'],
        ['id' => 7002, 'amount' => 1_050_00, 'status' => 'pending'],
        ['id' => 9999, 'amount' => 2_999_99, 'status' => 'refunded'],
    )
    ->create(['gateway' => 'stripe']);

Now we have our five Payment models and never need to worry about specifying the count of models to create.