From 4428a3eea22dbe45a4d6bcde4c4a6ed47ece51b4 Mon Sep 17 00:00:00 2001 From: "Sebastian J. Bronner" Date: Thu, 14 Dec 2017 18:24:52 +0100 Subject: [PATCH 1/4] WIP --- phpunit.xml | 2 +- src/CachedBuilder.php | 13 +++++++++++++ src/CachedModel.php | 13 ++++++++++--- tests/Unit/CachedModelTest.php | 19 ++++++++++++++++++- 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index 730c6c8..c3ae6e6 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,7 +6,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="true" + stopOnFailure="false" syntaxCheck="false" bootstrap="vendor/autoload.php" > diff --git a/src/CachedBuilder.php b/src/CachedBuilder.php index b7d29ae..8354c88 100644 --- a/src/CachedBuilder.php +++ b/src/CachedBuilder.php @@ -7,6 +7,19 @@ class CachedBuilder extends EloquentBuilder { use Cachable; + protected $isCacheDisabled = false; + + public function __call(string $method, array $parameters) + { + if (method_exists($this, $method)) { + if (isCacheDisabled) { + return parent::$method($parameters); + } + + $this->$method($parameters); + } + } + public function avg($column) { return $this->cache($this->makeCacheTags()) diff --git a/src/CachedModel.php b/src/CachedModel.php index 008e333..5cc7b71 100644 --- a/src/CachedModel.php +++ b/src/CachedModel.php @@ -3,12 +3,12 @@ use GeneaLabs\LaravelModelCaching\CachedBuilder as Builder; use Illuminate\Cache\CacheManager; use Illuminate\Cache\TaggableStore; +use Illuminate\Cache\TaggedCache; +use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Relation; -use LogicException; - -use Illuminate\Cache\TaggedCache; use Illuminate\Support\Collection; +use LogicException; abstract class CachedModel extends Model { @@ -52,6 +52,13 @@ public function cache(array $tags = []) return $cache; } + public function scopeDisableCache(EloquentBuilder $query) : EloquentBuilder + { + $query->disableCache(); + + return $query; + } + public function flushCache(array $tags = []) { $this->cache($tags)->flush(); diff --git a/tests/Unit/CachedModelTest.php b/tests/Unit/CachedModelTest.php index e93b6ef..2e5024f 100644 --- a/tests/Unit/CachedModelTest.php +++ b/tests/Unit/CachedModelTest.php @@ -24,7 +24,7 @@ public function setUp() cache()->flush(); $publishers = factory(Publisher::class, 10)->create(); factory(Author::class, 10)->create() - ->each(function($author) use ($publishers) { + ->each(function ($author) use ($publishers) { factory(Book::class, random_int(2, 10))->make() ->each(function ($book) use ($author, $publishers) { $book->author()->associate($author); @@ -59,4 +59,21 @@ public function testAllModelResultsCreatesCache() $this->assertEquals($authors, $cachedResults); $this->assertEmpty($liveResults->diffAssoc($cachedResults)); } + + public function testScopeDisablesCaching() + { + $key = 'genealabslaravelmodelcachingtestsfixturesauthor'; + $tags = ['genealabslaravelmodelcachingtestsfixturesauthor']; + $authors = (new Author) + ->where("name", "Bruno") + ->disableCache() + ->get(); + + $cachedResults = cache() + ->tags($tags) + ->get($key); + + $this->assertNull($cachedResults); + $this->assertNotEquals($authors, $cachedResults); + } } From 0b0a5bb393baef20c3c3feae7603a9839dc47182 Mon Sep 17 00:00:00 2001 From: Mike Bronner Date: Thu, 14 Dec 2017 12:18:44 -0800 Subject: [PATCH 2/4] Finish implementing optional cache disabling --- src/CachedBuilder.php | 61 ++++- src/CachedModel.php | 12 +- tests/Unit/CachedBuilderTest.php | 2 +- tests/Unit/CachedModelTest.php | 9 +- tests/Unit/DisabledCachedBuilderTest.php | 333 +++++++++++++++++++++++ tests/Unit/DisabledCachedModelTest.php | 64 +++++ 6 files changed, 464 insertions(+), 17 deletions(-) create mode 100644 tests/Unit/DisabledCachedBuilderTest.php create mode 100644 tests/Unit/DisabledCachedModelTest.php diff --git a/src/CachedBuilder.php b/src/CachedBuilder.php index 8354c88..a2fbeaa 100644 --- a/src/CachedBuilder.php +++ b/src/CachedBuilder.php @@ -3,25 +3,21 @@ use GeneaLabs\LaravelModelCaching\Traits\Cachable; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; +/** + * @SuppressWarnings(PHPMD.TooManyPublicMethods) + */ class CachedBuilder extends EloquentBuilder { use Cachable; - protected $isCacheDisabled = false; + protected $isCachable = true; - public function __call(string $method, array $parameters) + public function avg($column) { - if (method_exists($this, $method)) { - if (isCacheDisabled) { - return parent::$method($parameters); - } - - $this->$method($parameters); + if (! $this->isCachable) { + return parent::avg($column); } - } - public function avg($column) - { return $this->cache($this->makeCacheTags()) ->rememberForever($this->makeCacheKey() . "-avg_{$column}", function () use ($column) { return parent::avg($column); @@ -30,6 +26,10 @@ public function avg($column) public function count($columns = ['*']) { + if (! $this->isCachable) { + return parent::count($columns); + } + return $this->cache($this->makeCacheTags()) ->rememberForever($this->makeCacheKey() . "-count", function () use ($columns) { return parent::count($columns); @@ -38,6 +38,10 @@ public function count($columns = ['*']) public function cursor() { + if (! $this->isCachable) { + return collect(parent::cursor()); + } + return $this->cache($this->makeCacheTags()) ->rememberForever($this->makeCacheKey() . "-cursor", function () { return collect(parent::cursor()); @@ -52,11 +56,22 @@ public function delete() return parent::delete(); } + public function disableCache() + { + $this->isCachable = false; + + return $this; + } + /** * @SuppressWarnings(PHPMD.ShortVariable) */ public function find($id, $columns = ['*']) { + if (! $this->isCachable) { + return parent::find($id, $columns); + } + return $this->cache($this->makeCacheTags()) ->rememberForever($this->makeCacheKey($columns, $id), function () use ($id, $columns) { return parent::find($id, $columns); @@ -65,6 +80,10 @@ public function find($id, $columns = ['*']) public function first($columns = ['*']) { + if (! $this->isCachable) { + return parent::first($columns); + } + return $this->cache($this->makeCacheTags()) ->rememberForever($this->makeCacheKey($columns) . '-first', function () use ($columns) { return parent::first($columns); @@ -73,6 +92,10 @@ public function first($columns = ['*']) public function get($columns = ['*']) { + if (! $this->isCachable) { + return parent::get($columns); + } + return $this->cache($this->makeCacheTags()) ->rememberForever($this->makeCacheKey($columns), function () use ($columns) { return parent::get($columns); @@ -81,6 +104,10 @@ public function get($columns = ['*']) public function max($column) { + if (! $this->isCachable) { + return parent::max($column); + } + return $this->cache($this->makeCacheTags()) ->rememberForever($this->makeCacheKey() . "-max_{$column}", function () use ($column) { return parent::max($column); @@ -89,6 +116,10 @@ public function max($column) public function min($column) { + if (! $this->isCachable) { + return parent::min($column); + } + return $this->cache($this->makeCacheTags()) ->rememberForever($this->makeCacheKey() . "-min_{$column}", function () use ($column) { return parent::min($column); @@ -97,6 +128,10 @@ public function min($column) public function pluck($column, $key = null) { + if (! $this->isCachable) { + return parent::pluck($column, $key); + } + $cacheKey = $this->makeCacheKey([$column]) . "-pluck_{$column}"; if ($key) { @@ -111,6 +146,10 @@ public function pluck($column, $key = null) public function sum($column) { + if (! $this->isCachable) { + return parent::sum($column); + } + return $this->cache($this->makeCacheTags()) ->rememberForever($this->makeCacheKey() . "-sum_{$column}", function () use ($column) { return parent::sum($column); diff --git a/src/CachedModel.php b/src/CachedModel.php index 5cc7b71..c911d7f 100644 --- a/src/CachedModel.php +++ b/src/CachedModel.php @@ -14,6 +14,12 @@ abstract class CachedModel extends Model { public function newEloquentBuilder($query) { + if (session('genealabs-laravel-model-caching-is-disabled')) { + session()->forget('genealabs-laravel-model-caching-is-disabled'); + + return new EloquentBuilder($query); + } + return new Builder($query); } @@ -52,11 +58,11 @@ public function cache(array $tags = []) return $cache; } - public function scopeDisableCache(EloquentBuilder $query) : EloquentBuilder + public function disableCache() : self { - $query->disableCache(); + session(['genealabs-laravel-model-caching-is-disabled' => true]); - return $query; + return $this; } public function flushCache(array $tags = []) diff --git a/tests/Unit/CachedBuilderTest.php b/tests/Unit/CachedBuilderTest.php index a79519e..fbaaa28 100644 --- a/tests/Unit/CachedBuilderTest.php +++ b/tests/Unit/CachedBuilderTest.php @@ -29,7 +29,7 @@ public function setUp() cache()->flush(); $publishers = factory(Publisher::class, 10)->create(); factory(Author::class, 10)->create() - ->each(function($author) use ($publishers) { + ->each(function ($author) use ($publishers) { factory(Book::class, random_int(2, 10))->make() ->each(function ($book) use ($author, $publishers) { $book->author()->associate($author); diff --git a/tests/Unit/CachedModelTest.php b/tests/Unit/CachedModelTest.php index 2e5024f..db34ec4 100644 --- a/tests/Unit/CachedModelTest.php +++ b/tests/Unit/CachedModelTest.php @@ -52,14 +52,19 @@ public function testAllModelResultsCreatesCache() 'genealabslaravelmodelcachingtestsfixturesauthor', ]; - $cachedResults = cache()->tags($tags) + $cachedResults = cache() + ->tags($tags) ->get($key); - $liveResults = (new UncachedAuthor)->all(); + $liveResults = (new UncachedAuthor) + ->all(); $this->assertEquals($authors, $cachedResults); $this->assertEmpty($liveResults->diffAssoc($cachedResults)); } + /** + * @group test + **/ public function testScopeDisablesCaching() { $key = 'genealabslaravelmodelcachingtestsfixturesauthor'; diff --git a/tests/Unit/DisabledCachedBuilderTest.php b/tests/Unit/DisabledCachedBuilderTest.php new file mode 100644 index 0000000..249a994 --- /dev/null +++ b/tests/Unit/DisabledCachedBuilderTest.php @@ -0,0 +1,333 @@ +flush(); + $publishers = factory(Publisher::class, 10)->create(); + factory(Author::class, 10)->create() + ->each(function ($author) use ($publishers) { + factory(Book::class, random_int(2, 10))->make() + ->each(function ($book) use ($author, $publishers) { + $book->author()->associate($author); + $book->publisher()->associate($publishers[rand(0, 9)]); + $book->save(); + }); + factory(Profile::class)->make([ + 'author_id' => $author->id, + ]); + }); + + $bookIds = (new Book)->all()->pluck('id'); + factory(Store::class, 10)->create() + ->each(function ($store) use ($bookIds) { + $store->books()->sync(rand($bookIds->min(), $bookIds->max())); + }); + cache()->flush(); + } + + public function testAvgModelResultsIsNotCached() + { + $authorId = (new Author) + ->with('books', 'profile') + ->disableCache() + ->avg('id'); + $key = 'genealabslaravelmodelcachingtestsfixturesauthor-books-profile-avg_id'; + $tags = [ + 'genealabslaravelmodelcachingtestsfixturesauthor', + 'genealabslaravelmodelcachingtestsfixturesbook', + 'genealabslaravelmodelcachingtestsfixturesprofile', + ]; + + $cachedResult = cache() + ->tags($tags) + ->get($key); + $liveResult = (new UncachedAuthor) + ->with('books', 'profile') + ->avg('id'); + + $this->assertEquals($authorId, $liveResult); + $this->assertNull($cachedResult); + } + + public function testChunkModelResultsIsNotCached() + { + $cachedChunks = collect([ + 'authors' => collect(), + 'keys' => collect(), + ]); + $chunkSize = 3; + $tags = [ + 'genealabslaravelmodelcachingtestsfixturesauthor', + 'genealabslaravelmodelcachingtestsfixturesbook', + 'genealabslaravelmodelcachingtestsfixturesprofile', + ]; + $uncachedChunks = collect(); + + $authors = (new Author)->with('books', 'profile') + ->disableCache() + ->chunk($chunkSize, function ($chunk) use (&$cachedChunks, $chunkSize) { + $offset = ''; + + if ($cachedChunks['authors']->count()) { + $offsetIncrement = $cachedChunks['authors']->count() * $chunkSize; + $offset = "-offset_{$offsetIncrement}"; + } + + $cachedChunks['authors']->push($chunk); + $cachedChunks['keys']->push("genealabslaravelmodelcachingtestsfixturesauthor-books-profile_orderBy_authors.id_asc{$offset}-limit_3"); + }); + + $liveResults = (new UncachedAuthor)->with('books', 'profile') + ->chunk($chunkSize, function ($chunk) use (&$uncachedChunks) { + $uncachedChunks->push($chunk); + }); + + for ($index = 0; $index < $cachedChunks['authors']->count(); $index++) { + $key = $cachedChunks['keys'][$index]; + $cachedResults = cache()->tags($tags) + ->get($key); + + $this->assertNull($cachedResults); + $this->assertEquals($authors, $liveResults); + } + } + + public function testCountModelResultsIsNotCached() + { + $authors = (new Author) + ->with('books', 'profile') + ->disableCache() + ->count(); + $key = 'genealabslaravelmodelcachingtestsfixturesauthor-books-profile-count'; + $tags = [ + 'genealabslaravelmodelcachingtestsfixturesauthor', + 'genealabslaravelmodelcachingtestsfixturesbook', + 'genealabslaravelmodelcachingtestsfixturesprofile', + ]; + + $cachedResults = cache() + ->tags($tags) + ->get($key); + $liveResults = (new UncachedAuthor) + ->with('books', 'profile') + ->count(); + + $this->assertEquals($authors, $liveResults); + $this->assertNull($cachedResults); + } + + public function testCursorModelResultsIsNotCached() + { + $authors = (new Author) + ->with('books', 'profile') + ->disableCache() + ->cursor(); + $key = 'genealabslaravelmodelcachingtestsfixturesauthor-books-profile-cursor'; + $tags = [ + 'genealabslaravelmodelcachingtestsfixturesauthor', + 'genealabslaravelmodelcachingtestsfixturesbook', + 'genealabslaravelmodelcachingtestsfixturesprofile', + ]; + + $cachedResults = cache() + ->tags($tags) + ->get($key); + $liveResults = collect( + (new UncachedAuthor) + ->with('books', 'profile') + ->cursor() + ); + + $this->assertEmpty($liveResults->diffAssoc($authors)); + $this->assertNull($cachedResults); + } + + public function testFindModelResultsIsNotCached() + { + $author = (new Author) + ->disableCache() + ->find(1); + $key = 'genealabslaravelmodelcachingtestsfixturesauthor_1'; + $tags = [ + 'genealabslaravelmodelcachingtestsfixturesauthor', + ]; + + $cachedResult = cache() + ->tags($tags) + ->get($key); + $liveResult = (new UncachedAuthor) + ->find(1); + + $this->assertEquals($liveResult->name, $author->name); + $this->assertNull($cachedResult); + } + + public function testGetModelResultsIsNotCached() + { + $authors = (new Author) + ->with('books', 'profile') + ->disableCache() + ->get(); + $key = 'genealabslaravelmodelcachingtestsfixturesauthor-books-profile'; + $tags = [ + 'genealabslaravelmodelcachingtestsfixturesauthor', + 'genealabslaravelmodelcachingtestsfixturesbook', + 'genealabslaravelmodelcachingtestsfixturesprofile', + ]; + + $cachedResults = cache() + ->tags($tags) + ->get($key); + $liveResults = (new UncachedAuthor) + ->with('books', 'profile') + ->get(); + + $this->assertEmpty($liveResults->diffAssoc($authors)); + $this->assertNull($cachedResults); + } + + public function testMaxModelResultsIsNotCached() + { + $authorId = (new Author) + ->with('books', 'profile') + ->disableCache() + ->max('id'); + $key = 'genealabslaravelmodelcachingtestsfixturesauthor-books-profile-max_id'; + $tags = [ + 'genealabslaravelmodelcachingtestsfixturesauthor', + 'genealabslaravelmodelcachingtestsfixturesbook', + 'genealabslaravelmodelcachingtestsfixturesprofile', + ]; + + $cachedResult = cache() + ->tags($tags) + ->get($key); + $liveResult = (new UncachedAuthor) + ->with('books', 'profile') + ->max('id'); + + $this->assertEquals($authorId, $liveResult); + $this->assertNull($cachedResult); + } + + public function testMinModelResultsIsNotCached() + { + $authorId = (new Author) + ->with('books', 'profile') + ->disableCache() + ->min('id'); + $key = 'genealabslaravelmodelcachingtestsfixturesauthor-books-profile-min_id'; + $tags = [ + 'genealabslaravelmodelcachingtestsfixturesauthor', + 'genealabslaravelmodelcachingtestsfixturesbook', + 'genealabslaravelmodelcachingtestsfixturesprofile', + ]; + + $cachedResult = cache() + ->tags($tags) + ->get($key); + $liveResult = (new UncachedAuthor) + ->with('books', 'profile') + ->min('id'); + + $this->assertEquals($authorId, $liveResult); + $this->assertNull($cachedResult); + } + + public function testPluckModelResultsIsNotCached() + { + $authors = (new Author) + ->with('books', 'profile') + ->disableCache() + ->pluck('name', 'id'); + $key = 'genealabslaravelmodelcachingtestsfixturesauthor_name-books-profile-pluck_name_id'; + $tags = [ + 'genealabslaravelmodelcachingtestsfixturesauthor', + 'genealabslaravelmodelcachingtestsfixturesbook', + 'genealabslaravelmodelcachingtestsfixturesprofile', + ]; + + $cachedResults = cache() + ->tags($tags) + ->get($key); + $liveResults = (new UncachedAuthor) + ->with('books', 'profile') + ->pluck('name', 'id'); + + $this->assertEmpty($liveResults->diffAssoc($authors)); + $this->assertNull($cachedResults); + } + + public function testSumModelResultsIsNotCached() + { + $authorId = (new Author) + ->with('books', 'profile') + ->disableCache() + ->sum('id'); + $key = 'genealabslaravelmodelcachingtestsfixturesauthor-books-profile-sum_id'; + $tags = [ + 'genealabslaravelmodelcachingtestsfixturesauthor', + 'genealabslaravelmodelcachingtestsfixturesbook', + 'genealabslaravelmodelcachingtestsfixturesprofile', + ]; + + $cachedResult = cache() + ->tags($tags) + ->get($key); + $liveResult = (new UncachedAuthor) + ->with('books', 'profile') + ->sum('id'); + + $this->assertEquals($authorId, $liveResult); + $this->assertNull($cachedResult); + } + + public function testValueModelResultsIsNotCached() + { + $author = (new Author) + ->with('books', 'profile') + ->disableCache() + ->value('name'); + $key = 'genealabslaravelmodelcachingtestsfixturesauthor_name-books-profile-first'; + $tags = [ + 'genealabslaravelmodelcachingtestsfixturesauthor', + 'genealabslaravelmodelcachingtestsfixturesbook', + 'genealabslaravelmodelcachingtestsfixturesprofile', + ]; + + $cachedResult = cache() + ->tags($tags) + ->get($key); + + $liveResult = (new UncachedAuthor) + ->with('books', 'profile') + ->value('name'); + + $this->assertEquals($author, $liveResult); + $this->assertNull($cachedResult); + } +} diff --git a/tests/Unit/DisabledCachedModelTest.php b/tests/Unit/DisabledCachedModelTest.php new file mode 100644 index 0000000..cae8e60 --- /dev/null +++ b/tests/Unit/DisabledCachedModelTest.php @@ -0,0 +1,64 @@ +flush(); + $publishers = factory(Publisher::class, 10)->create(); + factory(Author::class, 10)->create() + ->each(function ($author) use ($publishers) { + factory(Book::class, random_int(2, 10))->make() + ->each(function ($book) use ($author, $publishers) { + $book->author()->associate($author); + $book->publisher()->associate($publishers[rand(0, 9)]); + $book->save(); + }); + factory(Profile::class)->make([ + 'author_id' => $author->id, + ]); + }); + + $bookIds = (new Book)->all()->pluck('id'); + factory(Store::class, 10)->create() + ->each(function ($store) use ($bookIds) { + $store->books()->sync(rand($bookIds->min(), $bookIds->max())); + }); + cache()->flush(); + } + + public function testAllModelResultsIsNotCached() + { + $key = 'genealabslaravelmodelcachingtestsfixturesauthor'; + $tags = ['genealabslaravelmodelcachingtestsfixturesauthor']; + $authors = (new Author) + ->disableCache() + ->all(); + + $cachedResults = cache() + ->tags($tags) + ->get($key); + $liveResults = (new UncachedAuthor) + ->all(); + + $this->assertEmpty($liveResults->diffAssoc($authors)); + $this->assertNull($cachedResults); + } +} From 7fb08e194113767aed549f1d58fa919f0e64791b Mon Sep 17 00:00:00 2001 From: Mike Bronner Date: Thu, 14 Dec 2017 12:51:15 -0800 Subject: [PATCH 3/4] Update documentation --- CHANGELOG.md | 4 ++++ README.md | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b55be71..3ef0ba9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.2.9] - 14 Dec 2017 +### Added +- chainable method to disable caching of queries. + ## [0.2.8] - 2017-10-17 ### Updated - code with optimizations and refactoring. diff --git a/README.md b/README.md index 88b9a95..60b0755 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,16 @@ abstract class BaseModel extends CachedModel } ``` +### Disabling Caching of Queries +**Recommendation: add this to all your seeder queries to avoid pulling in +cacched information when reseeding multiple times.** +You can disable a given query by using `disableCache()` in the query chain, and +it needs to be placed (anywhere) prior to the query command (`get()`, `all()`, +`find()`, etc). For example: +```php +$results = $myModel->disableCache()->all(); +``` + **That's all you need to do. All model queries and relationships are now cached!** From bbdf79c7876e08d7988e53eb38e2d8f338de817d Mon Sep 17 00:00:00 2001 From: Mike Bronner Date: Thu, 14 Dec 2017 12:53:41 -0800 Subject: [PATCH 4/4] Update changelog --- CHANGELOG.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ef0ba9..3b157c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,27 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## [0.2.9] - 14 Dec 2017 +## [0.2.12] - 14 Dec 2017 ### Added - chainable method to disable caching of queries. +## [0.2.11] - 13 Dec 2017 +### Added +- functionality to clear corresponding cache tags when model is deleted. + +## [0.2.10] - 5 Dec 2017 +### Fixed +- caching when using `orderByRaw()`. + +## [0.2.9] - 19 Nov 2017 +### Added +- test for query scopes. +- test for relationship query. + +### Updated +- readme file. +- travis configuration. + ## [0.2.8] - 2017-10-17 ### Updated - code with optimizations and refactoring.