Skip to content

Commit 1f75b19

Browse files
committed
feat(doctrine): improve http cache invalidation using the info from the mapping
1 parent 02a7649 commit 1f75b19

File tree

2 files changed

+64
-18
lines changed

2 files changed

+64
-18
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<argument type="service" id="api_platform.iri_converter" />
1414
<argument type="service" id="api_platform.resource_class_resolver" />
1515
<argument type="service" id="api_platform.property_accessor" />
16+
<argument type="service" id="object_mapper" on-invalid="null" />
1617
<tag name="doctrine.event_listener" event="preUpdate" />
1718
<tag name="doctrine.event_listener" event="onFlush" />
1819
<tag name="doctrine.event_listener" event="postFlush" />

src/Symfony/Doctrine/EventListener/PurgeHttpCacheListener.php

Lines changed: 63 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
use ApiPlatform\HttpCache\PurgerInterface;
1717
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
1818
use ApiPlatform\Metadata\Exception\OperationNotFoundException;
19-
use ApiPlatform\Metadata\Exception\RuntimeException;
2019
use ApiPlatform\Metadata\GetCollection;
2120
use ApiPlatform\Metadata\IriConverterInterface;
2221
use ApiPlatform\Metadata\ResourceClassResolverInterface;
@@ -27,6 +26,8 @@
2726
use Doctrine\ORM\Event\PreUpdateEventArgs;
2827
use Doctrine\ORM\Mapping\AssociationMapping;
2928
use Doctrine\ORM\PersistentCollection;
29+
use Symfony\Component\ObjectMapper\Attribute\Map;
30+
use Symfony\Component\ObjectMapper\ObjectMapperInterface;
3031
use Symfony\Component\PropertyAccess\PropertyAccess;
3132
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
3233

@@ -41,7 +42,11 @@ final class PurgeHttpCacheListener
4142
private readonly PropertyAccessorInterface $propertyAccessor;
4243
private array $tags = [];
4344

44-
public function __construct(private readonly PurgerInterface $purger, private readonly IriConverterInterface $iriConverter, private readonly ResourceClassResolverInterface $resourceClassResolver, ?PropertyAccessorInterface $propertyAccessor = null)
45+
public function __construct(private readonly PurgerInterface $purger,
46+
private readonly IriConverterInterface $iriConverter,
47+
private readonly ResourceClassResolverInterface $resourceClassResolver,
48+
?PropertyAccessorInterface $propertyAccessor = null,
49+
private readonly ?ObjectMapperInterface $objectMapper=null)
4550
{
4651
$this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor();
4752
}
@@ -110,36 +115,48 @@ public function postFlush(): void
110115

111116
private function gatherResourceAndItemTags(object $entity, bool $purgeItem): void
112117
{
113-
try {
114-
$iri = $this->iriConverter->getIriFromResource($entity, UrlGeneratorInterface::ABS_PATH, new GetCollection());
115-
$this->tags[$iri] = $iri;
118+
$resources = $this->getResourcesForEntity($entity);
116119

117-
if ($purgeItem) {
118-
$this->addTagForItem($entity);
120+
foreach($resources as $resource) {
121+
try {
122+
$iri = $this->iriConverter->getIriFromResource($resource, UrlGeneratorInterface::ABS_PATH, new GetCollection());
123+
$this->tags[$iri] = $iri;
124+
125+
if ($purgeItem) {
126+
$this->addTagForItem($entity);
127+
}
128+
} catch (OperationNotFoundException|InvalidArgumentException) {
119129
}
120-
} catch (OperationNotFoundException|InvalidArgumentException) {
121130
}
122131
}
123132

124133
private function gatherRelationTags(EntityManagerInterface $em, object $entity): void
125134
{
126135
$associationMappings = $em->getClassMetadata($entity::class)->getAssociationMappings();
136+
127137
/** @var array|AssociationMapping $associationMapping according to the version of doctrine orm */
128138
foreach ($associationMappings as $property => $associationMapping) {
129139
if ($associationMapping instanceof AssociationMapping && ($associationMapping->targetEntity ?? null) && !$this->resourceClassResolver->isResourceClass($associationMapping->targetEntity)) {
130140
return;
131141
}
142+
if (!$this->propertyAccessor->isReadable($entity, $property)) {
143+
return;
144+
}
132145

133146
if (
134147
\is_array($associationMapping)
135148
&& \array_key_exists('targetEntity', $associationMapping)
136-
&& !$this->resourceClassResolver->isResourceClass($associationMapping['targetEntity'])) {
149+
&& !$this->resourceClassResolver->isResourceClass($associationMapping['targetEntity'])
150+
&& (
151+
!$this->objectMapper ||
152+
!(new \ReflectionClass($associationMapping['targetEntity']))->getAttributes(Map::class)
153+
)
154+
) {
137155
return;
138156
}
139157

140-
if ($this->propertyAccessor->isReadable($entity, $property)) {
141-
$this->addTagsFor($this->propertyAccessor->getValue($entity, $property));
142-
}
158+
$this->addTagsFor($this->propertyAccessor->getValue($entity, $property));
159+
143160
}
144161
}
145162

@@ -166,14 +183,42 @@ private function addTagsFor(mixed $value): void
166183

167184
private function addTagForItem(mixed $value): void
168185
{
169-
if (!$this->resourceClassResolver->isResourceClass($this->getObjectClass($value))) {
170-
return;
186+
$resources = $this->getResourcesForEntity($value);
187+
188+
foreach($resources as $resource) {
189+
try {
190+
$iri = $this->iriConverter->getIriFromResource($resource);
191+
$this->tags[$iri] = $iri;
192+
} catch (OperationNotFoundException|InvalidArgumentException) {
193+
}
171194
}
195+
}
172196

173-
try {
174-
$iri = $this->iriConverter->getIriFromResource($value);
175-
$this->tags[$iri] = $iri;
176-
} catch (RuntimeException|InvalidArgumentException) {
197+
private function getResourcesForEntity(object $entity): array
198+
{
199+
$resources = [];
200+
201+
if (!$this->resourceClassResolver->isResourceClass(($class= $this->getObjectClass($entity)))) {
202+
//is the entity mapped to resource(s)?
203+
if (!$this->objectMapper) {
204+
return [];
205+
}
206+
207+
$mapAttributes = (new \ReflectionClass($class))->getAttributes(Map::class);
208+
209+
if (!$mapAttributes) {
210+
return [];
211+
}
212+
213+
//loop over all mappings to fetch all resources mapped to this entity
214+
foreach($mapAttributes as $mapAttribute) {
215+
$resources[] = $this->objectMapper->map($entity, $mapAttribute->newInstance()->target);;
216+
}
177217
}
218+
else{
219+
$resources[] = $entity;
220+
}
221+
222+
return $resources;
178223
}
179224
}

0 commit comments

Comments
 (0)