-
Notifications
You must be signed in to change notification settings - Fork 11.3k
[6.x] Create Factory Primers #30880
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[6.x] Create Factory Primers #30880
Conversation
Don't worry about the code style. It gets auto-fixed on merge. |
gotcha, thanks @GrahamCampbell. it's more so OCD.... |
What are |
here is the commit that added it. c238aac I'm not exactly sure how you would use this.... |
|
What I'm failing to see, though, is why your would want/need to reference the override attributes in the factory definition. Do you have an example of how you would use this? Either way I don't think it makes a difference for this PR, since we couldn't remove that parameter in a Minor or Patch release. |
There's another way to handle this which is to cache So instead of this $factory->define(App\Post::class, function ($faker) {
return [
'title' => $faker->title,
'content' => $faker->paragraph,
'user_id' => App\User::all()->random(),
];
}); You can do this $factory->define(App\Post::class, function ($faker) {
static $users;
if ($users === null) {
$users = App\User::all();
}
return [
'title' => $faker->title,
'content' => $faker->paragraph,
'user_id' => $users->random(),
];
}); |
@browner12 We use it on our project to create related entities based on given info and to create belongsTo related entities if no We shouldn't be doing this and we no longer are, but there are still hundreds of places where it's necessary. Maybe 7.x is a good place to drop support for it to force usage of states - I don't know, but I'm pretty sure this won't happen and there are probably valid use cases outside of ones I described. |
The more I think about this I think I will hold off on it. There are already fairly simple ways of doing this. You can use the static variable as pointed out above... You could also create a class within your seeders directory with static methods and return any "primer" data you need and could even use the |
The $factory->define(App\Post::class, function ($faker) {
static $users;
if ($users === null) {
$users = App\User::all();
}
return [
'title' => $faker->title,
'content' => $faker->paragraph,
'user_id' => $users->random(),
];
}); This assumes you always want to pick a $normalUsers = factory(App/User::class, 10)->create();
$superUsers = factory(App/User::class, 5)->state('super')->create();
$departments = factory(App/Department::class, 3)->prime('manager', $superUsers)->create(); |
Problem
Let's take a typical factory with a relationship. We'll use the example right off the Laravel docs.
When we create a
Post
, we will also create aUser
. At the surface this seems okay, but let's think what happens when we create 50Post
s.Without the relationship we'd be running 50 inserts, but with the relationship we double that to 100 queries! Now imagine a factory that has multiple relationships. The number of database queries (and time) grows with each new relationship handled this way.
What if we assume that
User
s have already been created, and we'll randomly select 1 to attach thePost
to?Unfortunately, this does not cut down on our query count, since the
all()
query is executed for every single newPost
.We can also not pass it as an override attribute.
This will cut down our query count, but since it is only executed once, all 50
Post
s will receive the same User ID, which is not what we want.Solution
The solution to this is a feature I call "primers". At the time of building the factories, you can inject key/value pairs into the builder that the factories can then use to generate their data.
Let's first look at how we call it. Simply chain the
prime()
method onto your factory prior to callingmake()
orcreate()
, and pass it a string key and a value of any type you like.With this call we've made 1 query to get all the
User
s. Now let's see how to use primers in the factories.Here we can see the primers are passed into the factory definition as a new 3rd parameter. For properties that we expect primers for, we should first check to see the primer exists. If the primer exists, we can use it as needed, but also fall back to the old way of determining the relationship if the primer was not provided.
With this new feature, and a primed factory, we've now cut our queries down from 100 to 51!
Primers can also be used with factory states.
Practical Usage
There are 2 primary places factories are used: tests and seeders.
In regards to Tests, let's assume we have
User
s,Post
s, andComment
s. BothPost
s andComment
s belong to aUser
that authored them.Currently to test we may write something like:
Both the
Post
andComment
factory will be deferring to the factory to create newUser
s. Therefore, this test would run 120 queries. 10 to create thePost
s, 50 to create theComment
s, 10 to create thePost
User
s, and 50 to create theComment
User
s.Now let's tweak it just a little to improve our performance.
We're now down to 70 queries from 120!
In regards to seeding, we can make some small tweaks to our
DatabaseSeeder
to improve performance.Currently you might do something like:
Without primers this will require 210 queries.
Let use primers to improve this.
This takes us down to 110 queries, hooray!
Caveats
There is one thing to be aware of when using primers. It is very possible the factory could make assumptions about what type of data it is getting from the primer. My guess is the most common use case will be to pass
Collection
s in.You can see if the primer exists we are calling
random()
on it. If the users passes in the incorrect type of data, it will cause an error.Here you can see we passed an
array
instead of aCollection
, so when the factory is run it will throw an error thatrandom()
does not exist.This error will be easy enough to identify and correct, so I'm not really worried about it, but just something to be aware of.