From 222e31a8527bc91d85ffc086f74b1ab8ab9b1fee Mon Sep 17 00:00:00 2001 From: Thijs De Paepe Date: Tue, 19 Sep 2023 16:39:11 +0200 Subject: [PATCH] added: a cache layer to the bynder image action --- .../Action/GetImageFromBynderAction.php | 56 +++--- src/Component/Common/Cache/CacheSettings.php | 24 +++ .../Cache/Local/LocalFilesystemCache.php | 145 +++++++++++++++ .../Cache/Local/LocalFilesystemCacheTest.php | 166 ++++++++++++++++++ 4 files changed, 371 insertions(+), 20 deletions(-) create mode 100644 src/Component/Common/Cache/CacheSettings.php create mode 100644 src/Component/Common/Cache/Local/LocalFilesystemCache.php create mode 100644 tests/Component/Common/Cache/Local/LocalFilesystemCacheTest.php diff --git a/src/Component/Action/GetImageFromBynderAction.php b/src/Component/Action/GetImageFromBynderAction.php index 020af4fa..d2ecf759 100644 --- a/src/Component/Action/GetImageFromBynderAction.php +++ b/src/Component/Action/GetImageFromBynderAction.php @@ -2,9 +2,10 @@ namespace Misery\Component\Action; +use Misery\Component\Common\Cache\CacheSettings; +use Misery\Component\Common\Cache\Local\LocalFilesystemCache; use Misery\Component\Common\Options\OptionsInterface; use Misery\Component\Common\Options\OptionsTrait; -use function _PHPStan_76800bfb5\regex; class GetImageFromBynderAction implements OptionsInterface { @@ -19,29 +20,44 @@ class GetImageFromBynderAction implements OptionsInterface 'bynder_cookieid' => 'xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx', 'array_location' => [ 'original' ], ]; + private ?LocalFilesystemCache $cachePool; - public function apply(array $item): array + public function init(): void { - $fields = $this->options['fields']; - $bynder_url = $this->options['bynder_url']; - $bynder_token = $this->options['bynder_token']; - $bynder_cookieid = $this->options['bynder_cookieid']; - $array_location = $this->options['array_location']; - - $bynder = [ - 'url' => $bynder_url, - 'token' => $bynder_token, - 'cookieid' => $bynder_cookieid, - ]; - - // validation - if (!isset($fields) || $fields === []) { - return $item; + if (null === $this->cachePool) { + $this->cachePool = new LocalFilesystemCache( + sys_get_temp_dir() . + DIRECTORY_SEPARATOR . + 'bynder_cache' . + DIRECTORY_SEPARATOR . + md5($this->getOption('bynder_url')) + ); } + } - $item = $this->sendRequest($bynder, $item, $fields, $array_location); - - return $item; + public function apply(array $item): array + { + $this->init(); + + // Generate a unique cache key based on your data + $cacheKey = md5(json_encode($item)); // You can modify this based on your data structure + + return $this->cachePool->retrieve($cacheKey, function (CacheSettings $settings) use ($item) { + $settings->setTtl(259200); # 3 day + $fields = $this->options['fields']; + $bynder_url = $this->options['bynder_url']; + $bynder_token = $this->options['bynder_token']; + $bynder_cookieid = $this->options['bynder_cookieid']; + $array_location = $this->options['array_location']; + + $bynder = [ + 'url' => $bynder_url, + 'token' => $bynder_token, + 'cookieid' => $bynder_cookieid, + ]; + + return $this->sendRequest($bynder, $item, $fields, $array_location); + }); } public function sendRequest(array $bynder, array $item, array $fields, array $array_location) diff --git a/src/Component/Common/Cache/CacheSettings.php b/src/Component/Common/Cache/CacheSettings.php new file mode 100644 index 00000000..d796f7ba --- /dev/null +++ b/src/Component/Common/Cache/CacheSettings.php @@ -0,0 +1,24 @@ +ttl = $ttl; + } + + /** + * @return int + */ + public function getTtl(): int + { + return $this->ttl; + } +} \ No newline at end of file diff --git a/src/Component/Common/Cache/Local/LocalFilesystemCache.php b/src/Component/Common/Cache/Local/LocalFilesystemCache.php new file mode 100644 index 00000000..27b7f1f6 --- /dev/null +++ b/src/Component/Common/Cache/Local/LocalFilesystemCache.php @@ -0,0 +1,145 @@ +cacheDirectory = $cacheDirectory; + if (!is_dir($this->cacheDirectory)) { + mkdir($this->cacheDirectory, 0777, true); + } + } + + public function retrieve($key, callable $process) + { + $cachedData = $this->get($key); + if ($cachedData !== null) { + return $cachedData; + } + + $item = $process($setting = new CacheSettings()); + + // Cache the API response + $this->set($key, $item, $setting->getTtl()); + + return $item; + } + + public function get($key, $default = null) + { + $filename = $this->getCacheFilename($key); + + if (!file_exists($filename)) { + return $default; + } + + $data = file_get_contents($filename); + $cachedItem = unserialize($data); + + if ($cachedItem['ttl'] !== null && $cachedItem['ttl'] < time()) { + $this->delete($key); + return $default; + } + + return $cachedItem['value']; + } + + public function set($key, $value, $ttl = null) + { + $filename = $this->getCacheFilename($key); + $cachedItem = [ + 'value' => $value, + 'ttl' => $ttl !== null ? time() + $ttl : null, + ]; + + $data = serialize($cachedItem); + return file_put_contents($filename, $data) !== false; + } + + public function delete($key) + { + $filename = $this->getCacheFilename($key); + + if (file_exists($filename)) { + return unlink($filename); + } + + return false; + } + + public function clear() + { + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($this->cacheDirectory, \RecursiveDirectoryIterator::SKIP_DOTS), + \RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($iterator as $path) { + if ($path->isDir()) { + rmdir($path->getPathname()); + } else { + unlink($path->getPathname()); + } + } + + return true; + } + + public function getMultiple($keys, $default = null) + { + $values = []; + foreach ($keys as $key) { + $values[$key] = $this->get($key, $default); + } + return $values; + } + + public function setMultiple($values, $ttl = null) + { + $success = true; + foreach ($values as $key => $value) { + $success = $success && $this->set($key, $value, $ttl); + } + return $success; + } + + public function deleteMultiple($keys) + { + $success = true; + foreach ($keys as $key) { + $success = $success && $this->delete($key); + } + return $success; + } + + public function has($key) + { + $filename = $this->getCacheFilename($key); + + if (!file_exists($filename)) { + return false; + } + + $cachedItem = unserialize(file_get_contents($filename)); + + if ($cachedItem['ttl'] !== null && $cachedItem['ttl'] < time()) { + $this->delete($key); + return false; + } + + return true; + } + + private function getCacheFilename($key) + { + $key = preg_replace('/[^a-z0-9_\-]/i', '', $key); // Sanitize key + return $this->cacheDirectory . DIRECTORY_SEPARATOR . $key; + } +} diff --git a/tests/Component/Common/Cache/Local/LocalFilesystemCacheTest.php b/tests/Component/Common/Cache/Local/LocalFilesystemCacheTest.php new file mode 100644 index 00000000..554752f8 --- /dev/null +++ b/tests/Component/Common/Cache/Local/LocalFilesystemCacheTest.php @@ -0,0 +1,166 @@ +cacheDirectory = sys_get_temp_dir() . '/cache_test'; + $this->cache = new LocalFilesystemCache($this->cacheDirectory); + } + + protected function tearDown(): void + { + $this->cache->clear(); + } + + public function testSetAndGet() + { + $key = 'test_key'; + $value = 'test_value'; + + $this->assertTrue($this->cache->set($key, $value)); + $this->assertEquals($value, $this->cache->get($key)); + } + + public function testRetrieve() + { + $key = 'test_key'; + $value = 'test_value'; + + // Mock a callback function that simulates fetching data + $processCallback = function ($settings) use ($value) { + $this->assertInstanceOf(CacheSettings::class, $settings); + return $value; + }; + + // Call retrieve and ensure it returns the value from the callback + $cachedValue = $this->cache->retrieve($key, $processCallback); + $this->assertEquals($value, $cachedValue); + + // Verify that the value is cached + $cachedValueFromCache = $this->cache->get($key); + $this->assertEquals($value, $cachedValueFromCache); + } + + public function testGetWithDefault() + { + $key = 'non_existent_key'; + $defaultValue = 'default_value'; + + $result = $this->cache->get($key, $defaultValue); + + $this->assertEquals($defaultValue, $result); + } + + public function testDelete() + { + $key = 'test_key'; + $value = 'test_value'; + + $this->cache->set($key, $value); + $this->assertTrue($this->cache->delete($key)); + $this->assertNull($this->cache->get($key)); + } + + public function testClear() + { + $key1 = 'test_key1'; + $value1 = 'test_value1'; + $key2 = 'test_key2'; + $value2 = 'test_value2'; + + $this->cache->set($key1, $value1); + $this->cache->set($key2, $value2); + + $this->assertTrue($this->cache->clear()); + + $this->assertNull($this->cache->get($key1)); + $this->assertNull($this->cache->get($key2)); + } + + public function testSetWithTTL() + { + $key = 'test_key'; + $value = 'test_value'; + + $ttl = 1; // 1 second + + $this->assertTrue($this->cache->set($key, $value, $ttl)); + sleep(2); // Sleep for more than the TTL + + $this->assertNull($this->cache->get($key)); + } + + public function testHas() + { + $key = 'test_key'; + $value = 'test_value'; + + $this->cache->set($key, $value); + + $this->assertTrue($this->cache->has($key)); + $this->assertFalse($this->cache->has('non_existent_key')); + } + + public function testGetMultiple() + { + $key1 = 'test_key1'; + $value1 = 'test_value1'; + $key2 = 'test_key2'; + $value2 = 'test_value2'; + + $this->cache->set($key1, $value1); + $this->cache->set($key2, $value2); + + $keys = [$key1, $key2]; + $default = 'default_value'; + + $result = $this->cache->getMultiple($keys, $default); + + $this->assertEquals([$key1 => $value1, $key2 => $value2], $result); + } + + public function testSetMultiple() + { + $data = [ + 'key1' => 'value1', + 'key2' => 'value2', + 'key3' => 'value3', + ]; + + $this->assertTrue($this->cache->setMultiple($data)); + + $this->assertEquals($data['key1'], $this->cache->get('key1')); + $this->assertEquals($data['key2'], $this->cache->get('key2')); + $this->assertEquals($data['key3'], $this->cache->get('key3')); + } + + public function testDeleteMultiple() + { + $key1 = 'test_key1'; + $value1 = 'test_value1'; + $key2 = 'test_key2'; + $value2 = 'test_value2'; + $key3 = 'test_key3'; + $value3 = 'test_value3'; + + $this->cache->set($key1, $value1); + $this->cache->set($key2, $value2); + $this->cache->set($key3, $value3); + + $keysToDelete = [$key1, $key2]; + + $this->assertTrue($this->cache->deleteMultiple($keysToDelete)); + $this->assertNull($this->cache->get($key1)); + $this->assertNull($this->cache->get($key2)); + $this->assertEquals($value3, $this->cache->get($key3)); + } +}