The Differences Between UpdateOrCreate And UpdateOrInsert in Laravel

Arie Visser • November 21, 2023

laravel php database eloquent

When you want to update a record and create it when it doesn't exist, you can use the updateOrCreate or updateOrInsert method in Laravel.

The so-called "upsert" methods.

I've always wondered what the exact differences are, and it turns that quite some aspects of Laravel have to be discussed to make this clear.

I hope this article will give some insight in the anatomy of these methods, and how to use them.

How to call "upsert" methods in Laravel?

Both updateOrCreate and updateOrInsert can be called in the same way.

For example, when you want to update or create a user with a certain email, you can call the methods like this:

User::updateOrInsert(
    ['email' => 'brennan50@example.org', 'status' => 'DISABLED'], 
    ['name' => 'Christopher Jones', 'status' => 'ACTIVE']
);
User::updateOrCreate(
    ['email' => 'brennan50@example.org', 'status' => 'DISABLED'], 
    ['name' => 'Christopher Jones', 'status' => 'ACTIVE']
);

In both cases the user with email "brennan50@example.org" and status "DISABLED" will be renamed to "Christopher Jones" and get the status "ACTIVE".

When no user with email "brennan50@example.org" and status "DISABLED" exists, it will be created with name "Christopher Jones" and status "ACTIVE".

Both methods will always only affect a single record.

So far, they seem to be the same. However, there are some important distinctions.

What are the differences between updateOrCreate and updateOrInsert?

After reading the documentation of updateOrCreate and updateOrInsert, it is helpful to navigate through Laravel's source.

Eloquent Builder or Query Builder

When looking at the function definitions, the first distinction is that updateOrCreate is part of Illuminate\Database\Eloquent\Builder and updateOrInsert is part of Illuminate\Database\Query\Builder.

In vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php you can find updateOrCreate:

**
 * Create or update a record matching the attributes, and fill it with values.
 *
 * @param  array  $attributes
 * @param  array  $values
 * @return \Illuminate\Database\Eloquent\Model|static
 */
public function updateOrCreate(array $attributes, array $values = [])
{
    return tap($this->firstOrCreate($attributes, $values), function ($instance) use ($values) {
        if (! $instance->wasRecentlyCreated) {
            $instance->fill($values)->save();
        }
    });
}

As you can see, the updateOrCreate method will just call firstOrCreate, and return the new model when it is just created. When it already exists it will call fill and save to update the model.

The logic of the implementation in updateOrInsert is a bit different, but it will lead to the same result. You can find the definition in vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php:

/**
 * Insert or update a record matching the attributes, and fill it with values.
 *
 * @param  array  $attributes
 * @param  array  $values
 * @return bool
 */
public function updateOrInsert(array $attributes, array $values = [])
{
    if (! $this->where($attributes)->exists()) {
        return $this->insert(array_merge($attributes, $values));
    }

    if (empty($values)) {
        return true;
    }

    return (bool) $this->limit(1)->update($values);
}

First it checks if a record with the given attributes exists. When it doesn't, it will create one with insert.

When it does exist and $values are provided in the second parameter, it will update the record.

Since updateOrInsert is part of the query builder, it can also be called like this:

use Illuminate\Support\Facades\DB;

DB::table('users')->updateOrInsert(
    ['email' => 'brennan50@example.org', 'status' => 'DISABLED'], 
    ['name' => 'Christopher Jones', 'status' => 'ACTIVE']
);

However, as seen in the examples above, you can also call updateOrInsert directly from the model, in this case User::updateOrInsert.

How is this possible?

This needs a short explanation about the __call and __callStatic magic methods in PHP. As you can see in Illuminate\Eloquent\Model, the updateOrInsert method is not defined. But, when a class method is inaccessible in a static context, the __callStatic method will be triggered. As you can see in the __callStatic method definition in the Model class, this will create a new instance of the Model (in this case User), and try to call the updateOrInsert method again from an object context:

public static function __callStatic($method, $parameters)
{
    return (new static)->$method(...$parameters);
}

When a method is inaccessible in an object context, the __call method will be triggered. Now, when we look at this method in Illuminate\Eloquent\Model, we will see that it forwards the call like this :

return $this->forwardCallTo($this->newQuery(), $method, $parameters);

This means the call will be forwarded to Illuminate\Database\Eloquent\Builder, since newQuery (at last a method that is defined in the Model class!) will return an Eloquent Builder instance.

To make things harder, even on the Illuminate\Database\Eloquent\Builder class, the updateOrInsert method is not defined. Again, we turn to the __call method, where we see the call is forwarded:

$this->forwardCallTo($this->query, $method, $parameters);

Since $this->query is an instance of Illuminate\Database\Query\Builder, it will call the updateOrInsert method that is defined in this class.

Return types

Another difference is the return type.

As seen in the function definition, updateOrInsert will return a boolean when called from the DB facade.

However, when the method is called from the model, like in our case User::updateOrInsert(...), it will return Illuminate\Database\Eloquent\Builder. As explained earlier, it forwards the call first to the Illuminate\Database\Eloquent\Builder. Since the method doesn't exist there, the __call method in this class will be triggered. This method will return the Illuminate\Database\Eloquent\Builder object itself, and not the return value of the method it is forwarded to, i.e. the updateOrInsert method.

The updateOrCreate method on the other hand, will return the updated or created model.

Timestamps

Something else you will notice, is that updateOrInsert will not affect the created_at and updated_at timestamps. Even when called from the model like User::updateOrInsert(...), they will not be updated. This can result in a record with a created_at and updated_at value that is null.

The updateOrCreate method will always update the timestamps. This is because the method will call the save method, that is defined in the Model class, when the model is updated or created. As you can see in the definition of the save method, this will trigger model events that can change the timestamps.

Performance

This may lead to the conclusion that is better to use updateOrCreate in most situations. The updateOrCreate method will update the timestamps and return the updated or created model.

One latest distinction that I will mention here however, is that the updateOrInsert method can be faster.

I don't want to dive into this too far, since it depends on many things such as the environment and the amount of records and fields that are affected.

But when you need to "upsert" a large amount of records, performance can be a reason to prefer updateOrInsert.