Skip to content

Commit 7880953

Browse files
JakovKnezoviccsoyuka
authored andcommitted
fix(httpcache): generating iri cache tag for collection operation with path parameter
1 parent fd010ea commit 7880953

File tree

3 files changed

+69
-21
lines changed

3 files changed

+69
-21
lines changed

src/Symfony/Doctrine/EventListener/PurgeHttpCacheListener.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,7 @@ public function postFlush(): void
111111
private function gatherResourceAndItemTags(object $entity, bool $purgeItem): void
112112
{
113113
try {
114-
$resourceClass = $this->resourceClassResolver->getResourceClass($entity);
115-
$iri = $this->iriConverter->getIriFromResource($resourceClass, UrlGeneratorInterface::ABS_PATH, new GetCollection());
114+
$iri = $this->iriConverter->getIriFromResource($entity, UrlGeneratorInterface::ABS_PATH, new GetCollection());
116115
$this->tags[$iri] = $iri;
117116

118117
if ($purgeItem) {

src/Symfony/Routing/IriConverter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ public function getIriFromResource(object|string $resource, int $referenceType =
121121
$operation = $this->operationMetadataFactory->create($context['item_uri_template']);
122122
}
123123

124-
$localOperationCacheKey = ($operation?->getName() ?? '').$resourceClass.(\is_string($resource) ? '_c' : '_i');
124+
$localOperationCacheKey = ($operation?->getName() ?? '').$resourceClass.((\is_string($resource) || $operation instanceof CollectionOperationInterface) ? '_c' : '_i');
125125
if ($operation && isset($this->localOperationCache[$localOperationCacheKey])) {
126126
return $this->generateSymfonyRoute($resource, $referenceType, $this->localOperationCache[$localOperationCacheKey], $context, $this->localIdentifiersExtractorOperationCache[$localOperationCacheKey] ?? null);
127127
}

src/Symfony/Tests/Doctrine/EventListener/PurgeHttpCacheListenerTest.php

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -65,19 +65,16 @@ public function testOnFlush(): void
6565
$purgerProphecy->purge(['/dummies', '/dummies/1', '/dummies/2', '/dummies/3', '/dummies/4'])->shouldBeCalled();
6666

6767
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
68-
$iriConverterProphecy->getIriFromResource(Dummy::class, UrlGeneratorInterface::ABS_PATH, new GetCollection())->willReturn('/dummies')->shouldBeCalled();
69-
$iriConverterProphecy->getIriFromResource(DummyNoGetOperation::class, UrlGeneratorInterface::ABS_PATH, new GetCollection())->willThrow(new InvalidArgumentException())->shouldBeCalled();
68+
$iriConverterProphecy->getIriFromResource(Argument::type(Dummy::class), UrlGeneratorInterface::ABS_PATH, new GetCollection())->willReturn('/dummies')->shouldBeCalled();
7069
$iriConverterProphecy->getIriFromResource($toUpdate1)->willReturn('/dummies/1')->shouldBeCalled();
7170
$iriConverterProphecy->getIriFromResource($toUpdate2)->willReturn('/dummies/2')->shouldBeCalled();
7271
$iriConverterProphecy->getIriFromResource($toDelete1)->willReturn('/dummies/3')->shouldBeCalled();
7372
$iriConverterProphecy->getIriFromResource($toDelete2)->willReturn('/dummies/4')->shouldBeCalled();
74-
$iriConverterProphecy->getIriFromResource($toDeleteNoPurge)->shouldNotBeCalled();
73+
$iriConverterProphecy->getIriFromResource(Argument::type(DummyNoGetOperation::class), UrlGeneratorInterface::ABS_PATH, new GetCollection())->willThrow(new InvalidArgumentException())->shouldBeCalled();
7574
$iriConverterProphecy->getIriFromResource(Argument::any())->willThrow(new ItemNotFoundException());
7675

7776
$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
7877
$resourceClassResolverProphecy->isResourceClass(Argument::type('string'))->willReturn(true)->shouldBeCalled();
79-
$resourceClassResolverProphecy->getResourceClass(Argument::type(Dummy::class))->willReturn(Dummy::class)->shouldBeCalled();
80-
$resourceClassResolverProphecy->getResourceClass(Argument::type(DummyNoGetOperation::class))->willReturn(DummyNoGetOperation::class)->shouldBeCalled();
8178

8279
$uowProphecy = $this->prophesize(UnitOfWork::class);
8380
$uowProphecy->getScheduledEntityInsertions()->willReturn([$toInsert1, $toInsert2])->shouldBeCalled();
@@ -105,6 +102,9 @@ public function testOnFlush(): void
105102
$listener = new PurgeHttpCacheListener($purgerProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal());
106103
$listener->onFlush($eventArgs);
107104
$listener->postFlush();
105+
106+
$iriConverterProphecy->getIriFromResource(Argument::type(Dummy::class), UrlGeneratorInterface::ABS_PATH, new GetCollection())->shouldHaveBeenCalled();
107+
$iriConverterProphecy->getIriFromResource(Argument::type(DummyNoGetOperation::class), UrlGeneratorInterface::ABS_PATH, new GetCollection())->shouldHaveBeenCalled();
108108
}
109109

110110
public function testPreUpdate(): void
@@ -122,14 +122,13 @@ public function testPreUpdate(): void
122122
$purgerProphecy->purge(['/dummies', '/dummies/1', '/related_dummies/old', '/related_dummies/new'])->shouldBeCalled();
123123

124124
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
125-
$iriConverterProphecy->getIriFromResource(Dummy::class, UrlGeneratorInterface::ABS_PATH, new GetCollection())->willReturn('/dummies')->shouldBeCalled();
125+
$iriConverterProphecy->getIriFromResource(Argument::type(Dummy::class), UrlGeneratorInterface::ABS_PATH, new GetCollection())->willReturn('/dummies')->shouldBeCalled();
126126
$iriConverterProphecy->getIriFromResource($dummy)->willReturn('/dummies/1')->shouldBeCalled();
127127
$iriConverterProphecy->getIriFromResource($oldRelatedDummy)->willReturn('/related_dummies/old')->shouldBeCalled();
128128
$iriConverterProphecy->getIriFromResource($newRelatedDummy)->willReturn('/related_dummies/new')->shouldBeCalled();
129129

130130
$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
131131
$resourceClassResolverProphecy->isResourceClass(Argument::type('string'))->willReturn(true)->shouldBeCalled();
132-
$resourceClassResolverProphecy->getResourceClass(Argument::type(Dummy::class))->willReturn(Dummy::class)->shouldBeCalled();
133132

134133
$emProphecy = $this->prophesize(EntityManagerInterface::class);
135134

@@ -154,11 +153,11 @@ public function testNothingToPurge(): void
154153
$purgerProphecy->purge([])->shouldNotBeCalled();
155154

156155
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
157-
$iriConverterProphecy->getIriFromResource(DummyNoGetOperation::class, UrlGeneratorInterface::ABS_PATH, new GetCollection())->willThrow(new InvalidArgumentException())->shouldBeCalled();
156+
$iriConverterProphecy->getIriFromResource(Argument::type(DummyNoGetOperation::class), UrlGeneratorInterface::ABS_PATH, new GetCollection())->willThrow(new InvalidArgumentException())->shouldBeCalled();
158157
$iriConverterProphecy->getIriFromResource($dummyNoGetOperation)->shouldNotBeCalled();
159158

160159
$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
161-
$resourceClassResolverProphecy->getResourceClass(Argument::type(DummyNoGetOperation::class))->willReturn(DummyNoGetOperation::class)->shouldBeCalled();
160+
$resourceClassResolverProphecy->getResourceClass(Argument::type(DummyNoGetOperation::class))->willReturn(DummyNoGetOperation::class)->shouldNotBeCalled();
162161

163162
$emProphecy = $this->prophesize(EntityManagerInterface::class);
164163

@@ -176,18 +175,20 @@ public function testNothingToPurge(): void
176175
public function testNotAResourceClass(): void
177176
{
178177
$containNonResource = new ContainNonResource();
179-
$nonResource = new NotAResource('foo', 'bar');
178+
$nonResource1 = new NotAResource('foo', 'bar');
179+
$nonResource2 = new NotAResource('baz', 'qux');
180+
$collectionOfNotAResource = [$nonResource1, $nonResource2];
180181

181182
$purgerProphecy = $this->prophesize(PurgerInterface::class);
182-
$purgerProphecy->purge([])->shouldNotBeCalled();
183+
$purgerProphecy->purge(['/dummies'])->shouldBeCalled();
183184

184185
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
185-
$iriConverterProphecy->getIriFromResource(ContainNonResource::class, UrlGeneratorInterface::ABS_PATH, Argument::any())->willReturn('/dummies/1');
186-
$iriConverterProphecy->getIriFromResource($nonResource)->shouldNotBeCalled();
186+
$iriConverterProphecy->getIriFromResource(Argument::type(ContainNonResource::class), UrlGeneratorInterface::ABS_PATH, new GetCollection())->willReturn('/dummies')->shouldBeCalled();
187+
$iriConverterProphecy->getIriFromResource($nonResource1)->willThrow(new InvalidArgumentException())->shouldBeCalled();
188+
$iriConverterProphecy->getIriFromResource($nonResource2)->willThrow(new InvalidArgumentException())->shouldBeCalled();
187189

188190
$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
189-
$resourceClassResolverProphecy->getResourceClass(Argument::type(ContainNonResource::class))->willReturn(ContainNonResource::class)->shouldBeCalled();
190-
$resourceClassResolverProphecy->isResourceClass(NotAResource::class)->willReturn(false)->shouldBeCalled();
191+
$resourceClassResolverProphecy->isResourceClass(Argument::type('string'))->willReturn(true)->shouldBeCalled();
191192

192193
$uowProphecy = $this->prophesize(UnitOfWork::class);
193194
$uowProphecy->getScheduledEntityInsertions()->willReturn([$containNonResource])->shouldBeCalled();
@@ -208,11 +209,59 @@ public function testNotAResourceClass(): void
208209

209210
$propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
210211
$propertyAccessorProphecy->isReadable(Argument::type(ContainNonResource::class), 'notAResource')->willReturn(true);
211-
$propertyAccessorProphecy->isReadable(Argument::type(ContainNonResource::class), 'collectionOfNotAResource')->shouldNotBeCalled();
212-
$propertyAccessorProphecy->getValue(Argument::type(ContainNonResource::class), 'notAResource')->shouldBeCalled()->willReturn($nonResource);
213-
$propertyAccessorProphecy->getValue(Argument::type(ContainNonResource::class), 'collectionOfNotAResource')->shouldNotBeCalled();
212+
$propertyAccessorProphecy->isReadable(Argument::type(ContainNonResource::class), 'collectionOfNotAResource')->willReturn(true);
213+
$propertyAccessorProphecy->getValue(Argument::type(ContainNonResource::class), 'notAResource')->shouldBeCalled()->willReturn($nonResource1);
214+
$propertyAccessorProphecy->getValue(Argument::type(ContainNonResource::class), 'collectionOfNotAResource')->shouldBeCalled()->willReturn($collectionOfNotAResource);
214215

215216
$listener = new PurgeHttpCacheListener($purgerProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal());
216217
$listener->onFlush($eventArgs);
218+
$listener->postFlush();
219+
}
220+
221+
public function testAddTagsForCollection(): void
222+
{
223+
$dummy1 = new Dummy();
224+
$dummy1->setId(1);
225+
$dummy2 = new Dummy();
226+
$dummy2->setId(2);
227+
$collection = [$dummy1, $dummy2];
228+
229+
$purgerProphecy = $this->prophesize(PurgerInterface::class);
230+
$purgerProphecy->purge(['/dummies', '/dummies/1', '/dummies/2'])->shouldBeCalled();
231+
232+
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
233+
$iriConverterProphecy->getIriFromResource(Argument::type(Dummy::class), UrlGeneratorInterface::ABS_PATH, new GetCollection())->willReturn('/dummies')->shouldBeCalled();
234+
$iriConverterProphecy->getIriFromResource($dummy1)->willReturn('/dummies/1')->shouldBeCalled();
235+
$iriConverterProphecy->getIriFromResource($dummy2)->willReturn('/dummies/2')->shouldBeCalled();
236+
237+
$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
238+
$resourceClassResolverProphecy->isResourceClass(Argument::type('string'))->willReturn(true)->shouldBeCalled();
239+
240+
$dummyWithCollection = new Dummy();
241+
$dummyWithCollection->setId(3);
242+
243+
$uowProphecy = $this->prophesize(UnitOfWork::class);
244+
$uowProphecy->getScheduledEntityInsertions()->willReturn([$dummyWithCollection])->shouldBeCalled();
245+
$uowProphecy->getScheduledEntityUpdates()->willReturn([])->shouldBeCalled();
246+
$uowProphecy->getScheduledEntityDeletions()->willReturn([])->shouldBeCalled();
247+
248+
$emProphecy = $this->prophesize(EntityManagerInterface::class);
249+
$emProphecy->getUnitOfWork()->willReturn($uowProphecy->reveal())->shouldBeCalled();
250+
251+
$dummyClassMetadata = new ClassMetadata(Dummy::class);
252+
// @phpstan-ignore-next-line
253+
$dummyClassMetadata->associationMappings = [
254+
'relatedDummies' => ['targetEntity' => Dummy::class],
255+
];
256+
$emProphecy->getClassMetadata(Dummy::class)->willReturn($dummyClassMetadata)->shouldBeCalled();
257+
$eventArgs = new OnFlushEventArgs($emProphecy->reveal());
258+
259+
$propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
260+
$propertyAccessorProphecy->isReadable(Argument::type(Dummy::class), 'relatedDummies')->willReturn(true);
261+
$propertyAccessorProphecy->getValue(Argument::type(Dummy::class), 'relatedDummies')->willReturn($collection)->shouldBeCalled();
262+
263+
$listener = new PurgeHttpCacheListener($purgerProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal());
264+
$listener->onFlush($eventArgs);
265+
$listener->postFlush();
217266
}
218267
}

0 commit comments

Comments
 (0)