framework: [5.2+] FirstOrCreate doesn't work as expected

In my opinion firstOrCreate method doesn’t work as expected. It does not apply only for this method but probably also for other Builder methods.

As example let’s use it like this:

$user =  User::firstOrCreate([
    'name' => 'test',
]);

(and assume we have only id and name fields in users table) for table database.

When we run this multiple times, we will always have only one user created with test name.

But now, let’s add mutator to User model like so:

public function setNameAttribute($value)
{
    $this->attributes['name'] = 'Mr '.$value;
}

let’s again make table empty.

And what happens now - each time we run the code we have new record created because mutator is not used for verifying the record existence. It might get even worse, because If we has one record before creating mutator, each time we run the code we would have returned this record however in fact it would not be what we expect.

As a quick fix I think in firstOrCreate this piece of code:

if (! is_null($instance = $this->where($attributes)->first())) {
    return $instance;
}

should be changed in something like this:

if (!is_null($instance = $this->where($this->model->newInstance($attributes)->toArray())->first())) {
    return $instance;
} 

what would make the mutators are applied now to verifying records existence.

However it would be a quite breaking change (though I think it’s the way it should always work this way) so probably it should be put into upcoming 5.3 release if decided it’s a bug.

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 17 (16 by maintainers)

Most upvoted comments

I think that the taylor’s suggestion on that PR will also fix the behavior described in your mutator example.

Also mutators can be dynamic, right?

public function setNameAttribute($value)
{
    $this->attributes['name'] = $this->attributes['title'].$value;
}

How would you deal with that?

I personally believe queries should stay decoupled from mutators, think of all the drama that could happen if you provide name and you find the Builder querying for Mr name instead.

I think for use case FirstOrCreate is not a good choice. Not sure if others think the same though 😃

I think the mutator shouldn’t affect the query, I mean you may decide to change the mutator at some point 'Mr. '.$value instead of 'Mr '.$value for example, if the query to be affected by the mutator the results would be different than expected.