From 807f5ee1c0f1bd09d94c05bb01d576c46ee37979 Mon Sep 17 00:00:00 2001 From: Serban Ghita Date: Sun, 8 Dec 2024 21:22:20 +0200 Subject: [PATCH] Changed configuration cacheKeyFn to work via call_user_func Added tests --- src/Cache/Cache.php | 3 ++ src/MobileDetect.php | 15 +++++--- tests/MobileDetectWithCacheTest.php | 52 +++++++++++++++++++++++++++ tests/benchmark/MobileDetectBench.php | 21 +++++++++++ 4 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/Cache/Cache.php b/src/Cache/Cache.php index 1f7bc82b..484e0414 100644 --- a/src/Cache/Cache.php +++ b/src/Cache/Cache.php @@ -4,6 +4,9 @@ use Psr\SimpleCache\CacheInterface; +/** + * Generic implementation of a cache system using an associative array. + */ class Cache implements CacheInterface { /** diff --git a/src/MobileDetect.php b/src/MobileDetect.php index 3a9c7cb7..59e908bc 100644 --- a/src/MobileDetect.php +++ b/src/MobileDetect.php @@ -251,6 +251,8 @@ class MobileDetect // Maximum HTTP User-Agent value allowed. // @var int 'maximumUserAgentLength' => 500, + // Function that creates the cache key. e.g. (base64, sha1, custom fn). + 'cacheKeyFn' => 'base64_encode', // Cache TTL // @var null|int|\DateInterval 'cacheTtl' => 86400, @@ -1700,6 +1702,9 @@ public function getCache(): Cache return $this->cache; } + /** + * @throws CacheException + */ protected function createCacheKey(string $key): string { $userAgentKey = $this->hasUserAgent() ? $this->userAgent : ''; @@ -1707,11 +1712,13 @@ protected function createCacheKey(string $key): string $cacheKey = "$key:$userAgentKey:$httpHeadersKey"; - $cacheKeyUsing = $this->config['cacheKeyUsing'] ?? function (string $cacheKey) { - return base64_encode($cacheKey); - }; + $cacheKeyFn = $this->config['cacheKeyFn']; + + if (!is_callable($cacheKeyFn)) { + throw new CacheException('cacheKeyFn is not a function.'); + } - return call_user_func($cacheKeyUsing, $cacheKey); + return call_user_func($cacheKeyFn, $cacheKey); } public static function flattenHeaders(array $httpHeaders): string diff --git a/tests/MobileDetectWithCacheTest.php b/tests/MobileDetectWithCacheTest.php index e8e95980..754a53e6 100644 --- a/tests/MobileDetectWithCacheTest.php +++ b/tests/MobileDetectWithCacheTest.php @@ -2,6 +2,8 @@ namespace DetectionTests; +use Detection\Cache\Cache; +use Detection\Cache\CacheItem; use Detection\Exception\MobileDetectException; use Detection\MobileDetect; use PHPUnit\Framework\TestCase; @@ -112,4 +114,54 @@ public function testDefaultCacheClassCreatesMultipleCacheRecordsForAllCalls() base64_decode($detect->getCache()->getKeys()[3]) ); } + + /** + * @throws MobileDetectException + */ + public function testCustomCacheWithInvalidFnThrowsException() + { + $this->expectException(MobileDetectException::class); + $this->expectExceptionMessage('Cache problem in isMobile(): cacheKeyFn is not a function.'); + $cache = new Cache(); + + $detect = new MobileDetect($cache, ['cacheKeyFn' => 'not a function']); + $detect->setUserAgent('iPad; AppleWebKit/533.17.9 Version/5.0.2 Mobile/8C148 Safari/6533.18.5'); + $detect->isMobile(); + } + + public function testCustomCacheForConsecutiveCalls() + { + $cache = new Cache(); + + $detect = new MobileDetect($cache, ['cacheKeyFn' => fn ($key) => base64_encode($key)]); + $detect->setUserAgent('iPad; AppleWebKit/533.17.9 Version/5.0.2 Mobile/8C148 Safari/6533.18.5'); + + $detect->isMobile(); + $this->assertCount(1, $cache->getKeys()); + + $detect->isMobile(); + $this->assertCount(1, $cache->getKeys()); + } + + public function testGetCacheKeyIsUsedInConsecutiveCallsIfFoundIn() + { + $cache = $this->getMockBuilder(Cache::class) + ->onlyMethods(["get", "set"]) + ->getMock(); + $cache->method('get')->withAnyParameters()->willReturn(new CacheItem('name', 'value')); + $cache->method('set')->withAnyParameters()->willReturn(true); + + + $cache->expects($spy = $this->exactly(2))->method('get'); + $cache->expects($spy = $this->never())->method('set'); + + + + $detect = new MobileDetect($cache); + $detect->setUserAgent('iPad; AppleWebKit/533.17.9 Version/5.0.2 Mobile/8C148 Safari/6533.18.5'); + + $detect->isMobile(); + $detect->isMobile(); + + } } diff --git a/tests/benchmark/MobileDetectBench.php b/tests/benchmark/MobileDetectBench.php index 01ec860e..ca9fc4ab 100644 --- a/tests/benchmark/MobileDetectBench.php +++ b/tests/benchmark/MobileDetectBench.php @@ -124,4 +124,25 @@ public function benchIsSamsungTablet(): void $detect->setUserAgent('Mozilla/5.0 (Linux; Android 12; SM-X906C Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/80.0.3987.119 Mobile Safari/537.36'); $detect->isSamsungTablet(); } + + public function benchIsMobileCacheKeyFnBase64AgainstBestMatch(): void + { + $detect = new MobileDetect(null, ['cacheKeyFn' => 'base64_encode']); + $detect->setUserAgent('iPhone'); + $detect->isMobile(); + } + + public function benchIsMobileCacheKeyFnSha1AgainstBestMatch(): void + { + $detect = new MobileDetect(null, ['cacheKeyFn' => 'sha1']); + $detect->setUserAgent('iPhone'); + $detect->isMobile(); + } + + public function benchIsMobileCacheKeyFnCustomCryptFnAgainstBestMatch(): void + { + $detect = new MobileDetect(null, ['cacheKeyFn' => fn ($key) => crypt($key, 'bla')]); + $detect->setUserAgent('iPhone'); + $detect->isMobile(); + } }