Skip to content

Commit

Permalink
purge tags for many-to-many relations
Browse files Browse the repository at this point in the history
  • Loading branch information
usu committed Jun 1, 2024
1 parent 221b3a3 commit f315baf
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 5 deletions.
29 changes: 29 additions & 0 deletions api/src/HttpCache/PurgeHttpCacheListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Mapping\AssociationMapping;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\PersistentCollection;
use FOS\HttpCacheBundle\CacheManager;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
Expand Down Expand Up @@ -72,6 +73,7 @@ public function preUpdate(PreUpdateEventArgs $eventArgs): void {
* Collects tags from inserted, updated and deleted entities, including relations.
*/
public function onFlush(OnFlushEventArgs $eventArgs): void {
/** @var EntityManagerInterface */
$em = method_exists($eventArgs, 'getObjectManager') ? $eventArgs->getObjectManager() : $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();

Expand All @@ -92,6 +94,16 @@ public function onFlush(OnFlushEventArgs $eventArgs): void {
$this->gatherResourceTags($em, $originalEntity);
$this->gatherRelationTags($em, $originalEntity);
}

// trigger cache purges for changes on many-to-many relations
// for some reason, changes to Many-To-Many relations are not included in the preUpdate changeSet
foreach ($uow->getScheduledCollectionUpdates() as $collection) {
$this->addTagsForManyToManyRelations($collection, $collection->getInsertDiff());
$this->addTagsForManyToManyRelations($collection, $collection->getDeleteDiff());
}
foreach ($uow->getScheduledCollectionDeletions() as $collection) {
$this->addTagsForManyToManyRelations($collection, $collection->getDeleteDiff());
}
}

/**
Expand All @@ -101,6 +113,23 @@ public function postFlush(): void {
$this->cacheManager->flush();
}

private function addTagsForManyToManyRelations($collection, $entities) {
$associationMapping = $collection->getMapping();

if (ClassMetadataInfo::MANY_TO_MANY !== $associationMapping['type']) {
return;
}

foreach ($entities as $entity) {
$relatedProperty = $associationMapping['isOwningSide'] ? $associationMapping['inversedBy'] : $associationMapping['mappedBy'];
if (!$relatedProperty) {
continue;
}

$this->addTagForItem($entity, $relatedProperty);
}
}

/**
* Computes the original state of the entity based on the current entity and on the changeset.
*/
Expand Down
9 changes: 5 additions & 4 deletions api/tests/Api/Categories/UpdateCategoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -521,19 +521,20 @@ public function testPatchCategoryPurgesCacheTags() {
'short' => 'LP',
'preferredContentTypes' => [
$this->getIriFor('contentTypeColumnLayout'),
$this->getIriFor('contentTypeSafetyConcept'),
$this->getIriFor('contentTypeNotes'),
],
], 'headers' => ['Content-Type' => 'application/merge-patch+json']]);

$this->assertResponseStatusCodeSame(200);

$contentTypeColumnLayout = static::getFixture('contentTypeColumnLayout');
$contentTypeNotes = static::getFixture('contentTypeNotes');
$contentTypeSafetyConcept = static::getFixture('contentTypeSafetyConcept');
self::assertEqualsCanonicalizing([
$category->getId(),
// TODO: fix PurgeHttpCacheListener to include the following tags:
// $contentTypeColumnLayout->getId().'#categories',
// $contentTypeSafetyConcept->getId().'#categories',
$contentTypeColumnLayout->getId().'#categories',
$contentTypeNotes->getId().'#categories',
$contentTypeSafetyConcept->getId().'#categories', // SafetyConcept was previously in the list, so this is purged because it was removed
], $purgedCacheTags);
}
}
2 changes: 1 addition & 1 deletion api/tests/Api/ECampApiTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ protected function &getPurgedCacheTags(): array {
$cacheManager
->method('invalidateTags')
->willReturnCallback(function ($tags) use (&$purgedCacheTags, $cacheManager) {
$purgedCacheTags = array_merge($purgedCacheTags, $tags);
$purgedCacheTags = array_unique(array_merge($purgedCacheTags, $tags));

return $cacheManager;
})
Expand Down
10 changes: 10 additions & 0 deletions api/tests/HttpCache/PurgeHttpCacheListenerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ public function testOnFlush(): void {
$uowMock->method('getScheduledEntityInsertions')->willReturn([$toInsert1, $toInsert2]);
$uowMock->method('getScheduledEntityUpdates')->willReturn([]);
$uowMock->method('getScheduledEntityDeletions')->willReturn([$toDelete1, $toDelete2, $toDeleteNoPurge]);
$uowMock->method('getScheduledCollectionUpdates')->willReturn([]);
$uowMock->method('getScheduledCollectionDeletions')->willReturn([]);
$uowMock->method('getEntityChangeSet')->willReturn([]);

$emProphecy = $this->prophesize(EntityManagerInterface::class);
Expand Down Expand Up @@ -293,6 +295,8 @@ public function testNotAResourceClass(): void {
$uowProphecy->getScheduledEntityInsertions()->willReturn([$containNonResource])->shouldBeCalled();
$uowProphecy->getScheduledEntityDeletions()->willReturn([])->shouldBeCalled();
$uowProphecy->getScheduledEntityUpdates()->willReturn([])->shouldBeCalled();
$uowProphecy->getScheduledCollectionUpdates()->willReturn([])->shouldBeCalled();
$uowProphecy->getScheduledCollectionDeletions()->willReturn([])->shouldBeCalled();

$emProphecy = $this->prophesize(EntityManagerInterface::class);
$emProphecy->getUnitOfWork()->willReturn($uowProphecy->reveal())->shouldBeCalled();
Expand Down Expand Up @@ -327,6 +331,8 @@ public function testInsertingShouldPurgeSubresourceCollections(): void {
$this->uowProphecy->getScheduledEntityInsertions()->willReturn([$toInsert1]);
$this->uowProphecy->getScheduledEntityDeletions()->willReturn([]);
$this->uowProphecy->getScheduledEntityUpdates()->willReturn([])->shouldBeCalled();
$this->uowProphecy->getScheduledCollectionUpdates()->willReturn([]);
$this->uowProphecy->getScheduledCollectionDeletions()->willReturn([]);

// then
$this->cacheManagerProphecy->invalidateTags(['/dummies'])->willReturn($this->cacheManagerProphecy)->shouldBeCalled();
Expand All @@ -350,6 +356,8 @@ public function testDeleteShouldPurgeSubresourceCollections(): void {
$uowMock->method('getScheduledEntityInsertions')->willReturn([]);
$uowMock->method('getScheduledEntityUpdates')->willReturn([]);
$uowMock->method('getScheduledEntityDeletions')->willReturn([$toDelete1]);
$uowMock->method('getScheduledCollectionUpdates')->willReturn([]);
$uowMock->method('getScheduledCollectionDeletions')->willReturn([]);
$uowMock->method('getEntityChangeSet')->willReturn([]);

$this->emProphecy->getUnitOfWork()->willReturn($uowMock)->shouldBeCalled();
Expand Down Expand Up @@ -380,6 +388,8 @@ public function testUpdateShouldPurgeSubresourceCollections(): void {
$uowMock->method('getScheduledEntityInsertions')->willReturn([]);
$uowMock->method('getScheduledEntityUpdates')->willReturn([$toUpdate1]);
$uowMock->method('getScheduledEntityDeletions')->willReturn([]);
$uowMock->method('getScheduledCollectionUpdates')->willReturn([]);
$uowMock->method('getScheduledCollectionDeletions')->willReturn([]);
$uowMock->method('getEntityChangeSet')->willReturn(['relatedDummy' => [$relatedDummyOld, $relatedDummy]]);

$this->emProphecy->getUnitOfWork()->willReturn($uowMock)->shouldBeCalled();
Expand Down

0 comments on commit f315baf

Please sign in to comment.