From 2e5872c99bc02dc48e931486fcfefc2041b29bb2 Mon Sep 17 00:00:00 2001 From: Hector Mendoza Jacobo Date: Tue, 13 Aug 2024 18:59:00 +0000 Subject: [PATCH 01/14] Add a file system cache class --- src/Cache/FileSystemCacheItemPool.php | 206 +++++++++++++++++++ src/Cache/FileSystemCacheItemPoolOptions.php | 23 +++ tests/Cache/FileSystemCacheItemPoolTest.php | 159 ++++++++++++++ 3 files changed, 388 insertions(+) create mode 100644 src/Cache/FileSystemCacheItemPool.php create mode 100644 src/Cache/FileSystemCacheItemPoolOptions.php create mode 100644 tests/Cache/FileSystemCacheItemPoolTest.php diff --git a/src/Cache/FileSystemCacheItemPool.php b/src/Cache/FileSystemCacheItemPool.php new file mode 100644 index 000000000..80f77afe6 --- /dev/null +++ b/src/Cache/FileSystemCacheItemPool.php @@ -0,0 +1,206 @@ +cachePath = $options->cachePath; + + if (is_dir($this->cachePath)) { + return; + } + + if (!mkdir($this->cachePath)) { + throw new ErrorException("Cache folder couldn't be created."); + } + } + + /** + * {@inheritdoc} + */ + public function getItem(string $key): CacheItemInterface + { + if (!$this->validKey($key)) { + throw new InvalidArgumentException("The key '$key' is not valid. The key should follow the pattern |^[a-zA-Z0-9_\.! ]+$|"); + } + + $itemPath = $this->cacheFilePath($key); + + if (!file_exists($itemPath)) { + return new TypedItem($key); + } + + $serializedItem = file_get_contents($itemPath); + return unserialize($serializedItem); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []): iterable + { + $result = []; + + foreach ($keys as $key) { + $result[$key] = $this->getItem($key); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function save(CacheItemInterface $item): bool + { + if (!$this->validKey($item->getKey())) { + throw new InvalidArgumentException("The key " . $item->getKey() . " is not valid. The key should follow the pattern |^[a-zA-Z0-9_\.! ]+$|"); + } + + $itemPath = $this->cacheFilePath($item->getKey()); + $serializedItem = serialize($item); + + $result = file_put_contents($itemPath, $serializedItem); + + if (!$result) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function hasItem(string $key): bool + { + return $this->getItem($key)->isHit(); + } + + /** + * {@inheritdoc} + */ + public function clear(): bool + { + if (!is_dir($this->cachePath)) { + return false; + } + + $files = scandir($this->cachePath); + if (!$files) { + return false; + } + + foreach($files as $fileName) { + if ($fileName === '.' || $fileName === '..') { + continue; + } + + if (!unlink($this->cachePath . "/" . $fileName)) { + return false; + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function deleteItem(string $key): bool + { + if (!$this->validKey($key)) { + throw new Exception("The key '$key' is not valid. The key should follow the pattern |^[a-zA-Z0-9_\.! ]+$|"); + } + + $itemPath = $this->cacheFilePath($key); + + if (!file_exists($itemPath)) { + return false; + } + + return unlink($itemPath); + } + + /** + * {@inheritdoc} + */ + public function deleteItems(array $keys): bool + { + $result = true; + + foreach($keys as $key) { + if (!$this->deleteItem($key)) { + $result = false; + } + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function saveDeferred(CacheItemInterface $item): bool + { + array_push($this->buffer, $item); + + return true; + } + + /** + * {@inheritdoc} + */ + public function commit(): bool + { + $result = true; + + foreach ($this->buffer as $item) { + if (!$this->save($item)) { + $result = false; + } + } + + return $result; + } + + private function cacheFilePath(string $key): string + { + return $this->cachePath . '/' . $key; + } + + private function validKey(string $key): bool + { + return preg_match('|^[a-zA-Z0-9_\.! ]+$|', $key); + } +} diff --git a/src/Cache/FileSystemCacheItemPoolOptions.php b/src/Cache/FileSystemCacheItemPoolOptions.php new file mode 100644 index 000000000..0231add69 --- /dev/null +++ b/src/Cache/FileSystemCacheItemPoolOptions.php @@ -0,0 +1,23 @@ +pool = new FileSystemCacheItemPool(); + } + + public function tearDown(): void + { + $files = scandir($this->defaultCacheDirectory); + + foreach($files as $fileName) { + if ($fileName === '.' || $fileName === '..') { + continue; + } + + unlink($this->defaultCacheDirectory . "/" . $fileName); + } + + rmdir($this->defaultCacheDirectory); + } + + public function testInstanceCreatesCacheFolder() + { + $this->assertTrue(file_exists($this->defaultCacheDirectory)); + $this->assertTrue(is_dir($this->defaultCacheDirectory)); + } + + public function testSaveAndGetItem() + { + $item = $this->getNewItem(); + $this->pool->save($item); + $retrievedItem = $this->pool->getItem($item->getKey()); + + $this->assertTrue($retrievedItem->isHit()); + $this->assertEquals($retrievedItem->get(), $item->get()); + } + + public function testHasItem() + { + $item = $this->getNewItem(); + $this->assertFalse($this->pool->hasItem($item->getKey())); + $this->pool->save($item); + $this->assertTrue($this->pool->hasItem($item->getKey())); + } + + public function testDeleteItem() + { + $item = $this->getNewItem(); + $this->pool->save($item); + + $this->assertTrue($this->pool->deleteItem($item->getKey())); + $this->assertFalse($this->pool->hasItem($item->getKey())); + } + + public function testDeleteItems() + { + $items = [ + $this->getNewItem(), + $this->getNewItem("NewItem2"), + $this->getNewItem("NewItem3") + ]; + + foreach ($items as $item) { + $this->pool->save($item); + } + + $result = $this->pool->deleteItems(array_map(function(CacheItemInterface $item) { + return $item->getKey(); + }, $items)); + $this->assertTrue($result); + + $result = $this->pool->deleteItems(array_map(function(CacheItemInterface $item) { + return $item->getKey(); + }, $items)); + $this->assertFalse($result); + } + + public function testGetItems() + { + $items = [ + $this->getNewItem(), + $this->getNewItem("NewItem2"), + $this->getNewItem("NewItem3") + ]; + + foreach ($items as $item) { + $this->pool->save($item); + } + + $keys = array_map(function(CacheItemInterface $item) { + return $item->getKey(); + }, $items); + array_push($keys, 'NonExistant'); + + $retrievedItems = $this->pool->getItems($keys); + + foreach ($items as $item) { + $this->assertTrue($retrievedItems[$item->getKey()]->isHit()); + } + + $this->assertFalse($retrievedItems['NonExistant']->isHit()); + } + + public function testClear() + { + $item = $this->getNewItem(); + $this->pool->save($item); + $this->assertLessThan(scandir($this->defaultCacheDirectory), 2); + $this->pool->clear(); + // Clear removes all the files, but scandir returns `.` and `..` as files + $this->assertEquals(count(scandir($this->defaultCacheDirectory)), 2); + } + + public function testSaveDeferredAndCommit() + { + $item = $this->getNewItem(); + $this->pool->saveDeferred($item); + $this->assertFalse($this->pool->getItem($item->getKey())->isHit()); + + $this->pool->commit(); + $this->assertTrue($this->pool->getItem($item->getKey())->isHit()); + } + + private function getNewItem(null|string $key = null): TypedItem + { + $item = new TypedItem($key ?? 'NewItem'); + $item->set('NewValue'); + + return $item; + } +} \ No newline at end of file From d16fc10a584b70f4eb2eba2b2de1a3164488b324 Mon Sep 17 00:00:00 2001 From: Hector Mendoza Jacobo Date: Tue, 13 Aug 2024 19:59:32 +0000 Subject: [PATCH 02/14] Fix styles --- src/Cache/FileSystemCacheItemPool.php | 36 ++++++++++++++++---- src/Cache/FileSystemCacheItemPoolOptions.php | 2 +- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/Cache/FileSystemCacheItemPool.php b/src/Cache/FileSystemCacheItemPool.php index 80f77afe6..9ca83f8c2 100644 --- a/src/Cache/FileSystemCacheItemPool.php +++ b/src/Cache/FileSystemCacheItemPool.php @@ -24,16 +24,27 @@ class FileSystemCacheItemPool implements CacheItemPoolInterface { + /** + * @var string + */ private string $cachePath = 'cache/'; + + /** + * @var array + */ private array $buffer = []; /** * Creates a FileSystemCacheItemPool cache that stores values in local storage - * - * @var FileSystemCacheItemPoolOptions $options + * + * @var null|FileSystemCacheItemPoolOptions $options */ - public function __construct(FileSystemCacheItemPoolOptions $options = new FileSystemCacheItemPoolOptions()) + public function __construct(null|FileSystemCacheItemPoolOptions $options = null) { + if (!$options) { + $options = new FileSystemCacheItemPoolOptions(); + } + $this->cachePath = $options->cachePath; if (is_dir($this->cachePath)) { @@ -61,11 +72,22 @@ public function getItem(string $key): CacheItemInterface } $serializedItem = file_get_contents($itemPath); + + if ($serializedItem === false) { + return new TypedItem($key); + } + return unserialize($serializedItem); } /** * {@inheritdoc} + * + * @return iterable An iterable object containing all the + * A traversable collection of Cache Items keyed by the cache keys of + * each item. A Cache item will be returned for each key, even if that + * key is not found. However, if no keys are specified then an empty + * traversable MUST be returned instead. */ public function getItems(array $keys = []): iterable { @@ -84,7 +106,7 @@ public function getItems(array $keys = []): iterable public function save(CacheItemInterface $item): bool { if (!$this->validKey($item->getKey())) { - throw new InvalidArgumentException("The key " . $item->getKey() . " is not valid. The key should follow the pattern |^[a-zA-Z0-9_\.! ]+$|"); + throw new InvalidArgumentException('The key ' . $item->getKey() . " is not valid. The key should follow the pattern |^[a-zA-Z0-9_\.! ]+$|"); } $itemPath = $this->cacheFilePath($item->getKey()); @@ -104,7 +126,7 @@ public function save(CacheItemInterface $item): bool */ public function hasItem(string $key): bool { - return $this->getItem($key)->isHit(); + return $this->getItem($key)->isHit(); } /** @@ -126,7 +148,7 @@ public function clear(): bool continue; } - if (!unlink($this->cachePath . "/" . $fileName)) { + if (!unlink($this->cachePath . '/' . $fileName)) { return false; } } @@ -201,6 +223,6 @@ private function cacheFilePath(string $key): string private function validKey(string $key): bool { - return preg_match('|^[a-zA-Z0-9_\.! ]+$|', $key); + return (bool)preg_match('|^[a-zA-Z0-9_\.! ]+$|', $key); } } diff --git a/src/Cache/FileSystemCacheItemPoolOptions.php b/src/Cache/FileSystemCacheItemPoolOptions.php index 0231add69..91c5d47bc 100644 --- a/src/Cache/FileSystemCacheItemPoolOptions.php +++ b/src/Cache/FileSystemCacheItemPoolOptions.php @@ -20,4 +20,4 @@ class FileSystemCacheItemPoolOptions { public string $cachePath = 'cache/'; -} \ No newline at end of file +} From 5785ef346917968750d3d27a5191c3ced231b7a9 Mon Sep 17 00:00:00 2001 From: Hector Mendoza Jacobo Date: Tue, 13 Aug 2024 20:06:30 +0000 Subject: [PATCH 03/14] Fix doc typing for static analysis --- src/Cache/FileSystemCacheItemPool.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Cache/FileSystemCacheItemPool.php b/src/Cache/FileSystemCacheItemPool.php index 9ca83f8c2..1854c6704 100644 --- a/src/Cache/FileSystemCacheItemPool.php +++ b/src/Cache/FileSystemCacheItemPool.php @@ -37,7 +37,7 @@ class FileSystemCacheItemPool implements CacheItemPoolInterface /** * Creates a FileSystemCacheItemPool cache that stores values in local storage * - * @var null|FileSystemCacheItemPoolOptions $options + * @param null|FileSystemCacheItemPoolOptions $options */ public function __construct(null|FileSystemCacheItemPoolOptions $options = null) { @@ -82,8 +82,8 @@ public function getItem(string $key): CacheItemInterface /** * {@inheritdoc} - * - * @return iterable An iterable object containing all the + * + * @return iterable An iterable object containing all the * A traversable collection of Cache Items keyed by the cache keys of * each item. A Cache item will be returned for each key, even if that * key is not found. However, if no keys are specified then an empty From fd90aaa2b7310fc5c0d88a46f32625173d11074d Mon Sep 17 00:00:00 2001 From: Hector Mendoza Jacobo Date: Tue, 13 Aug 2024 20:29:26 +0000 Subject: [PATCH 04/14] Fix style guidelines --- tests/Cache/FileSystemCacheItemPoolTest.php | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/Cache/FileSystemCacheItemPoolTest.php b/tests/Cache/FileSystemCacheItemPoolTest.php index 1db5a99ee..e31ac9ec3 100644 --- a/tests/Cache/FileSystemCacheItemPoolTest.php +++ b/tests/Cache/FileSystemCacheItemPoolTest.php @@ -41,7 +41,7 @@ public function tearDown(): void continue; } - unlink($this->defaultCacheDirectory . "/" . $fileName); + unlink($this->defaultCacheDirectory . '/' . $fileName); } rmdir($this->defaultCacheDirectory); @@ -84,22 +84,22 @@ public function testDeleteItems() { $items = [ $this->getNewItem(), - $this->getNewItem("NewItem2"), - $this->getNewItem("NewItem3") + $this->getNewItem('NewItem2'), + $this->getNewItem('NewItem3') ]; foreach ($items as $item) { $this->pool->save($item); } - $result = $this->pool->deleteItems(array_map(function(CacheItemInterface $item) { + $itemKeys = array_map(function (CacheItemInterface $item) { return $item->getKey(); - }, $items)); + }, $items); + + $result = $this->pool->deleteItems($itemKeys); $this->assertTrue($result); - $result = $this->pool->deleteItems(array_map(function(CacheItemInterface $item) { - return $item->getKey(); - }, $items)); + $result = $this->pool->deleteItems($itemKeys); $this->assertFalse($result); } @@ -107,15 +107,15 @@ public function testGetItems() { $items = [ $this->getNewItem(), - $this->getNewItem("NewItem2"), - $this->getNewItem("NewItem3") + $this->getNewItem('NewItem2'), + $this->getNewItem('NewItem3') ]; foreach ($items as $item) { $this->pool->save($item); } - $keys = array_map(function(CacheItemInterface $item) { + $keys = array_map(function (CacheItemInterface $item) { return $item->getKey(); }, $items); array_push($keys, 'NonExistant'); @@ -156,4 +156,4 @@ private function getNewItem(null|string $key = null): TypedItem return $item; } -} \ No newline at end of file +} From a0557ff728d0f76b1f2c27c9fd89e64cf111a058 Mon Sep 17 00:00:00 2001 From: Hector Mendoza Jacobo Date: Fri, 16 Aug 2024 21:36:07 +0000 Subject: [PATCH 05/14] Removed the FileSystemCacheOptions class and made the path parameter mandatory for the Cache Pool --- src/Cache/FileSystemCacheItemPool.php | 12 ++++------ src/Cache/FileSystemCacheItemPoolOptions.php | 23 -------------------- tests/Cache/FileSystemCacheItemPoolTest.php | 2 +- 3 files changed, 5 insertions(+), 32 deletions(-) delete mode 100644 src/Cache/FileSystemCacheItemPoolOptions.php diff --git a/src/Cache/FileSystemCacheItemPool.php b/src/Cache/FileSystemCacheItemPool.php index 1854c6704..be771b965 100644 --- a/src/Cache/FileSystemCacheItemPool.php +++ b/src/Cache/FileSystemCacheItemPool.php @@ -28,7 +28,7 @@ class FileSystemCacheItemPool implements CacheItemPoolInterface * @var string */ private string $cachePath = 'cache/'; - + /** * @var array */ @@ -37,15 +37,11 @@ class FileSystemCacheItemPool implements CacheItemPoolInterface /** * Creates a FileSystemCacheItemPool cache that stores values in local storage * - * @param null|FileSystemCacheItemPoolOptions $options + * @param string $options The string representation of the path where the cache will store the serialized objects. */ - public function __construct(null|FileSystemCacheItemPoolOptions $options = null) + public function __construct(string $path) { - if (!$options) { - $options = new FileSystemCacheItemPoolOptions(); - } - - $this->cachePath = $options->cachePath; + $this->cachePath = $path; if (is_dir($this->cachePath)) { return; diff --git a/src/Cache/FileSystemCacheItemPoolOptions.php b/src/Cache/FileSystemCacheItemPoolOptions.php deleted file mode 100644 index 91c5d47bc..000000000 --- a/src/Cache/FileSystemCacheItemPoolOptions.php +++ /dev/null @@ -1,23 +0,0 @@ -pool = new FileSystemCacheItemPool(); + $this->pool = new FileSystemCacheItemPool($this->defaultCacheDirectory); } public function tearDown(): void From c8c16155c74c76cf24384948fb49cd6afc11c142 Mon Sep 17 00:00:00 2001 From: Hector Mendoza Jacobo Date: Fri, 16 Aug 2024 21:39:28 +0000 Subject: [PATCH 06/14] Fix type hinting --- src/Cache/FileSystemCacheItemPool.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cache/FileSystemCacheItemPool.php b/src/Cache/FileSystemCacheItemPool.php index be771b965..bfbd582bd 100644 --- a/src/Cache/FileSystemCacheItemPool.php +++ b/src/Cache/FileSystemCacheItemPool.php @@ -37,7 +37,7 @@ class FileSystemCacheItemPool implements CacheItemPoolInterface /** * Creates a FileSystemCacheItemPool cache that stores values in local storage * - * @param string $options The string representation of the path where the cache will store the serialized objects. + * @param string $path The string representation of the path where the cache will store the serialized objects. */ public function __construct(string $path) { From c3a8a09ab19fbff0537d6e798b340aceb8dbe404 Mon Sep 17 00:00:00 2001 From: Hector Mendoza Jacobo Date: Fri, 16 Aug 2024 22:48:06 +0000 Subject: [PATCH 07/14] Update the documentation and add exception testing --- README.md | 17 +++++ src/Cache/FileSystemCacheItemPool.php | 7 +- tests/Cache/FileSystemCacheItemPoolTest.php | 73 +++++++++++++++++++++ 3 files changed, 93 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 63bcfeaa2..9328160ee 100644 --- a/README.md +++ b/README.md @@ -300,6 +300,23 @@ $memoryCache = new MemoryCacheItemPool; $middleware = ApplicationDefaultCredentials::getCredentials($scope, cache: $memoryCache); ``` +### FileSystemCacheItemPool Cache +The `FileSystemCacheItemPool` class is a `PSR-6` compliant cache that stores its serialized objects on disk eliminating for caching data between processes making it possible to use data between different requests. + +```php +use Google\Auth\Cache\FileSystemCacheItemPool; +use Google\Auth\ApplicationDefaultCredentials; + +// Create a Cache pool instance +$cache = new FileSystemCacheItemPool(__DIR__ . '/cache'); + +// Pass your Cache to the Auth Library +$credentials = ApplicationDefaultCredentials::getCredentials($scope, cache: $cache); + +// This token will be cached and be able to be used for the next request +$token = $credentials->fetchAuthToken(); +``` + ### Integrating with a third party cache You can use a third party that follows the `PSR-6` interface of your choice. diff --git a/src/Cache/FileSystemCacheItemPool.php b/src/Cache/FileSystemCacheItemPool.php index bfbd582bd..5face00de 100644 --- a/src/Cache/FileSystemCacheItemPool.php +++ b/src/Cache/FileSystemCacheItemPool.php @@ -18,7 +18,6 @@ namespace Google\Auth\Cache; use ErrorException; -use Exception; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; @@ -102,7 +101,7 @@ public function getItems(array $keys = []): iterable public function save(CacheItemInterface $item): bool { if (!$this->validKey($item->getKey())) { - throw new InvalidArgumentException('The key ' . $item->getKey() . " is not valid. The key should follow the pattern |^[a-zA-Z0-9_\.! ]+$|"); + return false; } $itemPath = $this->cacheFilePath($item->getKey()); @@ -158,7 +157,7 @@ public function clear(): bool public function deleteItem(string $key): bool { if (!$this->validKey($key)) { - throw new Exception("The key '$key' is not valid. The key should follow the pattern |^[a-zA-Z0-9_\.! ]+$|"); + throw new InvalidArgumentException("The key '$key' is not valid. The key should follow the pattern |^[a-zA-Z0-9_\.! ]+$|"); } $itemPath = $this->cacheFilePath($key); @@ -219,6 +218,6 @@ private function cacheFilePath(string $key): string private function validKey(string $key): bool { - return (bool)preg_match('|^[a-zA-Z0-9_\.! ]+$|', $key); + return (bool)preg_match('|^[a-zA-Z0-9_\.]+$|', $key); } } diff --git a/tests/Cache/FileSystemCacheItemPoolTest.php b/tests/Cache/FileSystemCacheItemPoolTest.php index a669a9aa2..c49aaa2ff 100644 --- a/tests/Cache/FileSystemCacheItemPoolTest.php +++ b/tests/Cache/FileSystemCacheItemPoolTest.php @@ -21,11 +21,19 @@ use Google\Auth\Cache\TypedItem; use PHPUnit\Framework\TestCase; use Psr\Cache\CacheItemInterface; +use Psr\Cache\InvalidArgumentException; class FileSystemCacheItemPoolTest extends TestCase { private string $defaultCacheDirectory = 'cache/'; private FileSystemCacheItemPool $pool; + private array $invalidChars = [ + '`','~','!','@','#','$', + '%','^','&','*','(',')', + '-','=','+','[',']','{', + '}','\\',';','\'','"','<', + '>',',','/',' ', + ]; public function setUp(): void { @@ -149,6 +157,71 @@ public function testSaveDeferredAndCommit() $this->assertTrue($this->pool->getItem($item->getKey())->isHit()); } + public function testGetItemWithIncorrectKeyShouldThrowAnException() + { + foreach ($this->invalidChars as $char) { + try { + $item = $this->getNewItem($char); + $this->pool->getItem($item->getKey()); + $this->fail('The key ' . $char . ' is passing validation when should not be valid'); + } catch (InvalidArgumentException $e) { + $this->assertTrue($e instanceof InvalidArgumentException); + } + } + } + + public function testGetItemsWithIncorrectKeyShouldThrowAnException() + { + foreach ($this->invalidChars as $char) { + try { + $item = $this->getNewItem($char); + $this->pool->getItems([$item->getKey()]); + $this->fail('The key ' . $char . ' is passing validation when should not be valid'); + } catch (InvalidArgumentException $e) { + $this->assertTrue($e instanceof InvalidArgumentException); + } + } + } + + public function testHasItemWithIncorrectKeyShouldThrowAnException() + { + foreach ($this->invalidChars as $char) { + try { + $item = $this->getNewItem($char); + $this->pool->hasItem($item->getKey()); + $this->fail('The key ' . $char . ' is passing validation when should not be valid'); + } catch (InvalidArgumentException $e) { + $this->assertTrue($e instanceof InvalidArgumentException); + } + } + } + + public function testDeleteItemWithIncorrectKeyShouldThrowAnException() + { + foreach ($this->invalidChars as $char) { + try { + $item = $this->getNewItem($char); + $this->pool->deleteItem($item->getKey()); + $this->fail('The key ' . $char . ' is passing validation when should not be valid'); + } catch (InvalidArgumentException $e) { + $this->assertTrue($e instanceof InvalidArgumentException); + } + } + } + + public function testDeleteItemsWithIncorrectKeyShouldThrowAnException() + { + foreach ($this->invalidChars as $char) { + try { + $item = $this->getNewItem($char); + $this->pool->deleteItems([$item->getKey()]); + $this->fail('The key ' . $char . ' is passing validation when should not be valid'); + } catch (InvalidArgumentException $e) { + $this->assertTrue($e instanceof InvalidArgumentException); + } + } + } + private function getNewItem(null|string $key = null): TypedItem { $item = new TypedItem($key ?? 'NewItem'); From e4a45b1dafc2397f8ffc71b1d04c58abeb7f1808 Mon Sep 17 00:00:00 2001 From: Hector Mendoza Jacobo Date: Fri, 16 Aug 2024 23:04:55 +0000 Subject: [PATCH 08/14] Fix php coding style --- src/Cache/FileSystemCacheItemPool.php | 2 +- tests/Cache/FileSystemCacheItemPoolTest.php | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Cache/FileSystemCacheItemPool.php b/src/Cache/FileSystemCacheItemPool.php index 5face00de..1cdb8bb62 100644 --- a/src/Cache/FileSystemCacheItemPool.php +++ b/src/Cache/FileSystemCacheItemPool.php @@ -26,7 +26,7 @@ class FileSystemCacheItemPool implements CacheItemPoolInterface /** * @var string */ - private string $cachePath = 'cache/'; + private string $cachePath; /** * @var array diff --git a/tests/Cache/FileSystemCacheItemPoolTest.php b/tests/Cache/FileSystemCacheItemPoolTest.php index c49aaa2ff..15a01f9d9 100644 --- a/tests/Cache/FileSystemCacheItemPoolTest.php +++ b/tests/Cache/FileSystemCacheItemPoolTest.php @@ -25,14 +25,14 @@ class FileSystemCacheItemPoolTest extends TestCase { - private string $defaultCacheDirectory = 'cache/'; + private string $defaultCacheDirectory = '.cache'; private FileSystemCacheItemPool $pool; private array $invalidChars = [ - '`','~','!','@','#','$', - '%','^','&','*','(',')', - '-','=','+','[',']','{', - '}','\\',';','\'','"','<', - '>',',','/',' ', + '`', '~', '!', '@', '#', '$', + '%', '^', '&', '*', '(', ')', + '-', '=', '+', '[', ']', '{', + '}', '\\', ';', '\'', '"', '<', + '>', ',', '/', ' ', ]; public function setUp(): void From 13dac0e33475b5dd2bd73937e6b4811287b695cb Mon Sep 17 00:00:00 2001 From: Hector Mendoza Jacobo Date: Mon, 19 Aug 2024 19:15:43 +0000 Subject: [PATCH 09/14] Change implementation for save, clear and deleteItem --- src/Cache/FileSystemCacheItemPool.php | 7 +++++-- tests/Cache/FileSystemCacheItemPoolTest.php | 3 --- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Cache/FileSystemCacheItemPool.php b/src/Cache/FileSystemCacheItemPool.php index 1cdb8bb62..fdda7b6d2 100644 --- a/src/Cache/FileSystemCacheItemPool.php +++ b/src/Cache/FileSystemCacheItemPool.php @@ -109,7 +109,8 @@ public function save(CacheItemInterface $item): bool $result = file_put_contents($itemPath, $serializedItem); - if (!$result) { + // 0 bytes write is considered a successful operation + if ($result === false) { return false; } @@ -129,6 +130,8 @@ public function hasItem(string $key): bool */ public function clear(): bool { + $this->buffer = []; + if (!is_dir($this->cachePath)) { return false; } @@ -163,7 +166,7 @@ public function deleteItem(string $key): bool $itemPath = $this->cacheFilePath($key); if (!file_exists($itemPath)) { - return false; + return true; } return unlink($itemPath); diff --git a/tests/Cache/FileSystemCacheItemPoolTest.php b/tests/Cache/FileSystemCacheItemPoolTest.php index 15a01f9d9..469417a87 100644 --- a/tests/Cache/FileSystemCacheItemPoolTest.php +++ b/tests/Cache/FileSystemCacheItemPoolTest.php @@ -106,9 +106,6 @@ public function testDeleteItems() $result = $this->pool->deleteItems($itemKeys); $this->assertTrue($result); - - $result = $this->pool->deleteItems($itemKeys); - $this->assertFalse($result); } public function testGetItems() From ab052e67d47ac5d5bd95891992272a960dc1d8bd Mon Sep 17 00:00:00 2001 From: Hector Mendoza Jacobo Date: Wed, 21 Aug 2024 21:52:45 +0000 Subject: [PATCH 10/14] Change the testing structure to use a dataProvider --- README.md | 3 +- src/Cache/FileSystemCacheItemPool.php | 6 +- tests/Cache/FileSystemCacheItemPoolTest.php | 103 +++++++++----------- 3 files changed, 52 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 9328160ee..bf178310a 100644 --- a/README.md +++ b/README.md @@ -301,7 +301,8 @@ $middleware = ApplicationDefaultCredentials::getCredentials($scope, cache: $memo ``` ### FileSystemCacheItemPool Cache -The `FileSystemCacheItemPool` class is a `PSR-6` compliant cache that stores its serialized objects on disk eliminating for caching data between processes making it possible to use data between different requests. +The `FileSystemCacheItemPool` class is a `PSR-6` compliant cache that stores its + serialized objects on disk eliminating for caching data between processes making it possible to use data between different requests. ```php use Google\Auth\Cache\FileSystemCacheItemPool; diff --git a/src/Cache/FileSystemCacheItemPool.php b/src/Cache/FileSystemCacheItemPool.php index fdda7b6d2..8e67beb39 100644 --- a/src/Cache/FileSystemCacheItemPool.php +++ b/src/Cache/FileSystemCacheItemPool.php @@ -141,7 +141,7 @@ public function clear(): bool return false; } - foreach($files as $fileName) { + foreach ($files as $fileName) { if ($fileName === '.' || $fileName === '..') { continue; } @@ -179,7 +179,7 @@ public function deleteItems(array $keys): bool { $result = true; - foreach($keys as $key) { + foreach ($keys as $key) { if (!$this->deleteItem($key)) { $result = false; } @@ -221,6 +221,6 @@ private function cacheFilePath(string $key): string private function validKey(string $key): bool { - return (bool)preg_match('|^[a-zA-Z0-9_\.]+$|', $key); + return (bool) preg_match('|^[a-zA-Z0-9_\.]+$|', $key); } } diff --git a/tests/Cache/FileSystemCacheItemPoolTest.php b/tests/Cache/FileSystemCacheItemPoolTest.php index 469417a87..98bf04f4f 100644 --- a/tests/Cache/FileSystemCacheItemPoolTest.php +++ b/tests/Cache/FileSystemCacheItemPoolTest.php @@ -100,9 +100,7 @@ public function testDeleteItems() $this->pool->save($item); } - $itemKeys = array_map(function (CacheItemInterface $item) { - return $item->getKey(); - }, $items); + $itemKeys = array_map(fn($item) => $item->getKey(), $items); $result = $this->pool->deleteItems($itemKeys); $this->assertTrue($result); @@ -120,9 +118,7 @@ public function testGetItems() $this->pool->save($item); } - $keys = array_map(function (CacheItemInterface $item) { - return $item->getKey(); - }, $items); + $keys = array_map(fn($item) => $item->getKey(), $items); array_push($keys, 'NonExistant'); $retrievedItems = $this->pool->getItems($keys); @@ -154,69 +150,59 @@ public function testSaveDeferredAndCommit() $this->assertTrue($this->pool->getItem($item->getKey())->isHit()); } - public function testGetItemWithIncorrectKeyShouldThrowAnException() + /** + * @dataProvider provideInvalidChars + */ + public function testGetItemWithIncorrectKeyShouldThrowAnException($char) { - foreach ($this->invalidChars as $char) { - try { - $item = $this->getNewItem($char); - $this->pool->getItem($item->getKey()); - $this->fail('The key ' . $char . ' is passing validation when should not be valid'); - } catch (InvalidArgumentException $e) { - $this->assertTrue($e instanceof InvalidArgumentException); - } - } + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("The key '$char' is not valid. The key should follow the pattern |^[a-zA-Z0-9_\.! ]+$|"); + $item = $this->getNewItem($char); + $this->pool->getItem($item->getKey()); } - public function testGetItemsWithIncorrectKeyShouldThrowAnException() + /** + * @dataProvider provideInvalidChars + */ + public function testGetItemsWithIncorrectKeyShouldThrowAnException($char) { - foreach ($this->invalidChars as $char) { - try { - $item = $this->getNewItem($char); - $this->pool->getItems([$item->getKey()]); - $this->fail('The key ' . $char . ' is passing validation when should not be valid'); - } catch (InvalidArgumentException $e) { - $this->assertTrue($e instanceof InvalidArgumentException); - } - } + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("The key '$char' is not valid. The key should follow the pattern |^[a-zA-Z0-9_\.! ]+$|"); + $item = $this->getNewItem($char); + $this->pool->getItems([$item->getKey()]); } - public function testHasItemWithIncorrectKeyShouldThrowAnException() + /** + * @dataProvider provideInvalidChars + */ + public function testHasItemWithIncorrectKeyShouldThrowAnException($char) { - foreach ($this->invalidChars as $char) { - try { - $item = $this->getNewItem($char); - $this->pool->hasItem($item->getKey()); - $this->fail('The key ' . $char . ' is passing validation when should not be valid'); - } catch (InvalidArgumentException $e) { - $this->assertTrue($e instanceof InvalidArgumentException); - } - } + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("The key '$char' is not valid. The key should follow the pattern |^[a-zA-Z0-9_\.! ]+$|"); + $item = $this->getNewItem($char); + $this->pool->hasItem($item->getKey()); } - public function testDeleteItemWithIncorrectKeyShouldThrowAnException() + /** + * @dataProvider provideInvalidChars + */ + public function testDeleteItemWithIncorrectKeyShouldThrowAnException($char) { - foreach ($this->invalidChars as $char) { - try { - $item = $this->getNewItem($char); - $this->pool->deleteItem($item->getKey()); - $this->fail('The key ' . $char . ' is passing validation when should not be valid'); - } catch (InvalidArgumentException $e) { - $this->assertTrue($e instanceof InvalidArgumentException); - } - } + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("The key '$char' is not valid. The key should follow the pattern |^[a-zA-Z0-9_\.! ]+$|"); + $item = $this->getNewItem($char); + $this->pool->deleteItem($item->getKey()); } - public function testDeleteItemsWithIncorrectKeyShouldThrowAnException() + /** + * @dataProvider provideInvalidChars + */ + public function testDeleteItemsWithIncorrectKeyShouldThrowAnException($char) { - foreach ($this->invalidChars as $char) { - try { - $item = $this->getNewItem($char); - $this->pool->deleteItems([$item->getKey()]); - $this->fail('The key ' . $char . ' is passing validation when should not be valid'); - } catch (InvalidArgumentException $e) { - $this->assertTrue($e instanceof InvalidArgumentException); - } - } + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("The key '$char' is not valid. The key should follow the pattern |^[a-zA-Z0-9_\.! ]+$|"); + $item = $this->getNewItem($char); + $this->pool->deleteItems([$item->getKey()]); } private function getNewItem(null|string $key = null): TypedItem @@ -226,4 +212,9 @@ private function getNewItem(null|string $key = null): TypedItem return $item; } + + private function provideInvalidChars(): array + { + return array_map(fn($char) => [$char], $this->invalidChars); + } } From 6497832283f9667f37bd850db9f95eaaa8be52d1 Mon Sep 17 00:00:00 2001 From: Hector Mendoza Jacobo Date: Wed, 21 Aug 2024 21:55:15 +0000 Subject: [PATCH 11/14] Change the data provider from private to public --- tests/Cache/FileSystemCacheItemPoolTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Cache/FileSystemCacheItemPoolTest.php b/tests/Cache/FileSystemCacheItemPoolTest.php index 98bf04f4f..f1372913f 100644 --- a/tests/Cache/FileSystemCacheItemPoolTest.php +++ b/tests/Cache/FileSystemCacheItemPoolTest.php @@ -213,7 +213,7 @@ private function getNewItem(null|string $key = null): TypedItem return $item; } - private function provideInvalidChars(): array + public function provideInvalidChars(): array { return array_map(fn($char) => [$char], $this->invalidChars); } From 287bfc8292f8ff4eda180fcb4163ea5962ab0825 Mon Sep 17 00:00:00 2001 From: Hector Mendoza Jacobo Date: Wed, 21 Aug 2024 21:57:19 +0000 Subject: [PATCH 12/14] Fix PHP CS Style --- tests/Cache/FileSystemCacheItemPoolTest.php | 31 ++++++++++----------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/tests/Cache/FileSystemCacheItemPoolTest.php b/tests/Cache/FileSystemCacheItemPoolTest.php index f1372913f..97c8e7fa5 100644 --- a/tests/Cache/FileSystemCacheItemPoolTest.php +++ b/tests/Cache/FileSystemCacheItemPoolTest.php @@ -20,7 +20,6 @@ use Google\Auth\Cache\FileSystemCacheItemPool; use Google\Auth\Cache\TypedItem; use PHPUnit\Framework\TestCase; -use Psr\Cache\CacheItemInterface; use Psr\Cache\InvalidArgumentException; class FileSystemCacheItemPoolTest extends TestCase @@ -100,7 +99,7 @@ public function testDeleteItems() $this->pool->save($item); } - $itemKeys = array_map(fn($item) => $item->getKey(), $items); + $itemKeys = array_map(fn ($item) => $item->getKey(), $items); $result = $this->pool->deleteItems($itemKeys); $this->assertTrue($result); @@ -118,7 +117,7 @@ public function testGetItems() $this->pool->save($item); } - $keys = array_map(fn($item) => $item->getKey(), $items); + $keys = array_map(fn ($item) => $item->getKey(), $items); array_push($keys, 'NonExistant'); $retrievedItems = $this->pool->getItems($keys); @@ -150,9 +149,9 @@ public function testSaveDeferredAndCommit() $this->assertTrue($this->pool->getItem($item->getKey())->isHit()); } - /** - * @dataProvider provideInvalidChars - */ + /** + * @dataProvider provideInvalidChars + */ public function testGetItemWithIncorrectKeyShouldThrowAnException($char) { $this->expectException(InvalidArgumentException::class); @@ -161,9 +160,9 @@ public function testGetItemWithIncorrectKeyShouldThrowAnException($char) $this->pool->getItem($item->getKey()); } - /** - * @dataProvider provideInvalidChars - */ + /** + * @dataProvider provideInvalidChars + */ public function testGetItemsWithIncorrectKeyShouldThrowAnException($char) { $this->expectException(InvalidArgumentException::class); @@ -172,9 +171,9 @@ public function testGetItemsWithIncorrectKeyShouldThrowAnException($char) $this->pool->getItems([$item->getKey()]); } - /** - * @dataProvider provideInvalidChars - */ + /** + * @dataProvider provideInvalidChars + */ public function testHasItemWithIncorrectKeyShouldThrowAnException($char) { $this->expectException(InvalidArgumentException::class); @@ -194,9 +193,9 @@ public function testDeleteItemWithIncorrectKeyShouldThrowAnException($char) $this->pool->deleteItem($item->getKey()); } - /** - * @dataProvider provideInvalidChars - */ + /** + * @dataProvider provideInvalidChars + */ public function testDeleteItemsWithIncorrectKeyShouldThrowAnException($char) { $this->expectException(InvalidArgumentException::class); @@ -215,6 +214,6 @@ private function getNewItem(null|string $key = null): TypedItem public function provideInvalidChars(): array { - return array_map(fn($char) => [$char], $this->invalidChars); + return array_map(fn ($char) => [$char], $this->invalidChars); } } From 9b46b1f9533dd648bc4106001cddaba821c830fc Mon Sep 17 00:00:00 2001 From: Hector Mendoza Jacobo Date: Wed, 21 Aug 2024 23:35:59 +0000 Subject: [PATCH 13/14] Change the serialize usage to serialize the data inside the TypedItem and not the TypedItem itself --- src/Cache/FileSystemCacheItemPool.php | 12 ++++++++---- tests/Cache/FileSystemCacheItemPoolTest.php | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Cache/FileSystemCacheItemPool.php b/src/Cache/FileSystemCacheItemPool.php index 8e67beb39..ee0651a4e 100644 --- a/src/Cache/FileSystemCacheItemPool.php +++ b/src/Cache/FileSystemCacheItemPool.php @@ -60,19 +60,23 @@ public function getItem(string $key): CacheItemInterface throw new InvalidArgumentException("The key '$key' is not valid. The key should follow the pattern |^[a-zA-Z0-9_\.! ]+$|"); } + $item = new TypedItem($key); + $itemPath = $this->cacheFilePath($key); if (!file_exists($itemPath)) { - return new TypedItem($key); + return $item; } $serializedItem = file_get_contents($itemPath); if ($serializedItem === false) { - return new TypedItem($key); + return $item; } - return unserialize($serializedItem); + $item->set(unserialize($serializedItem)); + + return $item; } /** @@ -105,7 +109,7 @@ public function save(CacheItemInterface $item): bool } $itemPath = $this->cacheFilePath($item->getKey()); - $serializedItem = serialize($item); + $serializedItem = serialize($item->get()); $result = file_put_contents($itemPath, $serializedItem); diff --git a/tests/Cache/FileSystemCacheItemPoolTest.php b/tests/Cache/FileSystemCacheItemPoolTest.php index 97c8e7fa5..a3214587a 100644 --- a/tests/Cache/FileSystemCacheItemPoolTest.php +++ b/tests/Cache/FileSystemCacheItemPoolTest.php @@ -63,6 +63,7 @@ public function testInstanceCreatesCacheFolder() public function testSaveAndGetItem() { $item = $this->getNewItem(); + $item->expiresAfter(60); $this->pool->save($item); $retrievedItem = $this->pool->getItem($item->getKey()); From 2114fdf66f74eebd3c1140410fc33e80a3979c8a Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Thu, 22 Aug 2024 10:49:41 -0700 Subject: [PATCH 14/14] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bf178310a..ce23622af 100644 --- a/README.md +++ b/README.md @@ -302,7 +302,8 @@ $middleware = ApplicationDefaultCredentials::getCredentials($scope, cache: $memo ### FileSystemCacheItemPool Cache The `FileSystemCacheItemPool` class is a `PSR-6` compliant cache that stores its - serialized objects on disk eliminating for caching data between processes making it possible to use data between different requests. +serialized objects on disk, caching data between processes and making it possible +to use data between different requests. ```php use Google\Auth\Cache\FileSystemCacheItemPool;