diff --git a/src/GraphQl/Serializer/ItemNormalizer.php b/src/GraphQl/Serializer/ItemNormalizer.php index 1d94476c787..f3685aeed64 100644 --- a/src/GraphQl/Serializer/ItemNormalizer.php +++ b/src/GraphQl/Serializer/ItemNormalizer.php @@ -24,7 +24,6 @@ use ApiPlatform\Metadata\ResourceAccessCheckerInterface; use ApiPlatform\Metadata\ResourceClassResolverInterface; use ApiPlatform\Metadata\Util\ClassInfoTrait; -use ApiPlatform\Serializer\CacheKeyTrait; use ApiPlatform\Serializer\ItemNormalizer as BaseItemNormalizer; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; @@ -40,15 +39,12 @@ */ final class ItemNormalizer extends BaseItemNormalizer { - use CacheKeyTrait; use ClassInfoTrait; public const FORMAT = 'graphql'; public const ITEM_RESOURCE_CLASS_KEY = '#itemResourceClass'; public const ITEM_IDENTIFIERS_KEY = '#itemIdentifiers'; - private array $safeCacheKeysCache = []; - public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, private readonly IdentifiersExtractorInterface $identifiersExtractor, ResourceClassResolverInterface $resourceClassResolver, ?PropertyAccessorInterface $propertyAccessor = null, ?NameConverterInterface $nameConverter = null, ?ClassMetadataFactoryInterface $classMetadataFactory = null, ?LoggerInterface $logger = null, ?ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, ?ResourceAccessCheckerInterface $resourceAccessChecker = null) { parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $logger ?: new NullLogger(), $resourceMetadataCollectionFactory, $resourceAccessChecker); @@ -90,12 +86,6 @@ public function normalize(mixed $data, ?string $format = null, array $context = return parent::normalize($data, $format, $context); } - if ($this->isCacheKeySafe($context)) { - $context['cache_key'] = $this->getCacheKey($format, $context); - } else { - $context['cache_key'] = false; - } - unset($context['operation_name'], $context['operation']); // Remove operation and operation_name only when cache key has been created $normalizedData = parent::normalize($data, $format, $context); if (!\is_array($normalizedData)) { @@ -161,32 +151,4 @@ protected function setAttributeValue(object $object, string $attribute, mixed $v parent::setAttributeValue($object, $attribute, $value, $format, $context); } - - /** - * Check if any property contains a security grants, which makes the cache key not safe, - * as allowed_properties can differ for 2 instances of the same object. - */ - private function isCacheKeySafe(array $context): bool - { - if (!isset($context['resource_class']) || !$this->resourceClassResolver->isResourceClass($context['resource_class'])) { - return false; - } - $resourceClass = $this->resourceClassResolver->getResourceClass(null, $context['resource_class']); - if (isset($this->safeCacheKeysCache[$resourceClass])) { - return $this->safeCacheKeysCache[$resourceClass]; - } - $options = $this->getFactoryOptions($context); - $propertyNames = $this->propertyNameCollectionFactory->create($resourceClass, $options); - - $this->safeCacheKeysCache[$resourceClass] = true; - foreach ($propertyNames as $propertyName) { - $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName, $options); - if (null !== $propertyMetadata->getSecurity()) { - $this->safeCacheKeysCache[$resourceClass] = false; - break; - } - } - - return $this->safeCacheKeysCache[$resourceClass]; - } } diff --git a/src/Serializer/AbstractItemNormalizer.php b/src/Serializer/AbstractItemNormalizer.php index 59278353949..934f2a84553 100644 --- a/src/Serializer/AbstractItemNormalizer.php +++ b/src/Serializer/AbstractItemNormalizer.php @@ -60,6 +60,7 @@ */ abstract class AbstractItemNormalizer extends AbstractObjectNormalizer { + use CacheKeyTrait; use ClassInfoTrait; use CloneTrait; use ContextTrait; @@ -76,6 +77,8 @@ abstract class AbstractItemNormalizer extends AbstractObjectNormalizer protected array $localFactoryOptionsCache = []; protected ?ResourceAccessCheckerInterface $resourceAccessChecker; + private array $safeCacheKeysCache = []; + public function __construct(protected PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, protected PropertyMetadataFactoryInterface $propertyMetadataFactory, protected IriConverterInterface $iriConverter, protected ResourceClassResolverInterface $resourceClassResolver, ?PropertyAccessorInterface $propertyAccessor = null, ?NameConverterInterface $nameConverter = null, ?ClassMetadataFactoryInterface $classMetadataFactory = null, array $defaultContext = [], ?ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, ?ResourceAccessCheckerInterface $resourceAccessChecker = null, protected ?TagCollectorInterface $tagCollector = null, protected ?OperationResourceClassResolverInterface $operationResourceResolver = null) { if (!isset($defaultContext['circular_reference_handler'])) { @@ -188,6 +191,12 @@ public function normalize(mixed $data, ?string $format = null, array $context = $context['resources'][$iri] = $iri; } + if ($this->isCacheKeySafe($context)) { + $context['cache_key'] = $this->getCacheKey($format, $context); + } else { + $context['cache_key'] = false; + } + $context['object'] = $data; $context['format'] = $format; @@ -536,6 +545,34 @@ protected function canAccessAttributePostDenormalize(?object $object, ?object $p return true; } + /** + * Check if any property contains a security grants, which makes the cache key not safe, + * as allowed_properties can differ for 2 instances of the same object. + */ + private function isCacheKeySafe(array $context): bool + { + if (!isset($context['resource_class']) || !$this->resourceClassResolver->isResourceClass($context['resource_class'])) { + return false; + } + $resourceClass = $this->resourceClassResolver->getResourceClass(null, $context['resource_class']); + if (isset($this->safeCacheKeysCache[$resourceClass])) { + return $this->safeCacheKeysCache[$resourceClass]; + } + $options = $this->getFactoryOptions($context); + $propertyNames = $this->propertyNameCollectionFactory->create($resourceClass, $options); + + $this->safeCacheKeysCache[$resourceClass] = true; + foreach ($propertyNames as $propertyName) { + $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName, $options); + if (null !== $propertyMetadata->getSecurity()) { + $this->safeCacheKeysCache[$resourceClass] = false; + break; + } + } + + return $this->safeCacheKeysCache[$resourceClass]; + } + /** * {@inheritdoc} */