Sergej Fomin
Sergej Fomin

Reputation: 1992

Laravel check if updateOrCreate performed an update

I have the following code in my controller:

for($i=0; $i<$number_of_tourists; $i++) {
    $tourist = Tourist::updateOrCreate([
        'doc_number' => $request['doc_number'][$I]
    ],
    $tourist_to_update);
}

Each time updateOrCreate runs, it does 1 of 3 things:

  1. Updates the model instance; OR

  2. Creates and saves a new one; OR

  3. Leaves everything unchanged (if model with such values already exists)

I need to check if updateOrCreate has done the first one (updated) and then execute some code.

How can I do it?

Upvotes: 51

Views: 41045

Answers (5)

Adam
Adam

Reputation: 28968

You can figure it out like this:

$tourist = Tourist::updateOrCreate([...]);

if(!$tourist->wasRecentlyCreated && $tourist->wasChanged()){
    // updateOrCreate performed an update
}

if(!$tourist->wasRecentlyCreated && !$tourist->wasChanged()){
    // updateOrCreate performed nothing, row did not change
}

if($tourist->wasRecentlyCreated){
   // updateOrCreate performed create
}

Remarks

From Laravel 5.5 upwards you can check if updates have actually taken place with the wasChanged and isDirty method.

  • isDirty() is true if model attribute has been changed and not saved.
  • wasChanged() is true if model attribute has been changed and saved.

There is also a property (not method!) wasRecentlyCreated to check if user was created or not.

$user = factory(\App\User::class)->create();

$user->wasRecentlyCreated; // true
$user->wasChanged(); // false
$user->isDirty(); // false

$user = \App\User::find($user->id);

$user->wasRecentlyCreated; // false
$user->wasChanged(); // false
$user->isDirty(); // false

$user->firstname = 'Max';

$user->wasChanged(); // false
$user->isDirty(); // true

$user->save();

$user->wasChanged(); // true
$user->isDirty(); // false

//You can also check if a specific attribute was changed:
$user->wasChanged('firstname');
$user->isDirty('firstname');

You can checkout the link to the laravel's documentation for wasChanged and isDirty methods.

https://laravel.com/docs/8.x/eloquent#examining-attribute-changes or https://laravel.com/docs/9.x/eloquent#examining-attribute-changes

Upvotes: 124

Jera
Jera

Reputation: 183

Okay so I couldn't find a good answer for my scenario.

I was using: $this->created_at == $this->updated_at however I would sometimes update the record later in the request, which meant that 20% of the time the created_at and updated_at were about 1ms out.

To combat this I created something a little more relaxed which allows an extra second between creation and modification.

public function getRecentlyCreatedAttribute()
{
    return $this->wasRecentlyCreated || $this->created_at == $this->updated_at || $this->created_at->diffInSeconds($this->updated_at) <= 1;
}

I can now call $this->recentlyCreated which will return true if there is a small difference in time (1 second).

Tbh this is the second time I've needed this in a project, I'm posting as I just ended up googling it and coming back to this thread looking for the same answer.

If someone has a more elegant solution, hmu.

Upvotes: 1

Naveed Ali
Naveed Ali

Reputation: 369

The model attribute 'wasRecentlyCreated' would only be 'true' if it has just been created.

There is property named 'changes' in model (it is an array), that determines whether the model has been updated with new values or it has been saved as is without making any changes to its attribute.

Check the following code snippet:

       // Case 1 :  Model Created
        if ($model->wasRecentlyCreated) {

        } else { // Case 2 :  Model Updated

            if (count($model->changes)) { // model has been assigned new values to one of its attributes and saved successfully

            } else { // model has NOT been assigned new values to one of its attributes and saved as is

            }

        }

Upvotes: 0

patricus
patricus

Reputation: 62228

It is pretty easy to determine if the function resulted in an update or an insert (check the wasRecentlyCreated property). However, when using that function, it is less easy to determine if the update actually happened (if the model exists but is not dirty, no update will be performed). I would suggest not using that function, and splitting out the functionality yourself.

This is the function definition:

public function updateOrCreate(array $attributes, array $values = [])
{
    $instance = $this->firstOrNew($attributes);

    $instance->fill($values)->save();

    return $instance;
}

To integrate this into your code, I'd suggest something like:

for ($i=0; $i<$number_of_tourists; $i++) {
    $tourist = Tourist::firstOrNew(['doc_number' => $request['doc_number'][$i]]);

    $tourist->fill($tourist_to_update);

    // if the record exists and the fill changed data, update will be performed
    $updated = $tourist->exists && $tourist->isDirty();

    // save the tourist (insert or update)
    $tourist->save();

    if ($updated) {
        // extra code
    }
}

Upvotes: 5

Sergej Fomin
Sergej Fomin

Reputation: 1992

@patricus below presented a working way to solve the problem. though @TheFallen here gave a solution which uses Eloquent Events and seems more elegant: Laravel Eloquent Events - implement to save model if Updated

Upvotes: 0

Related Questions