Laravel 8 Dynamic Model Factories?

Viewed 748

I'm trying to create a package that allows for specifying model factory definitions inside of the model class itself.

For example:

class User extends Authenticatable
{
    use LaravelUIModel, Notifiable;

    protected $hidden = ['password', 'remember_token'];
    protected $casts = ['email_verified_at' => 'datetime'];

    public function definition(Generator $faker)
    {
        return [
            'name' => $faker->name,
            'email' => $faker->unique()->safeEmail,
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
        ];
    }
}

So I created a trait called LaravelUIModel, where I'm trying to specify a dynamic newFactory:

trait LaravelUIModel
{
    use HasFactory;

    protected static function newFactory()
    {
        $factory = new Factory;
        $factory->setModel(static::class);

        return $factory->new();
    }
}

Now, the $model factory properted is protected, so I made my own Factory class to extend the Eloquent Factory class, which should allow me to change it:

class Factory extends EloquentFactory
{
    public function setModel($model)
    {
        $this->model = $model;
    }

    public function definition()
    {
        return app($this->model)->definition($this->faker);
    }
}

I keep getting this error:

Class "App\" not found

It's like it isn't recognizing that I've set the model name at all, because when I print $this->model it shows me App\Models\User.

How do I set the factory $model dynamically like this so I can specify definitions in the model class, WITHOUT making a new factory file per model?

1 Answers

i got this working by creating this trait:


use Faker\Generator;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Factories\HasFactory;

trait HasNewFactory
{
    use HasFactory;

    protected static function newFactory()
    {
        $factory = new class extends Factory
        {
            public static $definition;

            public function definition()
            {
                $definition = static::$definition;

                return $definition($this->faker);
            }
        };

        $factory::guessModelNamesUsing(function () {
            return get_called_class();
        });

        $factory::$definition = function (Generator $faker) {
            return (new static)->definition($faker);
        };

        return $factory;
    }
}

Now in my model, instead of using HasFactory, I use this HasNewFactory trait:

use Diontron\Lucid\Traits\HasNewFactory;
use Faker\Generator;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Schema\Blueprint;

class MyModel extends Model
{
    use HasNewFactory;

    public function definition(Generator $faker)
    {
        return [
            'name' => $faker->name(),
        ];
    }
}
Related