Skip to content

Commit 7120884

Browse files
committed
Update functionality to prevent caching of models with relationships that are not cachable.
1 parent ece9a9c commit 7120884

File tree

10 files changed

+140
-37
lines changed

10 files changed

+140
-37
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
55
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
66

7+
## [0.7.2] - 2019-10-12
8+
### Fixed
9+
- improper caching of non-cachable eagerloaded relationships. Now any query with non-cachable eagerloaded relationships will not be cached in its entirety to prevent stale data. Special thanks to @Hornet-Wing for his help in this.
10+
11+
## [0.7.1] - 2019-10-02
12+
### Fixed
13+
- dependency version constraints.
14+
### Added
15+
- various tests.
16+
17+
## [0.7.0] - 2019-08-28
18+
### Added
19+
- Laravel 6.0 support.
20+
721
## [0.6.3] - 2019-08-27
822
### Fixed
923
- caching of methods that could pass field names as string or array.

src/Traits/Caching.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,9 +273,37 @@ public function isCachable() : bool
273273
$isCacheDisabled = ! Container::getInstance()
274274
->make("config")
275275
->get("laravel-model-caching.enabled");
276+
$allRelationshipsAreCachable = true;
277+
278+
if (property_exists($this, "eagerLoad")
279+
&& $this->eagerLoad
280+
) {
281+
$allRelationshipsAreCachable = collect($this
282+
->eagerLoad)
283+
->keys()
284+
->reduce(function ($carry, $related) {
285+
if (! method_exists($this, $related)
286+
|| $carry === false
287+
) {
288+
return $carry;
289+
}
290+
291+
$relatedModel = $this->model->$related()->getRelated();
292+
293+
if (! method_exists($relatedModel, "isCachable")
294+
|| ! $relatedModel->isCachable()
295+
) {
296+
return false;
297+
}
298+
299+
return true;
300+
})
301+
?? true;
302+
}
276303

277304
return $this->isCachable
278-
&& ! $isCacheDisabled;
305+
&& ! $isCacheDisabled
306+
&& $allRelationshipsAreCachable;
279307
}
280308

281309
protected function setCacheCooldownSavedAtTimestamp(Model $instance)

tests/Fixtures/Book.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,6 @@ public function stores() : BelongsToMany
5252
return $this->belongsToMany(Store::class);
5353
}
5454

55-
public function uncachedStores() : BelongsToMany
56-
{
57-
return $this->belongsToMany(UncachedStore::class, "book_store", "book_id", "store_id");
58-
}
59-
6055
public function scopeStartsWith(Builder $query, string $startOfName) : Builder
6156
{
6257
return $query->where("name", "LIKE", "{$startOfName}%");
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php namespace GeneaLabs\LaravelModelCaching\Tests\Fixtures;
2+
3+
use GeneaLabs\LaravelModelCaching\Traits\Cachable;
4+
use Illuminate\Database\Eloquent\Builder;
5+
use Illuminate\Database\Eloquent\Model;
6+
use Illuminate\Database\Eloquent\Relations\BelongsTo;
7+
use Illuminate\Database\Eloquent\Relations\MorphMany;
8+
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
9+
use Illuminate\Database\Eloquent\Relations\MorphOne;
10+
11+
class BookWithUncachedStore extends Model
12+
{
13+
use Cachable;
14+
15+
protected $casts = [
16+
'price' => 'float',
17+
];
18+
protected $dates = [
19+
'published_at',
20+
];
21+
protected $fillable = [
22+
"author_id",
23+
'description',
24+
'published_at',
25+
'title',
26+
"publisher_id",
27+
'price',
28+
];
29+
protected $table = "books";
30+
31+
public function author() : BelongsTo
32+
{
33+
return $this->belongsTo(Author::class);
34+
}
35+
36+
public function comments() : MorphMany
37+
{
38+
return $this->morphMany(Comment::class, "commentable");
39+
}
40+
41+
public function image() : MorphOne
42+
{
43+
return $this->morphOne(Image::class, "imagable");
44+
}
45+
46+
public function publisher() : BelongsTo
47+
{
48+
return $this->belongsTo(Publisher::class);
49+
}
50+
51+
public function stores() : BelongsToMany
52+
{
53+
return $this->belongsToMany(Store::class);
54+
}
55+
56+
public function uncachedStores() : BelongsToMany
57+
{
58+
return $this->belongsToMany(UncachedStore::class, "book_store", "book_id", "store_id");
59+
}
60+
61+
public function scopeStartsWith(Builder $query, string $startOfName) : Builder
62+
{
63+
return $query->where("name", "LIKE", "{$startOfName}%");
64+
}
65+
}

tests/Integration/CachedBuilder/BelongsToManyTest.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
<?php namespace GeneaLabs\LaravelModelCaching\Tests\Integration\CachedBuilder;
22

33
use GeneaLabs\LaravelModelCaching\Tests\Fixtures\Book;
4+
use GeneaLabs\LaravelModelCaching\Tests\Fixtures\BookWithUncachedStore;
45
use GeneaLabs\LaravelModelCaching\Tests\Fixtures\Store;
56
use GeneaLabs\LaravelModelCaching\Tests\Fixtures\UncachedBook;
67
use GeneaLabs\LaravelModelCaching\Tests\IntegrationTestCase;
7-
use GeneaLabs\LaravelModelCaching\Tests\Fixtures\UncachedBookWithCachedStores;
8-
use GeneaLabs\LaravelModelCaching\Tests\Fixtures\StoreWithUncachedBooks;
9-
use GeneaLabs\LaravelModelCaching\Tests\Fixtures\BookWithUncachedStores;
10-
use GeneaLabs\LaravelModelCaching\Tests\Fixtures\UncachedBookWithStores;
118

129
class BelongsToManyTest extends IntegrationTestCase
1310
{
@@ -147,7 +144,7 @@ public function testUncachedRelatedModelDoesntCache()
147144
"genealabs:laravel-model-caching:testing:{$this->testingSqlitePath}testing.sqlite:genealabslaravelmodelcachingtestsfixturesuncachedstore",
148145
];
149146

150-
$result = (new Book)
147+
$result = (new BookWithUncachedStore)
151148
->find($bookId)
152149
->uncachedStores;
153150
$cachedResult = $this

tests/Integration/CachedBuilder/GetTest.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ class GetTest extends IntegrationTestCase
88
{
99
public function testGetModelResultsCreatesCache()
1010
{
11-
$authors = (new Author)->with('books', 'profile')
11+
$authors = (new Author)
12+
->with('books', 'profile')
1213
->get();
1314
$key = sha1("genealabs:laravel-model-caching:testing:{$this->testingSqlitePath}testing.sqlite:authors:genealabslaravelmodelcachingtestsfixturesauthor-authors.deleted_at_null-testing:{$this->testingSqlitePath}testing.sqlite:books-testing:{$this->testingSqlitePath}testing.sqlite:profile");
1415
$tags = [
@@ -17,9 +18,12 @@ public function testGetModelResultsCreatesCache()
1718
"genealabs:laravel-model-caching:testing:{$this->testingSqlitePath}testing.sqlite:genealabslaravelmodelcachingtestsfixturesprofile",
1819
];
1920

20-
$cachedResults = $this->cache()->tags($tags)
21+
$cachedResults = $this
22+
->cache()
23+
->tags($tags)
2124
->get($key)['value'];
22-
$liveResults = (new UncachedAuthor)->with('books', 'profile')
25+
$liveResults = (new UncachedAuthor)
26+
->with('books', 'profile')
2327
->get();
2428

2529
$this->assertEquals($authors, $cachedResults);

tests/Integration/CachedBuilder/WhereHasTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php namespace GeneaLabs\LaravelModelCaching\Tests\Integration\CachedBuilder;
22

33
use GeneaLabs\LaravelModelCaching\Tests\Fixtures\Author;
4+
use GeneaLabs\LaravelModelCaching\Tests\Fixtures\BookWithUncachedStore;
45
use GeneaLabs\LaravelModelCaching\Tests\Fixtures\UncachedAuthor;
56
use GeneaLabs\LaravelModelCaching\Tests\IntegrationTestCase;
67

@@ -35,4 +36,26 @@ public function testNestedWhereHasClauses()
3536

3637
$this->assertEquals($authors->pluck("id"), $uncachedAuthors->pluck("id"));
3738
}
39+
40+
public function testNonCachedRelationshipPreventsCaching()
41+
{
42+
$book = (new BookWithUncachedStore)
43+
->with("uncachedStores")
44+
->whereHas("uncachedStores")
45+
->get()
46+
->first();
47+
$store = $book->uncachedStores->first();
48+
$store->name = "Waterstones";
49+
$store->save();
50+
$results = $this->cache()->tags([
51+
"genealabs:laravel-model-caching:testing:{$this->testingSqlitePath}testing.sqlite:genealabslaravelmodelcachingtestsfixturesbook",
52+
"genealabs:laravel-model-caching:testing:{$this->testingSqlitePath}testing.sqlite:genealabslaravelmodelcachingtestsfixturesuncachedstore"
53+
])
54+
->get(sha1(
55+
"genealabs:laravel-model-caching:testing:{$this->testingSqlitePath}testing.sqlite:books:genealabslaravelmodelcachingtestsfixturesbook-exists-" .
56+
"and_books.id_=_book_store.book_id-testing:{$this->testingSqlitePath}testing.sqlite:uncachedStores"
57+
));
58+
59+
$this->assertNull($results);
60+
}
3861
}

tests/Integration/CachedBuilderRelationshipsTest.php

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -44,27 +44,4 @@ public function testWhereHasRelationship()
4444

4545
$this->assertEquals($books->pluck("id"), $uncachedBooks->pluck("id"));
4646
}
47-
48-
public function testNonCacheableEagerLoadedRelationsAreCleared()
49-
{
50-
$book = Book::with("uncachedStores")
51-
->whereHas("uncachedStores")
52-
->get()
53-
->first();
54-
55-
$store = $book->uncachedStores->first();
56-
$store->name = "Waterstones";
57-
$store->save();
58-
59-
$results = $this->cache()->tags([
60-
"genealabs:laravel-model-caching:testing:{$this->testingSqlitePath}testing.sqlite:genealabslaravelmodelcachingtestsfixturesbook",
61-
"genealabs:laravel-model-caching:testing:{$this->testingSqlitePath}testing.sqlite:genealabslaravelmodelcachingtestsfixturesuncachedstore"
62-
])
63-
->get(sha1(
64-
"genealabs:laravel-model-caching:testing:{$this->testingSqlitePath}testing.sqlite:books:genealabslaravelmodelcachingtestsfixturesbook-exists-" .
65-
"and_books.id_=_book_store.book_id-testing:{$this->testingSqlitePath}testing.sqlite:uncachedStores"
66-
));
67-
68-
$this->assertNull($results);
69-
}
7047
}

tests/database/baseline.sqlite

-8 KB
Binary file not shown.

tests/database/testing.sqlite

-8 KB
Binary file not shown.

0 commit comments

Comments
 (0)