Sure thing.
So faker is used to generate "random" values for testing purposes generally speaking.
Say your user model has the following fields: (email, first_name, last_name, gender, date_of_birth, phone)
In the model factory you could use:
$factory->define(App\Models\User::class, function (Faker $faker) {
return [
'email' => $faker->unique()->safeEmail,
'first_name' => $faker->firstName,
'last_name' => $faker->lastName,
'gender' => $faker->randomElement(['male', 'female', 'other']),
'date_of_birth' => $faker->dateTimeBetween('-70 years', '-18 years'),
'phone' => $faker->phoneNumber,
];
});
Every time we seed our database, different values will be generated since we are not setting $faker->seed($seedNumber). If we did set the seed at the top of this factory, we would always get the same values for these fields. This is helpful for testing because you can make assertions about what data to expect.
Now imagine you have many different factories and many different seeders, I had two options.
-
Put $faker->seed() all over my code to ensure determinism (Even this may not work in some cases)
-
Make Faker a singleton so that the same instance of faker is being used, and the seed value is set before each call to a faker method
Now, Option 2 did not work because using $faker->seed() does not actually set a property of the instance of faker, it seeds php's random number generator which it uses to create "randomness". So other parts of your code, or your dependencies code can mess with php's number generator seed, and if you call faker at any point after it will become non-deterministic (different results on each seeding).
The solution in my PR is kind of a hack but achieves the desired results. By calling $faker->seed(2, true), you can set a durable mode. Faker uses the __get() and __call() magic methods where we can set mt_srand every time by incrementing the seed passed in. Now every call to any faker method anywhere in your code will have the desired results of determinism with "randomness".
I have also realized, that this should not be made a singleton, but just bound to the service container, so that changes in one class do not spill over to another.