FakerPHP / Faker

Faker is a PHP library that generates fake data for you
https://fakerphp.github.io
Other
3.47k stars 330 forks source link

Feature: Unique key #848

Open pxlrbt opened 5 months ago

pxlrbt commented 5 months ago

What is the reason for this PR?

Author's checklist

Summary of changes

When using FakerPHP in database factories, such as in Laravel, I frequently encounter the need for unique values for a specific model or column. For example, unique emails for that model or a non-auto-incrementing ID that needs to be set.

Consider this implementation of a Laravel database factory:

class UserFactory extends Factory
{
    public function definition(): array
    {
        return [
            'external_id' => $this->faker->unique()->numberBetween(0, 99),
        ];
    }
}

Because of the ->unique() method, we obtain a unique external_id for each user. However, we may have another model that also requires unique numbers. Ideally, the IDs should be reset for this model. However, calling ->unique(reset: true) would reset the entire generator, causing it to reset on every call.

class EmployeeFactory extends Factory
{
    public function definition(): array
    {
        return [
            'external_id' => $this->faker->unique(reset: true)->numberBetween(0, 99),
        ];
    }
}

Therefore, I propose an option to add a key so that people can have multiple unique generators. This would allow us to have an independent set of unique numbers for both factories and models.

class UserFactory extends Factory
{
    public function definition(): array
    {
        return [
            'external_id' => $this->faker->unique(key: 'user.external_id')->numberBetween(0, 99),
        ];
    }
}

class EmployeeFactory extends Factory
{
    public function definition(): array
    {
        return [
            'external_id' => $this->faker->unique(key: 'employee.external_id')->numberBetween(0, 99),
        ];
    }
}

What do you think about this?

I'm not sure whether you consider $uniqueGenerator internal or if removing $uniqueGenerator and adding $uniqueGenerators is considered a breaking change.

Maybe this should be implemented as a new method, but I couldn't think of a better name than keyedUnique(). It appears that there is some confusion surrounding the usage of ->unique() in general.

https://github.com/laravel/framework/issues/46287 https://github.com/laravel/laravel/pull/5137

Review checklist

pxlrbt commented 5 months ago

For now, I have created a small package that adds a betterUnique() method that allows a key as first param: https://github.com/pxlrbt/faker-better-unique

stale[bot] commented 3 months ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed after 1 week if no further activity occurs. Thank you for your contributions.

pxlrbt commented 3 months ago

"Activity" 🤖

stale[bot] commented 2 months ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed after 1 week if no further activity occurs. Thank you for your contributions.

ulcuber commented 2 weeks ago

I've tried this

Provider

$this->app->singleton('faker.unique.users.email', function ($app) {
    $emails = User::pluck('email')->all();
    $used = [];
    foreach ($emails as $email) {
        $used[serialize($email)] = null;
    }
    $uniques = [
        'email' => &$used,
        'safeEmail' => &$used,
    ];

    return new UniqueGenerator(fake(), 10000, $uniques);
});

Factory


'email' => app('faker.unique.users.email')->safeEmail(),

Should be enough for paratest to mostly avoid database exceptions for unique keys And it could help to split by keys to prevent OverflowException