Search code examples
phplaravelfakerlaravel-seedingfactories

Using PHP Faker in Laravel to generate "unique with" entry when seeding a database using a factory


So Similar to the unique with validation rule (See: https://github.com/felixkiss/uniquewith-validator), I want to know how to generate a entry, where one column is unique with another one. I want to seed my database as follows.

Example:

There are 12 steps in the "steps" table. Each step should have 5 categories associated with each one that are stored in the "step_categories" table. Each of those categories are assigned a unique order number 1 through 5 that is unique with each "step_id".

See this image here for an example of what the database should look like: https://i.sstatic.net/nYQBW.jpg

I had to manually to make the entries in the database for the above image example. I don't want to have to generate this manually every time, say I make a mistake and have to rollback the migrations for example.

I am using a factory to generate this data. So the factory name is StepCategoriesFactory.php and clearly I'm calling the factory with the create() method from the DatabaseSeeder.php file.

I thought about doing this in a for loop, then i got as far as realizing when i called the 'step_id' => App\Model::all()->random()->id to grab a new id, that I wouldn't be able to ensure I wasn't grabbing the id that i just generated 5 entries for. I'm really new with Laravel, and I'm not sure where to even start on this. There's no real information on SO where faker can use the unique with another column. How would I Go about this?

NOTE: The step id is not always going to be 1-12. The step ID might be different depending on whether a step gets deleted and remade. So just assigning the step_id to equal 1-12 wont work.

UPDATE: Here's some code I just wrote, and I think I'm on the right track. Maybe. I've grabbed the step_id by it's number field as that will always be 1-12, and I've grabbed the IID out of the entry. But now I'm stuck on how to generate the order 1-5 without repeating itself. I still haven't run this yet as its incomplete and I know it'll throw an error without the correct order number.

UPDATE 2: I think I'm on the right track here. However I'm getting an undefined variable error. When I put the first line from within the anonymous function, it's resetting the order to "1" for every entry. How do i make the $autoIncrement variable available to the anonymous function? The Seeder has stayed the same between updates.

Image of the error: https://i.sstatic.net/z4JI5.jpg Second image with the Die/Dump error in terminal: https://i.sstatic.net/J1Zj2.jpg

Reference this article here: https://laracasts.com/discuss/channels/laravel/model-factory-increment-value-faker?page=1

UPDATE 3: I forgot the use ($autoIncrement) line of code for the anonymous function. Code below has been updated, but now I'm getting a different error saying that the order column has a null value and can't be inserted. clearly it should be '1'. Even after I call my $autoIncrement->next(); which should increment it to '1' it's still returning null according to the terminal. However, when I do a diedump on $autoIncrement->current() it's returning 1. Weird.

Update 3 error: https://i.sstatic.net/gTiSy.jpg

StepCategoriesFactory.php

use Faker\Generator as Faker;

$autoIncrement = autoIncrement();

$factory->define(App\StepCategory::class, function (Faker $faker) use ($autoIncrement) {
    // Generate Created At and Updated at DATETIME
    $DateTime = $faker->dateTime($max = 'now');
    $autoIncrement->next();
    $order = (int) $autoIncrement->current();

    return [
        // Generate Dummy Data
        'order' =>  $order,
        'name' => $faker->words(4, true),
        'created_at' => $DateTime,
        'updated_at' => $DateTime,
    ];
});

function autoIncrement()
{
    for ($i = 0; $i < 5; $i++) {
        yield $i;
    }
}

Edit: Put a bounty on this question, as I think it would be helpful for the community to get a detailed answer. I'm looking for help to explain how to go about making sure I'm grabbing the same entry through each loop.


Solution

  • FINALLY SOLVED!

    So I took in everyone's answers, and thought long and hard about using a for loop to create the order number. 1-5. The problem that I was running into at the end was that the $i variable was not resetting. So after the yield I had to check if the $i variable equalled 5 and then reset it back to zero.

    Heres the code!

    StepCategories.php

    use Faker\Generator as Faker;
    
    $autoIncrement = autoIncrement();
    
    $factory->define(App\StepCategory::class, function (Faker $faker) use ($autoIncrement) {
        // Generate Created At and Updated at DATETIME
        $DateTime = $faker->dateTime($max = 'now');
    
        // Get the next iteration of the autoIncrement Function
        $autoIncrement->next();
        // Assign the current $i value to a typecast variable.
        $order = (int) $autoIncrement->current();
    
    
        return [
            // Generate Dummy Data
            'order' =>  $order,
            'name' => $faker->words(4, true),
            'created_at' => $DateTime,
            'updated_at' => $DateTime,
        ];
    });
    
    function autoIncrement()
    {
        // Start a loop
        for ($i = 0; $i <= 5; $i++) {
            // Yield the current value of $i
            yield $i;
            // If $i is equal to 5, that must mean the start of a new loop
            if($i == 5) {
                // Reset $i to 0 to start over.
                $i = 0;
            }
        }
    }
    

    DatabaseSeeder.php

    // Generate Dummy Categories
    // Run the factory 12 times
    foreach(range(1, 12) as $i) {
        // Generate 5 entries each time
        factory(App\StepCategory::class, 5)->create([
            // Since all steps have a number 1-12 grab the step by the number column and get it's ID
            'step_id' => App\Step::where('number', '=', $i)->first()->id,
        ]);
    }
    

    Thanks to all who helped!