Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 0 additions & 38 deletions src/GraphQl/Serializer/ItemNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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];
}
}
37 changes: 37 additions & 0 deletions src/Serializer/AbstractItemNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
*/
abstract class AbstractItemNormalizer extends AbstractObjectNormalizer
{
use CacheKeyTrait;
use ClassInfoTrait;
use CloneTrait;
use ContextTrait;
Expand All @@ -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'])) {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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}
*/
Expand Down
Loading