From c6e814fbddabf2f298e2e2f374c0aede00f565d2 Mon Sep 17 00:00:00 2001 From: soyuka Date: Sat, 8 Nov 2025 08:19:28 +0100 Subject: [PATCH] feat(metadata): cache operation metadata factory --- .../Factory/CacheOperationMetadataFactory.php | 66 +++++++++++++++++++ .../Factory/OperationMetadataFactory.php | 8 ++- .../Resources/config/metadata/operation.php | 11 ++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/Metadata/Operation/Factory/CacheOperationMetadataFactory.php diff --git a/src/Metadata/Operation/Factory/CacheOperationMetadataFactory.php b/src/Metadata/Operation/Factory/CacheOperationMetadataFactory.php new file mode 100644 index 00000000000..972b7801fe5 --- /dev/null +++ b/src/Metadata/Operation/Factory/CacheOperationMetadataFactory.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Metadata\Operation\Factory; + +use ApiPlatform\Metadata\Operation; +use Psr\Cache\CacheException; +use Psr\Cache\CacheItemPoolInterface; + +/** + * Caches operation metadata. + * + * @author Antoine Bluchet + */ +final class CacheOperationMetadataFactory implements OperationMetadataFactoryInterface +{ + public const CACHE_KEY_PREFIX = 'operation_metadata_'; + private array $localCache = []; + + public function __construct(private readonly CacheItemPoolInterface $cacheItemPool, private readonly OperationMetadataFactoryInterface $decorated) + { + } + + /** + * {@inheritdoc} + */ + public function create(string $uriTemplate, array $context = []): ?Operation + { + $cacheKey = self::CACHE_KEY_PREFIX.hash('xxh3', $uriTemplate); + if (\array_key_exists($cacheKey, $this->localCache)) { + return $this->localCache[$cacheKey]; + } + + try { + $cacheItem = $this->cacheItemPool->getItem($cacheKey); + } catch (CacheException) { + $operation = $this->decorated->create($uriTemplate, $context); + $this->localCache[$cacheKey] = $operation; + + return $operation; + } + + if ($cacheItem->isHit()) { + $this->localCache[$cacheKey] = $cacheItem->get(); + + return $this->localCache[$cacheKey]; + } + + $operation = $this->decorated->create($uriTemplate, $context); + $this->localCache[$cacheKey] = $operation; + $cacheItem->set($this->localCache[$cacheKey]); + $this->cacheItemPool->save($cacheItem); + + return $operation; + } +} diff --git a/src/Metadata/Operation/Factory/OperationMetadataFactory.php b/src/Metadata/Operation/Factory/OperationMetadataFactory.php index 8f966d80203..bcaa7bf63ed 100644 --- a/src/Metadata/Operation/Factory/OperationMetadataFactory.php +++ b/src/Metadata/Operation/Factory/OperationMetadataFactory.php @@ -19,17 +19,23 @@ final class OperationMetadataFactory implements OperationMetadataFactoryInterface { + private array $localCache = []; + public function __construct(private readonly ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory) { } public function create(string $uriTemplate, array $context = []): ?Operation { + if (isset($this->localCache[$uriTemplate])) { + return $this->localCache[$uriTemplate]; + } + foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) { foreach ($this->resourceMetadataCollectionFactory->create($resourceClass) as $resource) { foreach ($resource->getOperations() as $operation) { if ($operation->getUriTemplate() === $uriTemplate || $operation->getName() === $uriTemplate) { - return $operation; + return $this->localCache[$uriTemplate] = $operation; } } } diff --git a/src/Symfony/Bundle/Resources/config/metadata/operation.php b/src/Symfony/Bundle/Resources/config/metadata/operation.php index 66127b8fddf..a2d9e9e7f3a 100644 --- a/src/Symfony/Bundle/Resources/config/metadata/operation.php +++ b/src/Symfony/Bundle/Resources/config/metadata/operation.php @@ -23,4 +23,15 @@ ]); $services->alias('ApiPlatform\Metadata\Operation\Factory\OperationMetadataFactoryInterface', 'api_platform.metadata.operation.metadata_factory'); + + $services->set('api_platform.metadata.operation.metadata_factory.cached', 'ApiPlatform\Metadata\Operation\Factory\CacheOperationMetadataFactory') + ->decorate('api_platform.metadata.operation.metadata_factory', null, -10) + ->args([ + service('api_platform.cache.metadata.operation'), + service('api_platform.metadata.operation.metadata_factory.cached.inner'), + ]); + + $services->set('api_platform.cache.metadata.operation') + ->parent('cache.system') + ->tag('cache.pool'); };