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.