diff --git a/CHANGELOG.md b/CHANGELOG.md index 65934f9..3012e0d 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.29] - 18 Feb 2018 +### Added +- hash collision detection and prevetion. + ## [0.2.28] - 18 Feb 2018 ### Changed - disabling of cache from using session to use cache-key instead. diff --git a/src/CacheKey.php b/src/CacheKey.php index 7f6d165..139feb0 100644 --- a/src/CacheKey.php +++ b/src/CacheKey.php @@ -31,7 +31,6 @@ public function make( $key .= $this->getOffsetClause(); $key .= $this->getLimitClause(); $key .= $keyDifferentiator; - $key = sha1($key); return $key; } diff --git a/src/CachedBuilder.php b/src/CachedBuilder.php index ec84894..e5035c6 100644 --- a/src/CachedBuilder.php +++ b/src/CachedBuilder.php @@ -16,13 +16,11 @@ public function avg($column) return parent::avg($column); } - return $this->cache($this->makeCacheTags()) - ->rememberForever( - $this->makeCacheKey(['*'], null, "-avg_{$column}"), - function () use ($column) { - return parent::avg($column); - } - ); + $arguments = func_get_args(); + $cacheKey = $this->makeCacheKey(['*'], null, "-avg_{$column}"); + $method = 'avg'; + + return $this->cachedValue($arguments, $cacheKey, $method); } public function count($columns = ['*']) @@ -31,13 +29,11 @@ public function count($columns = ['*']) return parent::count($columns); } - return $this->cache($this->makeCacheTags()) - ->rememberForever( - $this->makeCacheKey(['*'], null, "-count"), - function () use ($columns) { - return parent::count($columns); - } - ); + $arguments = func_get_args(); + $cacheKey = $this->makeCacheKey(['*'], null, "-count"); + $method = 'count'; + + return $this->cachedValue($arguments, $cacheKey, $method); } public function cursor() @@ -46,13 +42,11 @@ public function cursor() return collect(parent::cursor()); } - return $this->cache($this->makeCacheTags()) - ->rememberForever( - $this->makeCacheKey(['*'], null, "-cursor"), - function () { - return collect(parent::cursor()); - } - ); + $arguments = func_get_args(); + $cacheKey = $this->makeCacheKey(['*'], null, "-cursor"); + $method = 'cursor'; + + return $this->cachedValue($arguments, $cacheKey, $method); } public function delete() @@ -72,13 +66,11 @@ public function find($id, $columns = ['*']) return parent::find($id, $columns); } - return $this->cache($this->makeCacheTags()) - ->rememberForever( - $this->makeCacheKey($columns, $id), - function () use ($id, $columns) { - return parent::find($id, $columns); - } - ); + $arguments = func_get_args(); + $cacheKey = $this->makeCacheKey($columns); + $method = 'find'; + + return $this->cachedValue($arguments, $cacheKey, $method); } public function first($columns = ['*']) @@ -87,13 +79,11 @@ public function first($columns = ['*']) return parent::first($columns); } - return $this->cache($this->makeCacheTags()) - ->rememberForever( - $this->makeCacheKey($columns, null, '-first'), - function () use ($columns) { - return parent::first($columns); - } - ); + $arguments = func_get_args(); + $cacheKey = $this->makeCacheKey($columns); + $method = 'first'; + + return $this->cachedValue($arguments, $cacheKey, $method); } public function get($columns = ['*']) @@ -102,13 +92,11 @@ public function get($columns = ['*']) return parent::get($columns); } - return $this->cache($this->makeCacheTags()) - ->rememberForever( - $this->makeCacheKey($columns), - function () use ($columns) { - return parent::get($columns); - } - ); + $arguments = func_get_args(); + $cacheKey = $this->makeCacheKey($columns); + $method = 'get'; + + return $this->cachedValue($arguments, $cacheKey, $method); } public function max($column) @@ -117,13 +105,11 @@ public function max($column) return parent::max($column); } - return $this->cache($this->makeCacheTags()) - ->rememberForever( - $this->makeCacheKey(['*'], null, "-max_{$column}"), - function () use ($column) { - return parent::max($column); - } - ); + $arguments = func_get_args(); + $cacheKey = $this->makeCacheKey(['*'], null, "-max_{$column}"); + $method = 'max'; + + return $this->cachedValue($arguments, $cacheKey, $method); } public function min($column) @@ -132,13 +118,11 @@ public function min($column) return parent::min($column); } - return $this->cache($this->makeCacheTags()) - ->rememberForever( - $this->makeCacheKey(['*'], null, "-min_{$column}"), - function () use ($column) { - return parent::min($column); - } - ); + $arguments = func_get_args(); + $cacheKey = $this->makeCacheKey(['*'], null, "-min_{$column}"); + $method = 'min'; + + return $this->cachedValue($arguments, $cacheKey, $method); } public function pluck($column, $key = null) @@ -148,12 +132,11 @@ public function pluck($column, $key = null) } $keyDifferentiator = "-pluck_{$column}" . ($key ? "_{$key}" : ""); + $arguments = func_get_args(); $cacheKey = $this->makeCacheKey([$column], null, $keyDifferentiator); + $method = 'pluck'; - return $this->cache($this->makeCacheTags()) - ->rememberForever($cacheKey, function () use ($column, $key) { - return parent::pluck($column, $key); - }); + return $this->cachedValue($arguments, $cacheKey, $method); } public function sum($column) @@ -162,13 +145,11 @@ public function sum($column) return parent::sum($column); } - return $this->cache($this->makeCacheTags()) - ->rememberForever( - $this->makeCacheKey(['*'], null, "-sum_{$column}"), - function () use ($column) { - return parent::sum($column); - } - ); + $arguments = func_get_args(); + $cacheKey = $this->makeCacheKey(['*'], null, "-sum_{$column}"); + $method = 'sum'; + + return $this->cachedValue($arguments, $cacheKey, $method); } public function value($column) @@ -177,11 +158,56 @@ public function value($column) return parent::value($column); } - return $this->cache($this->makeCacheTags()) + $arguments = func_get_args(); + $cacheKey = $this->makeCacheKey(['*'], null, "-value_{$column}"); + $method = 'value'; + + return $this->cachedValue($arguments, $cacheKey, $method); + } + + public function cachedValue(array $arguments, string $cacheKey, string $method) + { + $cacheTags = $this->makeCacheTags(); + $hashedCacheKey = sha1($cacheKey); + + $result = $this->retrieveCachedValue( + $arguments, + $cacheKey, + $cacheTags, + $hashedCacheKey, + $method + ); + + if ($result['key'] !== $cacheKey) { + cache()->tags($cacheTags)->forget($hashedCacheKey); + } + + $result = $this->retrieveCachedValue( + $arguments, + $cacheKey, + $cacheTags, + $hashedCacheKey, + $method + ); + + return $result['value']; + } + + protected function retrieveCachedValue( + array $arguments, + string $cacheKey, + array $cacheTags, + string $hashedCacheKey, + string $method + ) { + return $this->cache($cacheTags) ->rememberForever( - $this->makeCacheKey(['*'], null, "-value_{$column}"), - function () use ($column) { - return parent::value($column); + $hashedCacheKey, + function () use ($arguments, $cacheKey, $method) { + return [ + 'key' => $cacheKey, + 'value' => parent::{$method}(...$arguments), + ]; } ); } diff --git a/tests/Unit/CacheKeyTest.php b/tests/Unit/CacheKeyTest.php deleted file mode 100644 index 1613f4d..0000000 --- a/tests/Unit/CacheKeyTest.php +++ /dev/null @@ -1,23 +0,0 @@ -<?php namespace GeneaLabs\LaravelModelCaching\Tests\Unit; - -use GeneaLabs\LaravelModelCaching\CachedBuilder; -use GeneaLabs\LaravelModelCaching\Tests\Fixtures\Author; -use GeneaLabs\LaravelModelCaching\Tests\UnitTestCase; -use ReflectionMethod; - -class CacheKeyTest extends UnitTestCase -{ - public function testKeyIsSHA1() - { - $makeCacheKey = new ReflectionMethod( - CachedBuilder::class, - 'makeCacheKey' - ); - $makeCacheKey->setAccessible(true); - - $builder = (new Author)->startsWithA(); - $key = $makeCacheKey->invoke($builder); - - $this->assertTrue(strlen($key) === 40 && ctype_xdigit($key)); - } -} diff --git a/tests/Unit/CachedBuilderTest.php b/tests/Unit/CachedBuilderTest.php index bd94045..e37fd9b 100644 --- a/tests/Unit/CachedBuilderTest.php +++ b/tests/Unit/CachedBuilderTest.php @@ -134,7 +134,7 @@ public function testHasManyRelationshipIsCached() 'genealabslaravelmodelcachingtestsfixturesauthor', 'genealabslaravelmodelcachingtestsfixturesbook' ]) - ->get(sha1("genealabslaravelmodelcachingtestsfixturesauthor-books"))); + ->get(sha1("genealabslaravelmodelcachingtestsfixturesauthor-books"))['value']); $this->assertNotNull($results); $this->assertEmpty($authors->diffKeys($results)); @@ -151,7 +151,7 @@ public function testBelongsToRelationshipIsCached() 'genealabslaravelmodelcachingtestsfixturesbook', 'genealabslaravelmodelcachingtestsfixturesauthor' ]) - ->get(sha1("genealabslaravelmodelcachingtestsfixturesbook-author"))); + ->get(sha1("genealabslaravelmodelcachingtestsfixturesbook-author"))['value']); $this->assertNotNull($results); $this->assertEmpty($books->diffKeys($results)); @@ -168,7 +168,7 @@ public function testBelongsToManyRelationshipIsCached() 'genealabslaravelmodelcachingtestsfixturesbook', 'genealabslaravelmodelcachingtestsfixturesstore' ]) - ->get(sha1("genealabslaravelmodelcachingtestsfixturesbook-stores"))); + ->get(sha1("genealabslaravelmodelcachingtestsfixturesbook-stores"))['value']); $this->assertNotNull($results); $this->assertEmpty($books->diffKeys($results)); @@ -186,7 +186,7 @@ public function testHasOneRelationshipIsCached() 'genealabslaravelmodelcachingtestsfixturesauthor', 'genealabslaravelmodelcachingtestsfixturesprofile' ]) - ->get(sha1("genealabslaravelmodelcachingtestsfixturesauthor-profile"))); + ->get(sha1("genealabslaravelmodelcachingtestsfixturesauthor-profile"))['value']); $this->assertNotNull($results); $this->assertEmpty($authors->diffKeys($results)); @@ -206,8 +206,9 @@ public function testAvgModelResultsCreatesCache() 'genealabslaravelmodelcachingtestsfixturesprofile', ]; - $cachedResult = cache()->tags($tags) - ->get($key); + $cachedResult = cache() + ->tags($tags) + ->get($key)['value']; $liveResult = (new UncachedAuthor)->with('books', 'profile') ->avg('id'); @@ -252,7 +253,7 @@ public function testChunkModelResultsCreatesCache() for ($index = 0; $index < $cachedChunks['authors']->count(); $index++) { $key = $cachedChunks['keys'][$index]; $cachedResults = cache()->tags($tags) - ->get($key); + ->get($key)['value']; // $this->assertTrue($cachedChunks['authors'][$index]->diffKeys($cachedResults)->isEmpty()); // $this->assertTrue($uncachedChunks[$index]->diffKeys($cachedResults)->isEmpty()); @@ -275,7 +276,7 @@ public function testCountModelResultsCreatesCache() ]; $cachedResults = cache()->tags($tags) - ->get($key); + ->get($key)['value']; $liveResults = (new UncachedAuthor)->with('books', 'profile') ->count(); @@ -297,14 +298,14 @@ public function testCursorModelResultsCreatesCache() $cachedResults = cache() ->tags($tags) - ->get($key); + ->get($key)['value']; $liveResults = collect( (new UncachedAuthor) ->with('books', 'profile') ->cursor() ); - $this->assertEmpty($authors->diffKeys($cachedResults)); + $this->assertEquals($authors, $cachedResults); $this->assertEmpty($liveResults->diffKeys($cachedResults)); } @@ -324,6 +325,26 @@ public function testFindModelResultsCreatesCache() $this->assertEmpty($liveResults->diffKeys($cachedResults)); } + public function testFirstModelResultsCreatesCache() + { + $author = (new Author) + ->first(); + $key = sha1('genealabslaravelmodelcachingtestsfixturesauthor'); + $tags = [ + 'genealabslaravelmodelcachingtestsfixturesauthor', + ]; + + $cachedResult = cache()->tags($tags) + ->get($key)['value']; + + $liveResult = (new UncachedAuthor) + ->with('books', 'profile') + ->first(); + + $this->assertEquals($cachedResult->id, $author->id); + $this->assertEquals($liveResult->id, $author->id); + } + public function testGetModelResultsCreatesCache() { $authors = (new Author)->with('books', 'profile') @@ -336,7 +357,7 @@ public function testGetModelResultsCreatesCache() ]; $cachedResults = cache()->tags($tags) - ->get($key); + ->get($key)['value']; $liveResults = (new UncachedAuthor)->with('books', 'profile') ->get(); @@ -356,7 +377,7 @@ public function testMaxModelResultsCreatesCache() ]; $cachedResult = cache()->tags($tags) - ->get($key); + ->get($key)['value']; $liveResult = (new UncachedAuthor)->with('books', 'profile') ->max('id'); @@ -376,7 +397,7 @@ public function testMinModelResultsCreatesCache() ]; $cachedResult = cache()->tags($tags) - ->get($key); + ->get($key)['value']; $liveResult = (new UncachedAuthor)->with('books', 'profile') ->min('id'); @@ -396,7 +417,7 @@ public function testPluckModelResultsCreatesCache() ]; $cachedResults = cache()->tags($tags) - ->get($key); + ->get($key)['value']; $liveResults = (new UncachedAuthor)->with('books', 'profile') ->pluck('name', 'id'); @@ -416,7 +437,7 @@ public function testSumModelResultsCreatesCache() ]; $cachedResult = cache()->tags($tags) - ->get($key); + ->get($key)['value']; $liveResult = (new UncachedAuthor)->with('books', 'profile') ->sum('id'); @@ -436,7 +457,7 @@ public function testValueModelResultsCreatesCache() ]; $cachedResult = cache()->tags($tags) - ->get($key); + ->get($key)['value']; $liveResult = (new UncachedAuthor)->with('books', 'profile') ->value('name'); @@ -457,7 +478,7 @@ public function testNestedRelationshipEagerLoading() ]; $cachedResults = collect([cache()->tags($tags) - ->get($key)]); + ->get($key)['value']]); $liveResults = collect([(new UncachedAuthor)->with('books.publisher') ->first()]); @@ -473,7 +494,9 @@ public function testLazyLoadedRelationshipResolvesThroughCachedBuilder() 'genealabslaravelmodelcachingtestsfixturesbook', ]; - $cachedResults = cache()->tags($tags)->get($key); + $cachedResults = cache() + ->tags($tags) + ->get($key)['value']; $liveResults = (new UncachedAuthor)->first()->books; $this->assertEmpty($books->diffKeys($cachedResults)); @@ -488,7 +511,9 @@ public function testLazyLoadingOnResourceIsCached() 'genealabslaravelmodelcachingtestsfixturesbook', ]; - $cachedResults = cache()->tags($tags)->get($key); + $cachedResults = cache() + ->tags($tags) + ->get($key)['value']; $liveResults = (new UncachedAuthor)->first()->books; $this->assertEmpty($books->diffKeys($cachedResults)); @@ -504,7 +529,9 @@ public function testOrderByClauseParsing() 'genealabslaravelmodelcachingtestsfixturesauthor', ]; - $cachedResults = cache()->tags($tags)->get($key); + $cachedResults = cache() + ->tags($tags) + ->get($key)['value']; $liveResults = (new UncachedAuthor)->orderBy('name')->get(); $this->assertEmpty($authors->diffKeys($cachedResults)); @@ -513,7 +540,8 @@ public function testOrderByClauseParsing() public function testNestedRelationshipWhereClauseParsing() { - $authors = (new Author)->with('books.publisher') + $authors = (new Author) + ->with('books.publisher') ->get(); $key = sha1('genealabslaravelmodelcachingtestsfixturesauthor-books-books.publisher'); @@ -523,8 +551,9 @@ public function testNestedRelationshipWhereClauseParsing() 'genealabslaravelmodelcachingtestsfixturespublisher', ]; - $cachedResults = cache()->tags($tags) - ->get($key); + $cachedResults = cache() + ->tags($tags) + ->get($key)['value']; $liveResults = (new UncachedAuthor)->with('books.publisher') ->get(); @@ -541,7 +570,9 @@ public function testExistsRelationshipWhereClauseParsing() $key = sha1('genealabslaravelmodelcachingtestsfixturesauthor_exists_and_authors.id_=_books.author_id'); $tags = ['genealabslaravelmodelcachingtestsfixturesauthor']; - $cachedResults = cache()->tags($tags)->get($key); + $cachedResults = cache() + ->tags($tags) + ->get($key)['value']; $liveResults = (new UncachedAuthor)->whereHas('books') ->get(); @@ -560,7 +591,7 @@ public function testDoesntHaveWhereClauseParsing() $cachedResults = cache() ->tags($tags) - ->get($key); + ->get($key)['value']; $liveResults = (new UncachedAuthor) ->doesntHave('books') ->get(); @@ -583,7 +614,7 @@ public function testColumnsRelationshipWhereClauseParsing() $cachedResults = cache() ->tags($tags) - ->get($key); + ->get($key)['value']; $liveResults = (new UncachedAuthor) ->where('name', '=', $author->name) ->get(); @@ -601,7 +632,7 @@ public function testRawWhereClauseParsing() $key = sha1('genealabslaravelmodelcachingtestsfixturesauthor_and_name-first'); $tags = ['genealabslaravelmodelcachingtestsfixturesauthor']; - $cachedResults = collect([cache()->tags($tags)->get($key)]); + $cachedResults = collect([cache()->tags($tags)->get($key)['value']]); $liveResults = collect([(new UncachedAuthor) ->whereRaw('name <> \'\'')->first()]); @@ -621,7 +652,9 @@ public function testScopeClauseParsing() $key = sha1('genealabslaravelmodelcachingtestsfixturesauthor-name_like_A%'); $tags = ['genealabslaravelmodelcachingtestsfixturesauthor']; - $cachedResults = cache()->tags($tags)->get($key); + $cachedResults = cache() + ->tags($tags) + ->get($key)['value']; $liveResults = (new UncachedAuthor) ->startsWithA() ->get(); @@ -642,7 +675,9 @@ public function testRelationshipQueriesAreCached() 'genealabslaravelmodelcachingtestsfixturesbook' ]; - $cachedResults = cache()->tags($tags)->get($key); + $cachedResults = cache() + ->tags($tags) + ->get($key)['value']; $liveResults = (new UncachedAuthor) ->first() ->books() @@ -663,7 +698,7 @@ public function testRawOrderByWithoutColumnReference() $cachedResults = cache() ->tags($tags) - ->get($key); + ->get($key)['value']; $liveResults = (new UncachedAuthor) ->orderByRaw('DATE()') @@ -688,7 +723,7 @@ public function testDelete() $liveResult->delete(); $cachedResult = cache() ->tags($tags) - ->get($key); + ->get($key)['value']; $deletedAuthor = (new Author)->find($authorId); $this->assertEquals($liveResultId, $authorId); @@ -714,7 +749,7 @@ private function processWhereClauseTestWithOperator(string $operator) $cachedResults = cache() ->tags($tags) - ->get($key); + ->get($key)['value']; $liveResults = (new UncachedAuthor) ->where('name', $operator, $author->name) ->get(); @@ -744,8 +779,9 @@ public function testWhereBetweenIdsResults() 'genealabslaravelmodelcachingtestsfixturesbook', ]; - $cachedResults = cache()->tags($tags) - ->get($key); + $cachedResults = cache() + ->tags($tags) + ->get($key)['value']; $liveResults = (new UncachedAuthor) ->whereBetween('price', [5, 10]) ->get(); @@ -764,8 +800,9 @@ public function testWhereBetweenDatesResults() 'genealabslaravelmodelcachingtestsfixturesbook', ]; - $cachedResults = cache()->tags($tags) - ->get($key); + $cachedResults = cache() + ->tags($tags) + ->get($key)['value']; $liveResults = (new UncachedAuthor) ->whereBetween('price', [5, 10]) ->get(); @@ -784,8 +821,9 @@ public function testWhereDatesResults() 'genealabslaravelmodelcachingtestsfixturesbook', ]; - $cachedResults = cache()->tags($tags) - ->get($key); + $cachedResults = cache() + ->tags($tags) + ->get($key)['value']; $liveResults = (new UncachedAuthor) ->whereBetween('price', [5, 10]) ->get(); @@ -804,8 +842,9 @@ public function testWhereNotInResults() 'genealabslaravelmodelcachingtestsfixturesbook', ]; - $cachedResults = cache()->tags($tags) - ->get($key); + $cachedResults = cache() + ->tags($tags) + ->get($key)['value']; $liveResults = (new UncachedAuthor) ->whereNotIn('id', [1, 2]) ->get(); @@ -813,4 +852,38 @@ public function testWhereNotInResults() $this->assertTrue($cachedResults->diffKeys($books)->isEmpty()); $this->assertTrue($liveResults->diffKeys($books)->isEmpty()); } + + public function testHashCollision() + { + $key1 = sha1('genealabslaravelmodelcachingtestsfixturesbook-id_notin_1_2'); + $tags1 = [ + 'genealabslaravelmodelcachingtestsfixturesbook', + ]; + + $authors = (new Author) + ->disableCache() + ->get(); + $key2 = sha1('genealabslaravelmodelcachingtestsfixturesauthor'); + + cache()->tags($tags1) + ->rememberForever( + $key1, + function () use ($key2, $authors) { + return [ + 'key' => $key2, + 'value' => $authors, + ]; + } + ); + + $books = (new Book) + ->whereNotIn('id', [1, 2]) + ->get(); + + $cachedResults = cache()->tags($tags1) + ->get($key1)['value']; + + $this->assertTrue($cachedResults->diffKeys($books)->isEmpty()); + $this->assertTrue($cachedResults->diffKeys($authors)->isNotEmpty()); + } } diff --git a/tests/Unit/CachedModelTest.php b/tests/Unit/CachedModelTest.php index 6a563ab..6f5b499 100644 --- a/tests/Unit/CachedModelTest.php +++ b/tests/Unit/CachedModelTest.php @@ -54,7 +54,7 @@ public function testAllModelResultsCreatesCache() $cachedResults = cache() ->tags($tags) - ->get($key); + ->get($key)['value']; $liveResults = (new UncachedAuthor) ->all(); @@ -73,7 +73,7 @@ public function testScopeDisablesCaching() $cachedResults = cache() ->tags($tags) - ->get($key); + ->get($key)['value']; $this->assertNull($cachedResults); $this->assertNotEquals($authors, $cachedResults); diff --git a/tests/Unit/Console/Commands/FlushTest.php b/tests/Unit/Console/Commands/FlushTest.php index 9b8bfae..cfc9b42 100644 --- a/tests/Unit/Console/Commands/FlushTest.php +++ b/tests/Unit/Console/Commands/FlushTest.php @@ -48,11 +48,11 @@ public function testGivenModelIsFlushed() $cachedResults = cache() ->tags($tags) - ->get($key); + ->get($key)['value']; $result = $this->artisan('modelCache:flush', ['--model' => Author::class]); $flushedResults = cache() ->tags($tags) - ->get($key); + ->get($key)['value']; $this->assertEquals($authors, $cachedResults); $this->assertEmpty($flushedResults); @@ -70,14 +70,14 @@ public function testGivenModelWithRelationshipIsFlushed() $cachedResults = cache() ->tags($tags) - ->get($key); + ->get($key)['value']; $result = $this->artisan( 'modelCache:flush', ['--model' => Author::class] ); $flushedResults = cache() ->tags($tags) - ->get($key); + ->get($key)['value']; $this->assertEquals($authors, $cachedResults); $this->assertEmpty($flushedResults); diff --git a/tests/Unit/Traits/CachableTest.php b/tests/Unit/Traits/CachableTest.php index f6e8d8c..b56f233 100644 --- a/tests/Unit/Traits/CachableTest.php +++ b/tests/Unit/Traits/CachableTest.php @@ -57,11 +57,11 @@ public function testSpecifyingAlternateCacheDriver() ->all(); $defaultcacheResults = cache() ->tags($tags) - ->get($key); + ->get($key)['value']; $customCacheResults = cache() ->store('customCache') ->tags($tags) - ->get($key); + ->get($key)['value']; $liveResults = (new UncachedAuthor) ->all();