16

For the following factory definition, the column order needs to be sequential. There is already a column id that is auto-incremented. The first row's order should start at 1 and each additional row's order should be the next number (1,2,3, etc.)

$factory->define(App\AliasCommand::class, function (Faker\Generator $faker) {
    return [
        'user_id' => App\User::inRandomOrder()->first()->id,
        'command' => $faker->word,
        'content' => $faker->sentence,
        'order'   => (App\AliasCommand::count()) ?
            App\AliasCommand::orderBy('order', 'desc')->first()->order + 1 : 1
    ];
});

It should be setting the order column to be 1 more than the previous row, however, it results in all rows being assigned 1.

bnahin
  • 796
  • 1
  • 7
  • 20
  • Are you ok with getting duplicate values for the order field? This solution you have doesn't prevent race conditions. – N.B. Jul 08 '17 at 10:23

5 Answers5

49

Here's something that might work.

$factory->define(App\AliasCommand::class, function (Faker\Generator $faker) {
    static $order = 1;   
    return [
        'user_id' => App\User::inRandomOrder()->first()->id,
        'command' => $faker->word,
        'content' => $faker->sentence,
        'order'   => $order++
    ];
});

It just keeps a counter internal to that function.

Update:

Laravel 8 introduced new factory classes so this request becomes:

class AliasCommandFactory extends Factory {

    private static $order = 1;

    protected $model = AliasCommand::class;

    public function definition() {
         $faker = $this->faker;
         return [
            'user_id' => User::inRandomOrder()->first()->id,
            'command' => $faker->word,
            'content' => $faker->sentence,
            'order'   => self::$order++
        ];
    }
}
apokryfos
  • 38,771
  • 9
  • 70
  • 114
7

The answer by @apokryfos is a good solution if you're sure the factory model generations will only be run in sequential order and you're not concerned with pre-existing data.

However, this can result in incorrect order values if, for example, you want to generate models to be inserted into your test database, where some records already exist.

Using a closure for the column value, we can better automate the sequential order.

$factory->define(App\AliasCommand::class, function (Faker\Generator $faker) {
    return [
        'user_id' => App\User::inRandomOrder()->first()->id,
        'command' => $faker->word,
        'content' => $faker->sentence,
        'order'   => function() {
            $max = App\AliasCommand::max('order'); // returns 0 if no records exist.

            return $max+1;
        }
    ];
});

You almost had it right in your example, the problem is that you were running the order value execution at the time of defining the factory rather than the above code, which executes at the time the individual model is generated.

By the same principle, you should also enclose the user_id code in a closure, otherwise all of your factory generated models will have the same user ID.

Soulriser
  • 419
  • 8
  • 16
3

In Laravel 9 (and possibly some earlier versions?), there's a pretty clean way to make this happen when you're creating models (from the docs):

$users = User::factory()
    ->count(10)
    ->sequence(fn ($sequence) => ['order' => $sequence->index])
    ->create();

If you'd like to start with 1 instead of 0:

$users = User::factory()
    ->count(10)
    ->sequence(fn ($sequence) => ['order' => $sequence->index + 1])
    ->create();
Justin Russell
  • 1,009
  • 1
  • 9
  • 15
1

To achieve true autoIncrement rather use this approach:

   $__count = App\AliasCommand::count();
   $__lastid = $__count ? App\AliasCommand::orderBy('order', 'desc')->first()->id : 0 ;


   $factory->define(App\AliasCommand::class,
       function(Faker\Generator $faker) use($__lastid){

       return [

         'user_id' => App\User::inRandomOrder()->first()->id,
         'command' => $faker->word,
         'content' => $faker->sentence,
         'order'   => $faker->unique()->numberBetween($min=$__lastid+1, $max=$__lastid+25),

         /*  +25 (for example here) is the number of records you want to insert 
             per run.
             You can set this value in a config file and get it from there
             for both Seeder and Factory ( i.e here ).
         */
      ];

   });
edam
  • 910
  • 10
  • 29
-1

The solution also solves already data on table conditions:

class UserFactory extends Factory
{
    /**
     * @var string
     */
    protected $model = User::class;

    /**
     * @var int
     */
    protected static int $id = 0;

    /**
     * @return array
     */
    public function definition()
    {
        if ( self::$id == 0 ) {
            self::$id = User::query()->max("id") ?? 0;
            // Initialize the id from database if exists.
            // If conditions is necessary otherwise it would return same max id.
        }

        self::$id++;

        return [
            "id"        => self::$id,
            "email"      => $this->faker->email,
        ];
    }
}

Bedram Tamang
  • 3,748
  • 31
  • 27