Eloquent "Combo" Methods

Written By Jesse Schutt
Posted on

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?

  1. Does a record already exist for these values? If so, return it. 
  2. 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

findOrNew 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 updateOrCreate 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!

  1. Cover photo: Instrument panel from a submarine