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

3

8

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.

Savannasavannah answered 1/6, 2018 at 3:13 Comment(2)
just do a loop foreach(range(1, 12) as $i) in the seederEnunciation
Can you give some code example and actually put it in an answer?Savannasavannah
S
4

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!

Savannasavannah answered 4/6, 2018 at 0:24 Comment(0)
E
2

Sorry if you don't understand my point so I'll try to explain it in code

    use Illuminate\Database\Seeder;


$factory->define(App\StepCategory::class, function (Faker $faker) {
    // Generate Created At and Updated at DATETIME
    $DateTime = $faker->dateTime($max = 'now');
    $step_id = function () {
            return factory('App\Step')->create()->id;
        };

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

function uniqueOrder($step_id)
{
    $unique = rand(1,5);
    do {
       $unique = rand(1,5);
     }
     while(StepCategory::where('step_id', $step_id)->andWhere( 'order', $unique)->exists())

  return $unique;

}
Enunciation answered 3/6, 2018 at 23:51 Comment(6)
I have set up my hasMany Relationship. So that part is good to go. However I'm confused with your code as I have no column named title, closed, or such. I'm also confused as to how the rand function works. Is it unique? I can't have repeating values. Also where does the $categoryIndex variable used and why do you have to define it in the for loop if its not being used?Savannasavannah
I'm also creating a Category FOR the steps. Not the steps itself. I already have that factory set and done. Just an FYI. Also the Step Model has a hasMany relationship called "category".Savannasavannah
just ignore it important for foreach loopEnunciation
just focus on while -> do code i think this what you looking for ? but define it more I don't have your tables so I don't know honestlyEnunciation
The tables I'm using are steps and step_categories. I think my code is working as in my question, however it's just not "resetting" the counter and putting 0's in all the orders after it increments to "4". Screenshots of each can be found here: imgur.com/a/qv5idfG and a screenshot of the table after i run db:seed command: imgur.com/a/PMFOyxRSavannasavannah
Updated to a solution! I will be sure to give you your bounty after 22 hours which is required by StackOverflow. :P Thanks for all the help man. Got my mind joggin for the answer. :P See my answer above to really see what I was trying to do. :PSavannasavannah
V
0

for example if your Step model name is Steps :

$allSteps = Steps::all();
foreach($allSteps as $step){
   for($i=1;$i<6;$i++){
     //insert to table $step->id , $i  for example 
     DB::table('yourTableName')->insert([
           'name'=>'Step '.$step->id.'- Category '.$i , 
           'order'=>$i , 
          'step_id'=>$step->id
     ]);
    }
}
Vatic answered 3/6, 2018 at 23:25 Comment(2)
I think i've solved this using an autoIncrement function I've created. However when running the db:seed command I'm getting an undefined variable error. Updating above in a second.Savannasavannah
doesn't it say what variable ?Vatic

© 2022 - 2024 — McMap. All rights reserved.