Eloquent "Combo" Methods
Programming is all about identifying and solving problems, and often the problems we end up solving require similar solutions.
For example, frequently I need to be sure a model is available before proceeding to do something with it, but I'm not necessarily guaranteed it exists. One option might look something like this:
if( ! User::where('first_name', 'jesse')->exists()) {
$user = User::create(['first_name' => 'jesse']);
} else {
$user = User::where(['first_name' => 'jesse'])->first();
}
// Continue processing with the $user now available
Did you notice how there are essentially two steps in the code sample above?
- Does a record already exist for these values? If so, return it.
- If not, can we create/make a new one?
Obviously there are cleaner ways to do this, but the illustration shows that we frequently need to check if a model exists, retrieve it if so, or create/make a new one if not.
Fortunately for us Laravel offers a few methods that allow us to shorten this pattern! There are a handful of what I like to call "Combo" methods available in the Eloquent Builder class. These are concise methods that help us avoid excessive conditional statements.
firstOrNew
Let's start with the firstOrNew method, which takes an array of values in the first parameter and will return the first record that matches, or a new instance with the values set as attributes:
$user = User::firstOrNew(['first_name' => 'jesse']);
At this point we either have the first User
record that has a first_name
of jesse
, or a brand new User
instance with the first_name
attribute set to jesse
.
If there are no matches, and Eloquent returns a new instance, it's helpful to realize this new instance has not been saved to the database, yet. This allows you to set additional attributes like this:
$user = User::firstOrNew(['first_name' => 'jesse']);
$user->last_name = 'schutt';
All we need to do to persist the object to the database is call the ->save()
method on the instance.
$user = User::firstOrNew(['first_name' => 'jesse']);
$user->last_name = 'schutt';
$user->save();
We can make this even shorter by providing a second set of parameters and the new method will merge the first and second sets to create an instance in one fell swoop.
$user = User::firstOrNew(['first_name' => 'jesse'], ['last_name' => 'schutt']);
$user->save();
Caveat - If there is a record that matches the all values in the first parameter, it will be returned and the model will not be updated with the values in the second parameter!
In plain English
The firstOrNew
method tells Eloquent the following:
"Go check the users
table for any records with a first_name
of jesse
and return the first result. If there are no matching rows create a new User instance and set first_name
to jesse
and last_name
to schutt
."
firstOrCreate
While firstOrNew
retrieves either an existing record or a new instance, firstOrCreate
will retrieve the first record that matches the values in the first parameter, it will also save the model to the database if there are no matches.
$user = User::firstOrCreate(['first_name' => 'jesse']);
The firstOrCreate
method takes the values in the first parameter and attempts to locate a record that matches. If it exists, then the method returns it. Cool!
If there is no corresponding record, then Eloquent will create a new record, set first_name
to jesse
, and save it to the database.
However, what if I need to also add some additional data when I create the record? I could do the following:
$user = User::firstOrCreate(['first_name' => 'jesse']);
$user->update(['last_name' => 'schutt']);
This works fine, but by passing data in the second parameter of firstOrCreate
I can shrink it into one line:
$user = User::firstOrCreate(['first_name' => 'jesse'], ['last_name' => 'schutt']);
Now I'm assured that the $user
variable will contain a User
record with jesse
as the first_name
attribute.
Caveat - If a record already existed with jesse
as the first_name
, I'm not guaranteed that last_name
will be schutt
, since firstOrCreate
will return return the first record matching the values in parameter 1.
In plain English
The firstOrCreate
method tells Eloquent the following:
"Go check the users
table for any records with a first_name
of jesse
and return the first result. If there are no matching rows create a new record in the users
table with the merged values from the first and second parameter."
findOrNew
<a href="https://laravel.com/api/5.5/Illuminate/Database/Eloquent/Builder.html#method_findOrNew" target="_blank">findOrNew</a>
is similar to the previous two methods, in that it helps eliminate if/else conditionals, however the main difference is that it attempts to find a record by primary key instead of an array of values.
$user = User::findOrNew(1);
In this case, we attempt to locate a User
with the primary key of 1
. As you can guess, if it doesn't exist, the method will return an empty instance of User
.
$user = User::findOrNew(1);
$user->first_name = 'jesse';
$user->last_name = 'schutt';
$user->save();
In plain English
The findOrNew
method tells Eloquent the following:
"Go check the users
table for a record with ID 1
and return it. If there is no record, return a new empty instance of User
."
updateOrCreate
Each of these combo methods are useful in their own right, but I find myself using <a href="https://laravel.com/api/5.5/Illuminate/Database/Eloquent/Builder.html#method_updateOrCreate" target="_blank">updateOrCreate</a>
more often than the rest.
$creds = MailChimpCredentials::updateOrCreate(['user_id' => 1], ['api_key' => 'abcxyz123']);
By this point you can probably guess what updateOrCreate
does! Yep, it attempts to get the first record that matches the values in the first parameter, and update it with the values in the second. If no record exists, it will create one by merging the two sets of values.
In an app I'm currently working on, a user may have a record in the mail_chimp_credentials
table if they've added their api key. It's also possible they haven't set their key yet, so they don't have any records. The first time they attempt to set their key, we need to create a new record for them. Otherwise, I grab their existing key and update it with the new data. updateOrCreate
works like a charm in this situation.
In plain English
The updateOrCreate
method tells Eloquent the following:
"Go check the mail_chimp_credentials
table for a record that has 1
in the user_id
column. If you find any, update the first one by putting abcxyz123
in the api_key
column. If not, create a new MailChimpCredentials
record with 1
in the user_id
column and abcxyz123
in the api_key
column."
Conclusion
Hopefully the explanation of these "combo" methods will tighten up your code, make it more expressive, and easier to navigate!
Want to read more tips and insights on working with a website development team that wants to help your organization grow for good? Sign up for our bimonthly newsletter.
By Jesse Schutt
Director of Engineering
Jesse is our resident woodworker. His signature is to find the deeper meaning in a project and the right tool for the job.