diff --git a/src/Parser.php b/src/Parser.php index f098b2d..b815f57 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -14,6 +14,9 @@ */ final class Parser implements ParserInterface { + // Bump this to invalidate all cached results (e.g. after changing the serialization format). + private const CACHE_VERSION = 1; + /** * @var CacheManager|null */ @@ -171,12 +174,15 @@ public function parse(string $agent): ResultInterface if (! isset($this->runtime[$key])) { // In standalone mode, You can run the parser without a cache. if ($this->cache !== null) { - /** @var ResultInterface $result */ - $result = $this->cache->remember( + // Cache the plain array to avoid __PHP_Incomplete_Class on deserialization. + /** @var array $data */ + $data = $this->cache->remember( $key, $this->cacheConfig()['interval'], - fn () => $this->process($agent) + fn () => $this->process($agent)->toArray() ); + + $result = new Result($data); } else { $result = $this->process($agent); } @@ -192,7 +198,7 @@ public function parse(string $agent): ResultInterface */ protected function makeHashKey(string $agent): string { - return $this->cacheConfig()['prefix'].hash('xxh128', $agent); + return $this->cacheConfig()['prefix'].self::CACHE_VERSION.'_'.hash('xxh128', $agent); } /** diff --git a/tests/ParserTest.php b/tests/ParserTest.php index f390bb0..72b14d2 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -177,4 +177,27 @@ public function test_detect_truncates_long_user_agent() $this->assertSame(10, strlen($result->userAgent())); } + + /** + * Regression test for GitHub issue #16. + * Ensures cached results don't produce __PHP_Incomplete_Class errors. + * + * @covers ::parse() + */ + public function test_cache_returns_result_interface_not_incomplete_class() + { + $parser = $this->getParser(); + $agent = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:149.0) Gecko/20100101 Firefox/149.0'; + + // First call populates the cache. + $first = $parser->parse($agent); + $this->assertInstanceOf(ResultInterface::class, $first); + + // Create a new parser instance (no runtime cache) to force reading from the application cache. + $freshParser = $this->getParser(); + $second = $freshParser->parse($agent); + + $this->assertInstanceOf(ResultInterface::class, $second); + $this->assertSame($first->toArray(), $second->toArray()); + } }