Laravel Factories Explained

Model factories are a great feature of Laravel. However, their code is one of the more unique areas of the framework. This can make them quite difficult to understand without a deep dive into the source. When I created the factory ‘states’ feature awhile back, I had to spend quite a bit of time just trying to figure out the flow of how they actually worked, before I could even begin to make my PR.

A couple days ago, I came up with another idea for a factory feature, and I found myself having to dive in again to understand how it worked. It is not incredibly intuitive, so I thought I’d document my findings here to help others who are considering submitting a change to factories (and for myself in 6 months when I’ve forgotten again).

There are 4 files relevant to factories. They are:

Illuminate\Database\DatabaseServiceProvider.php
Illuminate\Foundation\helpers.php
Illuminate\Database\Eloquent\Factory.php
Illuminate\Database\Eloquent\FactoryBuilder.php

The DatabaseServiceProvider is easily overlooked, because it is not immediately apparent how it is used. However, like most things in Laravel, the Service Provider is the starting point for this feature. It is here we register the creation of the Eloquent\Factory (note: you will often see it aliased as EloquentFactory:

protected function registerEloquentFactory()
{
$this->app->singleton(FakerGenerator::class, function ($app) {
return FakerFactory::create($app['config']->get('app.faker_locale', 'en_US'));
});

$this->app->singleton(EloquentFactory::class, function ($app) {
return EloquentFactory::construct(
$app->make(FakerGenerator::class), $this->app->databasePath('factories')
);
});
}

You’ll notice we register it as a “singleton”, meaning however many times we request the Eloquent\Factory in a process, we will always get back the same instance. This is important, as you’ll see, so we only load our factories once.

Next, let’s look at a typical UserSeeder.

public function run()
{
//truncate
User::truncate();

//create
factory(User::class, 30)->create();
}

We start by emptying out the User model table. Next, we call the factory() helper method, and pass the model we would like to make and how many of them we want. The factory() method looks like this:

function factory()
{
$factory = app(EloquentFactory::class);

$arguments = func_get_args();

if (isset($arguments[1]) && is_string($arguments[1])) {
return $factory->of($arguments[0], $arguments[1])->times($arguments[2] ?? null);
} elseif (isset($arguments[1])) {
return $factory->of($arguments[0])->times($arguments[1]);
} else {
return $factory->of($arguments[0]);
}
}

It starts by instantiating our Eloquent\Factory from the container. This is a possible ‘gotcha’ moment, if you don’t remember that we bound our singleton in the service provider. If you look at the Factory class, you’ll notice it does have a constructor, but our container actually instructs us to use the construct() static method, which creates our Eloquent\Factory, and then loads() the individual user defined factories.

public static function construct(Faker $faker, $pathToFactories = null)
{
$pathToFactories = $pathToFactories ?: database_path('factories');

return (new static($faker))->load($pathToFactories);
}

The load() method simply loops through the path it is given, and requires any files ending in ‘.php’.

public function load($path)
{
$factory = $this;

if (is_dir($path)) {
foreach (Finder::create()->files()->name('*.php')->in($path) as $file) {
require $file->getRealPath();
}
}

return $factory;
}

When creating your first custom factory, another curious question that arises is where does the $factory variable come from? Again, it is not apparent unless you dig into the source. In the load() method, before we require our factories, you’ll notice we set $factory = $this. This variable is part of the scope of the required files, giving them access to $factory, which is our instance of Eloquent\Factory.

Back to our factory helper, we see that Laravel allows us to pass 1, 2, or 3 parameters to the function. The first parameter ( $arguments[0] ) is always the name of the model we are making. If the second parameter ( $argument[1] ) is a string, we are generating a custom named factory model. If the second parameter is an integer, we will make the specified number of models. In our UserSeeder example above, we are creating 30 App\User models.


Quick Side Note: A lot of the Eloquent\Factory methods are used to define our factories. They are well documented and outside the scope of this article.


Back to it, we see we are delegating to an of() method on our Eloquent\Factory, which is defined as:

public function of($class, $name = 'default')
{
return new FactoryBuilder($class, $name, $this->definitions, $this->states, $this->faker);
}

This bring us to our last player in the game, the FactoryBuilder. Here we return a new instance of the FactoryBuilder, and pass in all of the relevant definitions, states, the model class name, the custom class name, and our Faker instance. The FactoryBuilder does the heavy lifting of actually generating our fake data, and hydrating our models.

The 2 common methods to chain onto our factory() method are create() and make(), the only difference between the 2 being create() actually saves the models (or collection) after they are made. In fact create() delegates to make() in the code, so we will only concern ourselves with make().

public function make(array $attributes = [])
{
if ($this->amount === null) {
return $this->makeInstance($attributes);
}

if ($this->amount < 1) {
return (new $this->class)->newCollection();
}

return (new $this->class)->newCollection(array_map(function () use ($attributes) {
return $this->makeInstance($attributes);
}, range(1, $this->amount)));
}

Here we see a couple special cases, depending on how many models we request. If we do not specify a number to request, we will get exactly 1 Model back. If we specify any number less than 1, we will get an empty collection back. Our last return statement covers the most common case, returning a collection with 1 or more elements. One thing to note here is we create our collection from our specific Model. That means if you have a custom collection defined on your Model, it will be used, and falls back to the default collection if none is defined.

Now we delegate to the makeInstance() method. First it unguards the models (removes mass assignment protection), and then makes sure we have registered a definition for the given Model class.

protected function makeInstance(array $attributes = [])
{
return Model::unguarded(function () use ($attributes) {
if (! isset($this->definitions[$this->class][$this->name])) {
throw new InvalidArgumentException("Unable to locate factory with name [{$this->name}] [{$this->class}].");
}

$instance = new $this->class(
$this->getRawAttributes($attributes)
);

if (isset($this->connection)) {
$instance->setConnection($this->connection);
}

return $instance;
});
}

Next, we create a new instance of our model class, and delegate to getRawAttributes() to generate our array of attributes.

protected function getRawAttributes(array $attributes = [])
{
$definition = call_user_func(
$this->definitions[$this->class][$this->name],
$this->faker, $attributes
);

return $this->expandAttributes(
array_merge($this->applyStates($definition, $attributes), $attributes)
);
}

We can see the finish line, I promise we’re almost done! To create our attributes, we use call_user_func to execute the appropriate closure we defined for this Model, and pass it our Faker instance, and any override attributes. This gives us our ‘base’ attributes as the $definition variable. Finally, let’s break our last return line down into 3 parts. 1st, the applyStates() method takes our ‘base’ definition and overrides them with any states we have applied (you can read about ‘states’ in the docs, or feel free to ask me, since I created them 😄). 2nd, the array_merge takes any custom override attributes ( ->create(['foo' => 'bar']) ), and overwrites the originals. 3rd, expandAttributes() makes sure all of the attributes are scalar values, by evaluating them if necessary.

protected function expandAttributes(array $attributes)
{
foreach ($attributes as &$attribute) {
if (is_callable($attribute) && ! is_string($attribute)) {
$attribute = $attribute($attributes);
}

if ($attribute instanceof static) {
$attribute = $attribute->create()->getKey();
}

if ($attribute instanceof Model) {
$attribute = $attribute->getKey();
}
}

return $attributes;
}

We can see it handles callables, other factories, and Eloquent models.


I hope you’ve enjoyed this explanation of Laravel’s model factories, and that it makes this feature a little easier for you to understand. If you’d like to discuss factories more, please reach out to me on Twitter or the Laravel Slack.

Happy coding!