Eloquent's First or Create does not consider the mutator before checking if the record exists?

Viewed 125

Imagine that I have the following model:

class Link extends Model
{
    protected $fillable = ['path', 'secret'];

    public function setSecretAttribute($value)
    {
        $this->attributes['secret'] = sha1($value);
    }
}

And the following code:

$link = Link::firstOrCreate(['path' => $someValue, 'secret' => $anotherValue]);

Is there something wrong with my code or "firstOrCreate" ignore the mutator when it will check if the registry already exists? If it ignores, I just have to add sha1 encryption to "anotherValue" to get the behavior I expect. But my question is, wouldn't that be redundant?

1 Answers

Not a lot of detail in this question, but if path is a unique identifier this is easily done with the second argument to Model::firstOrCreate().

If the model can not be found in the database, a record will be inserted with the attributes resulting from merging the first array argument with the optional second array argument.

So your code would look like this:

$link = Link::firstOrCreate(
    ['path' => $someValue],
    ['secret' => $anotherValue]
);

If both values are required to uniquely identify the record, you can simply do a conditional check:

$link = Link::where('path', $someValue)
    ->where('secret', sha1($anotherValue))
    ->first();

if (!$link) {
    Link::create(['path' => $someValue, 'secret' => $anotherValue]);
}

Note that mutators apply only to database writes done via Eloquent methods. When you are reading the database, you must manually hash the value, as I've done.


To avoid manually hashing the value, the best option would be a query scope on the model like so:

class Link extends Model
{
    protected $fillable = ['path', 'secret'];

    public function setSecretAttribute($value)
    {
        $this->attributes['secret'] = sha1($value);
    }

    public function scopeSecretHashOf($query, $secret)
    {
        return $query->where('secret', '=', sha1($secret));
    }
}

Which you could then use like this:

$link = Link::where('path', $someValue)
    ->whereSecretHashOf($anotherValue)
    ->first();

if (!$link) {
    Link::create(['path' => $someValue, 'secret' => $anotherValue]);
}

In theory you could intercept and rewrite the query with a custom grammar but it would likely be more effort than it's worth.

Related