Skip to content

Commit 02e2af2

Browse files
committed
chore(HttpCache): outsource cache tag collection into normalizer events
1 parent e2e59b0 commit 02e2af2

File tree

12 files changed

+208
-20
lines changed

12 files changed

+208
-20
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\HttpCache\EventListener;
15+
16+
use ApiPlatform\Serializer\NormalizeAttributeEvent;
17+
use ApiPlatform\Serializer\NormalizeItemEvent;
18+
19+
/**
20+
* Collects cache tags during normalization
21+
*
22+
* @author Urban Suppiger <[email protected]>
23+
*/
24+
class NormalizerListener
25+
{
26+
public function onPreNormalizeItem(NormalizeItemEvent $event): void
27+
{
28+
$this->addResourceToContext($event);
29+
}
30+
31+
public function onNormalizeRelation(NormalizeItemEvent $event): void
32+
{
33+
$this->addResourceToContext($event);
34+
}
35+
36+
public function onJsonApiNormalizeRelation(NormalizeItemEvent $event): void
37+
{
38+
$this->addResourceToContext($event);
39+
}
40+
41+
private function addResourceToContext(NormalizeItemEvent $event): void{
42+
if (isset($event->context['resources'])) {
43+
$event->context['resources'][$event->iri] = $event->iri;
44+
}
45+
}
46+
47+
}

src/JsonApi/Serializer/ItemNormalizer.php

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
use ApiPlatform\Serializer\AbstractItemNormalizer;
2626
use ApiPlatform\Serializer\CacheKeyTrait;
2727
use ApiPlatform\Serializer\ContextTrait;
28+
use ApiPlatform\Serializer\NormalizeItemEvent;
2829
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface;
30+
use Symfony\Component\EventDispatcher\EventDispatcher;
2931
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
3032
use Symfony\Component\Serializer\Exception\LogicException;
3133
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
@@ -52,9 +54,9 @@ final class ItemNormalizer extends AbstractItemNormalizer
5254

5355
private array $componentsCache = [];
5456

55-
public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, array $defaultContext = [], ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, ResourceAccessCheckerInterface $resourceAccessChecker = null)
57+
public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, array $defaultContext = [], ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, ResourceAccessCheckerInterface $resourceAccessChecker = null, protected ?EventDispatcher $eventDispatcher = null)
5658
{
57-
parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $defaultContext, $resourceMetadataCollectionFactory, $resourceAccessChecker);
59+
parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $defaultContext, $resourceMetadataCollectionFactory, $resourceAccessChecker, $eventDispatcher);
5860
}
5961

6062
/**
@@ -222,10 +224,6 @@ protected function normalizeRelation(ApiProperty $propertyMetadata, ?object $rel
222224
if (null !== $relatedObject) {
223225
$iri = $this->iriConverter->getIriFromResource($relatedObject);
224226
$context['iri'] = $iri;
225-
226-
if (isset($context['resources'])) {
227-
$context['resources'][$iri] = $iri;
228-
}
229227
}
230228

231229
if (null === $relatedObject || isset($context['api_included'])) {
@@ -241,12 +239,19 @@ protected function normalizeRelation(ApiProperty $propertyMetadata, ?object $rel
241239
return $normalizedRelatedObject;
242240
}
243241

244-
return [
242+
$data = [
245243
'data' => [
246244
'type' => $this->getResourceShortName($resourceClass),
247245
'id' => $iri,
248246
],
249247
];
248+
249+
if ($this->eventDispatcher) {
250+
$event = new NormalizeItemEvent($relatedObject, $format, $context, $iri, $data);
251+
$this->eventDispatcher->dispatch($event, NormalizeItemEvent::JSONAPI_NORMALIZE_RELATION);
252+
}
253+
254+
return $data;
250255
}
251256

252257
/**

src/JsonLd/Serializer/ItemNormalizer.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use ApiPlatform\Serializer\AbstractItemNormalizer;
2727
use ApiPlatform\Serializer\ContextTrait;
2828
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface;
29+
use Symfony\Component\EventDispatcher\EventDispatcher;
2930
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
3031
use Symfony\Component\Serializer\Exception\LogicException;
3132
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
@@ -45,9 +46,9 @@ final class ItemNormalizer extends AbstractItemNormalizer
4546

4647
public const FORMAT = 'jsonld';
4748

48-
public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, private readonly ContextBuilderInterface $contextBuilder, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, array $defaultContext = [], ResourceAccessCheckerInterface $resourceAccessChecker = null)
49+
public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, private readonly ContextBuilderInterface $contextBuilder, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, array $defaultContext = [], ResourceAccessCheckerInterface $resourceAccessChecker = null, protected ?EventDispatcher $eventDispatcher = null)
4950
{
50-
parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $defaultContext, $resourceMetadataCollectionFactory, $resourceAccessChecker);
51+
parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $defaultContext, $resourceMetadataCollectionFactory, $resourceAccessChecker, $eventDispatcher);
5152
}
5253

5354
/**

src/Serializer/AbstractItemNormalizer.php

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use ApiPlatform\Metadata\Util\ClassInfoTrait;
2828
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface;
2929
use ApiPlatform\Util\CloneTrait;
30+
use Symfony\Component\EventDispatcher\EventDispatcher;
3031
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
3132
use Symfony\Component\PropertyAccess\PropertyAccess;
3233
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
@@ -61,7 +62,7 @@ abstract class AbstractItemNormalizer extends AbstractObjectNormalizer
6162
protected array $localCache = [];
6263
protected array $localFactoryOptionsCache = [];
6364

64-
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, protected ?ResourceAccessCheckerInterface $resourceAccessChecker = null)
65+
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, protected ?ResourceAccessCheckerInterface $resourceAccessChecker = null, protected ?EventDispatcher $eventDispatcher = null)
6566
{
6667
if (!isset($defaultContext['circular_reference_handler'])) {
6768
$defaultContext['circular_reference_handler'] = fn ($object): ?string => $this->iriConverter->getIriFromResource($object);
@@ -159,13 +160,19 @@ public function normalize(mixed $object, string $format = null, array $context =
159160
*/
160161
$emptyResourceAsIri = $context['api_empty_resource_as_iri'] ?? false;
161162
unset($context['api_empty_resource_as_iri']);
162-
163-
if (isset($context['resources'])) {
164-
$context['resources'][$iri] = $iri;
163+
164+
if ($this->eventDispatcher) {
165+
$event = new NormalizeItemEvent($object, $format, $context, $iri, null);
166+
$this->eventDispatcher->dispatch($event, NormalizeItemEvent::NORMALIZE_ITEM_PRE);
165167
}
166168

167169
$data = parent::normalize($object, $format, $context);
168170

171+
if ($this->eventDispatcher) {
172+
$event = new NormalizeItemEvent($object, $format, $context, $iri, $data);
173+
$this->eventDispatcher->dispatch($event, NormalizeItemEvent::NORMALIZE_ITEM_POST);
174+
}
175+
169176
if ($emptyResourceAsIri && \is_array($data) && 0 === \count($data)) {
170177
return $iri;
171178
}
@@ -635,7 +642,14 @@ protected function getAttributeValue(object $object, string $attribute, string $
635642
$resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className);
636643
$childContext = $this->createChildContext($this->createOperationContext($context, $resourceClass), $attribute, $format);
637644

638-
return $this->normalizeCollectionOfRelations($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext);
645+
$data = $this->normalizeCollectionOfRelations($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext);
646+
647+
if ($this->eventDispatcher) {
648+
$event = new NormalizeAttributeEvent($object, $format, $context, $context['iri'], $data, $attribute, $propertyMetadata, $type, $childContext);
649+
$this->eventDispatcher->dispatch($event, NormalizeAttributeEvent::NORMALIZE_ATTRIBUTE);
650+
}
651+
652+
return $data;
639653
}
640654

641655
if (
@@ -650,7 +664,13 @@ protected function getAttributeValue(object $object, string $attribute, string $
650664
$resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className);
651665
$childContext = $this->createChildContext($this->createOperationContext($context, $resourceClass), $attribute, $format);
652666

653-
return $this->normalizeRelation($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext);
667+
$data = $this->normalizeRelation($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext);
668+
if ($this->eventDispatcher) {
669+
$event = new NormalizeAttributeEvent($object, $format, $context, $context['iri'], $data, $attribute, $propertyMetadata, $type, $childContext);
670+
$this->eventDispatcher->dispatch($event, NormalizeAttributeEvent::NORMALIZE_ATTRIBUTE);
671+
}
672+
673+
return $data;
654674
}
655675

656676
if (!$this->serializer instanceof NormalizerInterface) {
@@ -728,8 +748,9 @@ protected function normalizeRelation(ApiProperty $propertyMetadata, ?object $rel
728748

729749
$iri = $this->iriConverter->getIriFromResource($relatedObject);
730750

731-
if (isset($context['resources'])) {
732-
$context['resources'][$iri] = $iri;
751+
if ($this->eventDispatcher) {
752+
$event = new NormalizeItemEvent($relatedObject, $format, $context, $iri, $iri);
753+
$this->eventDispatcher->dispatch($event, NormalizeItemEvent::NORMALIZE_RELATION);
733754
}
734755

735756
$push = $propertyMetadata->getPush() ?? false;

src/Serializer/ItemNormalizer.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface;
2525
use Psr\Log\LoggerInterface;
2626
use Psr\Log\NullLogger;
27+
use Symfony\Component\EventDispatcher\EventDispatcher;
2728
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
2829
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
2930
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
@@ -38,9 +39,9 @@ class ItemNormalizer extends AbstractItemNormalizer
3839
{
3940
private readonly LoggerInterface $logger;
4041

41-
public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, LoggerInterface $logger = null, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null, ResourceAccessCheckerInterface $resourceAccessChecker = null, array $defaultContext = [])
42+
public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, LoggerInterface $logger = null, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null, ResourceAccessCheckerInterface $resourceAccessChecker = null, array $defaultContext = [], protected ?EventDispatcher $eventDispatcher = null)
4243
{
43-
parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $defaultContext, $resourceMetadataFactory, $resourceAccessChecker);
44+
parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $defaultContext, $resourceMetadataFactory, $resourceAccessChecker, $eventDispatcher);
4445

4546
$this->logger = $logger ?: new NullLogger();
4647
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Serializer;
15+
16+
use ApiPlatform\Metadata\ApiProperty;
17+
use Symfony\Component\PropertyInfo\Type;
18+
use Symfony\Contracts\EventDispatcher\Event;
19+
20+
/**
21+
* Event class for normalizer events (normalize attributes)
22+
*
23+
* @author Urban Suppiger <[email protected]>
24+
*/
25+
class NormalizeAttributeEvent extends Event
26+
{
27+
public const NORMALIZE_ATTRIBUTE = 'api_platform.normalizer.normalize_attribute';
28+
29+
public function __construct(
30+
public mixed $object,
31+
public ?string $format = null,
32+
public array $context = [],
33+
public ?string $iri = null,
34+
public array|string|int|float|bool|\ArrayObject|null $data = null,
35+
public string $attribute,
36+
public ?ApiProperty $propertyMetadata = null,
37+
public ?Type $type = null,
38+
public array $childContext = [],
39+
) {
40+
}
41+
}

src/Serializer/NormalizeItemEvent.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Serializer;
15+
16+
use Symfony\Contracts\EventDispatcher\Event;
17+
18+
/**
19+
* Event class for normalizer events (normalize items)
20+
*
21+
* @author Urban Suppiger <[email protected]>
22+
*/
23+
class NormalizeItemEvent extends Event
24+
{
25+
public const NORMALIZE_ITEM_PRE = 'api_platform.normalizer.normalize_item.pre';
26+
public const NORMALIZE_ITEM_POST = 'api_platform.normalizer.normalize_item.post';
27+
public const NORMALIZE_RELATION = 'api_platform.normalizer.normalize_relation';
28+
public const JSONAPI_NORMALIZE_RELATION = 'api_platform.jsonapi.normalizer.normalize_relation';
29+
30+
public function __construct(
31+
public mixed $object,
32+
public ?string $format = null,
33+
public array $context = [],
34+
public ?string $iri = null,
35+
public array|string|int|float|bool|\ArrayObject|null $data = null
36+
) {
37+
}
38+
}

src/Symfony/Bundle/Resources/config/api.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
<argument>null</argument>
5858
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" on-invalid="ignore" />
5959
<argument type="service" id="api_platform.security.resource_access_checker" on-invalid="ignore" />
60+
<argument type="collection" />
61+
<argument type="service" id="event_dispatcher" on-invalid="ignore" />
6062

6163
<!-- Run before serializer.normalizer.json_serializable -->
6264
<tag name="serializer.normalizer" priority="-895" />

src/Symfony/Bundle/Resources/config/http_cache_purger.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,12 @@
2525
<argument type="service" id="api_platform.http_cache.purger" on-invalid="null" />
2626
<tag name="kernel.event_listener" event="kernel.response" method="onKernelResponse" priority="-2" />
2727
</service>
28+
29+
<service id="api_platform.http_cache.listener.normalizer.collect_tags" class="ApiPlatform\HttpCache\EventListener\NormalizerListener">
30+
<tag name="kernel.event_listener" event="api_platform.normalizer.normalize_item.pre" method="onPreNormalizeItem" />
31+
<tag name="kernel.event_listener" event="api_platform.normalizer.normalize_relation" method="onNormalizeRelation" />
32+
<tag name="kernel.event_listener" event="api_platform.jsonapi.normalizer.normalize_relation" method="onJsonApiNormalizeAttribute" />
33+
</service>
34+
2835
</services>
2936
</container>

src/Symfony/Bundle/Resources/config/jsonapi.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<argument type="collection" />
4444
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
4545
<argument type="service" id="api_platform.security.resource_access_checker" on-invalid="ignore" />
46+
<argument type="service" id="event_dispatcher" on-invalid="ignore" />
4647

4748
<!-- Run before serializer.normalizer.json_serializable -->
4849
<tag name="serializer.normalizer" priority="-890" />

src/Symfony/Bundle/Resources/config/jsonld.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<argument type="service" id="serializer.mapping.class_metadata_factory" on-invalid="ignore" />
3030
<argument type="collection" />
3131
<argument type="service" id="api_platform.security.resource_access_checker" on-invalid="ignore" />
32+
<argument type="service" id="event_dispatcher" on-invalid="ignore" />
3233

3334
<!-- Run before serializer.normalizer.json_serializable -->
3435
<tag name="serializer.normalizer" priority="-890" />

0 commit comments

Comments
 (0)