diff --git a/README.md b/README.md index 6048a79..7111187 100644 --- a/README.md +++ b/README.md @@ -166,11 +166,12 @@ $browser = new Parser(null, null, [ Available options: -| Key | Default | Description | -| :--------------------------- | :-----: | :------------------------------------- | -| `cache.interval` | `10080` | Cache TTL in seconds | -| `cache.prefix` | `bd4_` | Cache key prefix | -| `security.max-header-length` | `2048` | Max user agent length (DoS protection) | +| Key | Default | Description | +| :--------------------------- | :-------: | :----------------------------------------------------- | +| `cache.interval` | `10080` | Cache TTL in seconds | +| `cache.prefix` | `bd4_` | Cache key prefix | +| `cache.device-detector` | `null` | Cache driver for device-detector's internal cache. See `config/browser-detect.php` for examples. | +| `security.max-header-length` | `2048` | Max user agent length (DoS protection) | ## Quality diff --git a/config/browser-detect.php b/config/browser-detect.php index 1c85b19..68af1c4 100644 --- a/config/browser-detect.php +++ b/config/browser-detect.php @@ -10,6 +10,15 @@ * Cache prefix, the user agent string will be hashed and appended at the end. */ 'prefix' => 'bd4_', + /** + * Cache driver class for the device-detector engine's internal cache. When null, + * the library uses its built-in StaticCache. Override to swap in a different driver. + * + * Examples: + * \DeviceDetector\Cache\StaticCache::class — in-process static cache (default) + * \DeviceDetector\Cache\LaravelCache::class — persists via Laravel's cache store + */ + 'device-detector' => null, ], 'security' => [ /** diff --git a/src/Parser.php b/src/Parser.php index b815f57..2b2b308 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -2,6 +2,7 @@ namespace hisorange\BrowserDetect; +use DeviceDetector\Cache\CacheInterface; use hisorange\BrowserDetect\Contracts\ParserInterface; use hisorange\BrowserDetect\Contracts\ResultInterface; use hisorange\BrowserDetect\Contracts\StageInterface; @@ -81,7 +82,7 @@ public function __construct(?CacheManager $cache = null, ?Request $request = nul $this->pipeline = [ new Stages\CrawlerDetect, - new Stages\DeviceDetector, + new Stages\DeviceDetector($this->cacheConfig()['device-detector']), new Stages\BrowserDetect, ]; } @@ -202,11 +203,11 @@ protected function makeHashKey(string $agent): string } /** - * @return array{interval: int, prefix: string} + * @return array{interval: int, prefix: string, device-detector: class-string|null} */ private function cacheConfig(): array { - /** @var array{interval: int, prefix: string} */ + /** @var array{interval: int, prefix: string, device-detector: class-string|null} */ return $this->config['cache']; } diff --git a/src/Stages/DeviceDetector.php b/src/Stages/DeviceDetector.php index db57ee6..038cea1 100644 --- a/src/Stages/DeviceDetector.php +++ b/src/Stages/DeviceDetector.php @@ -2,6 +2,7 @@ namespace hisorange\BrowserDetect\Stages; +use DeviceDetector\Cache\CacheInterface; use DeviceDetector\Parser\Device\AbstractDeviceParser; use hisorange\BrowserDetect\Contracts\PayloadInterface; use hisorange\BrowserDetect\Contracts\StageInterface; @@ -13,12 +14,21 @@ class DeviceDetector implements StageInterface { protected ?\DeviceDetector\DeviceDetector $detector = null; + /** + * @param class-string|null $deviceDetectorCache + */ + public function __construct(protected ?string $deviceDetectorCache = null) {} + public function __invoke(PayloadInterface $payload): PayloadInterface { if ($this->detector === null) { $this->detector = new \DeviceDetector\DeviceDetector; // Skip bot detection — CrawlerDetect handles that upstream. $this->detector->skipBotDetection(true); + if ($this->deviceDetectorCache !== null) { + $cacheClass = $this->deviceDetectorCache; + $this->detector->setCache(new $cacheClass); + } } $this->detector->setUserAgent($payload->getAgent()); $this->detector->parse(); diff --git a/tests/Stages/DeviceDetectorTest.php b/tests/Stages/DeviceDetectorTest.php index 9244ba5..ea4e2a6 100644 --- a/tests/Stages/DeviceDetectorTest.php +++ b/tests/Stages/DeviceDetectorTest.php @@ -2,9 +2,11 @@ namespace hisorange\BrowserDetect\Test\Stages; +use DeviceDetector\Cache\LaravelCache; use hisorange\BrowserDetect\Payload; use hisorange\BrowserDetect\Stages\DeviceDetector; use hisorange\BrowserDetect\Test\TestCase; +use Illuminate\Support\Facades\Cache; use PHPUnit\Framework\Attributes\DataProvider; /** @@ -77,6 +79,24 @@ public static function provideAgents() ]; } + /** + * @covers ::__construct() + * @covers ::__invoke() + */ + public function test_invoke_with_device_detector_cache_enabled() + { + $spy = \Mockery::spy(Cache::getFacadeRoot())->makePartial(); + Cache::swap($spy); + + $stage = new DeviceDetector(deviceDetectorCache: LaravelCache::class); + $payload = new Payload('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36'); + $stage($payload); + + $this->assertSame('Blink', $payload->getValue('browserEngine')); + $this->assertSame('Chrome', $payload->getValue('browserFamily')); + $spy->shouldHaveReceived('put'); + } + /** * @covers ::__invoke() */