Eloquent Create, Update, New...the Untold Story

Tag: Laravel 5.1+, Eloquent

Like zombies, they lurch forward out of the swirling fog of the documentation, threatening your peace of mind...`firstOrNew`. `firstOrCreate`. `updateOrCreate`. You can look away, but you can never forget they're there...

Okay, probably better saved for Halloween than April Fool's. Still, for a great many of us, it captures the feeling, doesn't it? We know they must be for something, and the fact that we don't know for what leaves us slightly unsettled. Over on Laravel Quick Tips we've been looking at a few of these functions and their uses, and I thought it might be helpful to collect all of them together in a single (I hope) coherent post.

Without further preamble, let's get to it. I'm going to use the basic User object and table that ships with a default installation of Laravel so you can follow along if you like.

New vs. Create

Let's start by answering one of the fundamental differences in creating new records - what is the difference between using these two bits of code?

User::create(['name' => 'Jeff']); $user = new User(['name' => 'Joe']); $user->save();

The answer is a simple one - `create` actually creates and saves the record; the second method creates a new User instance but does not fire the save() method. Don't believe me? Have a look at Illuminate\Database\Eloquent\Model::create. It looks exactly like the second example!

Why would we do that? Why take the extra steps if we have a nice wrapper function? Because we are using an ORM. Something I see that is very common, and I think a mistake, is to ignore the fact that you are using an ORM and instead set up your relationships manually.

For example:

new Post::create(['user_id' => 1, 'title' => 'The title']);

This is bad for a couple of reasons. First - isn't that the job of the ORM? Of course it is! Compare:

$post = Post::create(['title' => 'The title']); User::find(1)->posts()->save($post);

Not only do I let my ORM do the work, but it gives me added security of checking that I've got the right User and they exist. (Better still is to write this with findOrFail, for example).

BUT!! I have a problem, which ties us back in to my original point. If you have set up your foreign keys correctly, this second method will fail! Why? Because you are tying to `create` the Post without the user_id, which will be required. That's why we simply make a new instance without saving it.

$post = new Post(['title' => 'The title']); User::find(1)->posts()->save($post);

In this case, Laravel will add the `user_id` itself, based on your relationship info. There are a few ways of making a new instance without saving (using factories, for example, which we'll touch on when we look into testing more), but this is the most straightforward.

findOrNew, firstOrCreate, updateOrCreate, ...

Once you understand the logic above, it's all downhill. There are a whole host of functions that are essentially two-step wrappers, with the name clearly saying what it does. The first action can be `first`, `find` or `update`, coupled with either `new` or `create`, as your situation calls for. Let's check how those three prefixes differ.

findOrNew (no findOrCreate)

A simple extension of `find`, this will search for the primary key or else create a new model instance. Here's a little tip: `find()` has a second parameter where you can pass an array of fields you want returned, instead of selecting all of them.

There is no `findOrCreate` - my assumption is, we want the primary key to be created via the usual auto-increment on the table. You can get around this by using `firstOrCreate`.

firstOrNew/firstOrCreate

These functions allow you to check if a record already exists and only create it if it doesn't. You may not have realized, but you can also pass a subset of fields to check against using:

User::firstOrCreate(['name' => 'Jeff', 'email' => 'jeff@codebyjeff.com']);

where the full array will be used as a where clause, and if no record is found, it will be created.

updateOrCreate (no updateOrNew)

Here is an edge case, but I run into it rather frequently when I want to make new records based on a template. The `firstOrCreate` is very handy to search for or create a record, but the problem is that it will only make it with that exact of data. So if your data also had 'some_count' => 12 that wasn't actually part of the search, you can't do it with the insert.

Fear not! This is what the surprisingly undocumented updateOrCreate() is for:

Illuminate\Database\Eloquent\Model::updateOrCreate(array $attributes, array $values = [])

The first array checks the conditions to search on, the second is the actual data you want to update or insert into the new record. So you would still search for a record "Jeff" and "jeff@codebyjeff.com", and either update its "some_count" to be 12, or create the whole record if not found.

So there you have it! No more running from the zombies.

Hope that helped!