Let’s say I have a model Foobar
with relationships named baz
and quux
. Foobar
belongsTo Baz
and Baz
hasOne Foobar
. This means there is a foreign key column in the Foobar
table for baz_id
. The relationships are defined correctly in the models. In case it is relevant, this model caching library is used on both models: https://github.com/GeneaLabs/laravel-model-caching
I would like to query all Foobar
that do not have either relationship; meaning I only want to select Foobar
where BOTH baz
and quux
relationships are absent. I do so as follows:
Foobar::doesntHave('baz', 'AND')->doesntHave('quux')->get();
Now let’s say $foobar
is a Foobar
instance with no relationships, and $baz
is a freshly created Baz
instance. I associate the two like so:
$baz->foobar()->associate($foobar); $baz->save();
Now, I run the above query again. The row represented by $foobar
is still appearing in the results, even though it shouldn’t since it now has a non-null baz
relationship. My question is: why is this happening, and how can I fix it?
I played around in artisan console while debugging this. In the same artisan session:
Foobar::doesntHave('baz', 'AND')->doesntHave('quux')->get(); // This gets a collection that is not empty, the first item is a Foobar instance that definitely has a baz Foobar::doesntHave('baz', 'AND')->doesntHave('quux')->first(); // This is null (?) Foobar::doesntHave('baz', 'AND')->doesntHave('quux')->count(); // This is 0 (?) Foobar::doesntHave('baz', 'AND')->doesntHave('quux')->get()->get(0)->baz; // This gets the Baz object attached to the first instance
To double-check that the SQL is correct:
Foobar::doesntHave('baz', 'AND')->doesntHave('quux')->get()->toSql(); // This outputs the generated raw SQL.
I input the raw SQL into a SQL client and got the correct result.
I also attempted / checked while debugging:
$foobar->touch()
– did not change results.- Made sure API endpoint was not cached by browser, Cloudflare, or anything else
Advertisement
Answer
I discovered the issue originated in the model caching library used on these models: https://github.com/GeneaLabs/laravel-model-caching
When I ran the library’s command for completely invalidating Foobar
cache, it resolved the issue. It looks like the library does not correctly invalidate cache in this scenario, though I can’t explain why without digging deeper into the library. Their README indicates this may be a known issue.
php artisan modelCache:clear --model='AppModelsFoobar'
To work around it, a few options came to mind:
- Programmatically run the artisan command for clearing model cache whenever the relationship is updated.
- Rewrite query to bypass model caching.
- Look for different model caching library.
We opted to remove model caching from these models entirely; we were able to do so because it was an “easy” premature optimization, but may not be necessary. We reasoned that we will remove the premature optimization for now and worry about it again only if it causes problems.