Skip to content

Commit cfd6107

Browse files
committed
Invalidate dependent elements
1 parent dd9dae6 commit cfd6107

File tree

7 files changed

+285
-16
lines changed

7 files changed

+285
-16
lines changed

config/services.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@
9191

9292
$services->set('neusta_pimcore_http_cache.element.invalidate_listener', InvalidateElementListener::class)
9393
->arg('$cacheInvalidator', service('neusta_pimcore_http_cache.cache_invalidator'))
94-
->arg('$dispatcher', service('event_dispatcher'));
94+
->arg('$dispatcher', service('event_dispatcher'))
95+
->arg('$elementRepository', service('.neusta_pimcore_http_cache.element.repository'));
9596

9697
$services->set('neusta_pimcore_http_cache.data_collector', DataCollector::class)
9798
->arg('$cacheTagCollector', service('.neusta_pimcore_http_cache.collect_tags_response_tagger'))

src/Element/InvalidateElementListener.php

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use Neusta\Pimcore\HttpCacheBundle\Cache\CacheInvalidator;
66
use Pimcore\Event\Model\ElementEventInterface;
7-
use Pimcore\Model\DataObject\Concrete;
7+
use Pimcore\Model\Dependency;
88
use Pimcore\Model\Element\ElementInterface;
99
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
1010

@@ -13,6 +13,7 @@ final class InvalidateElementListener
1313
public function __construct(
1414
private readonly CacheInvalidator $cacheInvalidator,
1515
private readonly EventDispatcherInterface $dispatcher,
16+
private readonly ElementRepository $elementRepository,
1617
) {
1718
}
1819

@@ -25,15 +26,16 @@ public function onUpdate(ElementEventInterface $event): void
2526
$element = $event->getElement();
2627

2728
$this->invalidateElement($element);
28-
$this->invalidateDependencies($element);
29+
30+
$this->invalidateDependencies($element->getDependencies());
2931
}
3032

3133
public function onDelete(ElementEventInterface $event): void
3234
{
3335
$element = $event->getElement();
3436

3537
$this->invalidateElement($element);
36-
$this->invalidateDependencies($element);
38+
$this->invalidateDependencies($element->getDependencies());
3739
}
3840

3941
private function invalidateElement(ElementInterface $element): void
@@ -48,15 +50,23 @@ private function invalidateElement(ElementInterface $element): void
4850
$this->cacheInvalidator->invalidate($invalidationEvent->cacheTags());
4951
}
5052

51-
private function invalidateDependencies(ElementInterface $element): void
53+
private function invalidateDependencies(Dependency $dependency): void
5254
{
53-
if (!$element instanceof Concrete) {
54-
return;
55-
}
55+
$requiredBy = $dependency->getRequiredBy();
56+
foreach ($requiredBy as $required) {
57+
if (!isset($required['id'], $required['type'])) {
58+
continue;
59+
}
60+
61+
$element = match (ElementType::tryFrom($required['type'])) {
62+
ElementType::Object => $this->elementRepository->findObject($required['id']),
63+
ElementType::Document => $this->elementRepository->findDocument($required['id']),
64+
ElementType::Asset => $this->elementRepository->findAsset($required['id']),
65+
default => null,
66+
};
5667

57-
foreach ($element->getDependencies()->getRequiredBy() as $dependency) {
58-
if ($dependency instanceof ElementInterface) {
59-
$this->invalidateElement($dependency);
68+
if ($element) {
69+
$this->invalidateElement($element);
6070
}
6171
}
6272
}

tests/Integration/Helpers/TestObjectFactory.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Pimcore\Model\DataObject;
66
use Pimcore\Model\DataObject\AbstractObject;
77
use Pimcore\Model\DataObject\TestDataObject;
8+
use Pimcore\Model\DataObject\TestObject;
89

910
final class TestObjectFactory
1011
{
@@ -20,6 +21,18 @@ public static function simpleObject(): TestDataObject
2021
return $object;
2122
}
2223

24+
public static function testObject(int $id): TestObject
25+
{
26+
$object = new TestObject();
27+
$object->setId($id);
28+
$object->setKey('test_object_1' . $id);
29+
$object->setContent('Test content');
30+
$object->setPublished(true);
31+
$object->setParentId(1);
32+
33+
return $object;
34+
}
35+
2336
public static function simpleVariant(): TestDataObject
2437
{
2538
$object = new TestDataObject();

tests/Integration/Invalidation/InvalidateObjectTest.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,32 @@ public function response_is_invalidated_when_object_is_updated(): void
5555
$this->cacheManager->invalidateTags(['o42'])->shouldHaveBeenCalledTimes(1);
5656
}
5757

58+
/**
59+
* @test
60+
*/
61+
#[ConfigureExtension('neusta_pimcore_http_cache', [
62+
'elements' => [
63+
'objects' => [
64+
'enabled' => true,
65+
'types' => [
66+
'variant' => true,
67+
],
68+
],
69+
],
70+
])]
71+
public function dependent_element_is_invalidated_on_update(): void
72+
{
73+
$object1 = self::arrange(fn () => TestObjectFactory::testObject(12)->save());
74+
$object2 = self::arrange(fn () => TestObjectFactory::testObject(24)->save());
75+
76+
self::arrange(fn () => $object1->setRelated([$object2])->save());
77+
78+
$object2->setContent('Updated content')->save();
79+
80+
$this->cacheManager->invalidateTags(['o12'])->shouldHaveBeenCalledTimes(1);
81+
$this->cacheManager->invalidateTags(['o24'])->shouldHaveBeenCalledTimes(1);
82+
}
83+
5884
/**
5985
* @test
6086
*/

tests/Unit/Element/InvalidateElementListenerTest.php

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTag;
77
use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTags;
88
use Neusta\Pimcore\HttpCacheBundle\Element\ElementInvalidationEvent;
9+
use Neusta\Pimcore\HttpCacheBundle\Element\ElementRepository;
910
use Neusta\Pimcore\HttpCacheBundle\Element\InvalidateElementListener;
1011
use PHPUnit\Framework\TestCase;
1112
use Pimcore\Event\Model\AssetEvent;
@@ -33,13 +34,18 @@ final class InvalidateElementListenerTest extends TestCase
3334
/** @var ObjectProphecy<EventDispatcherInterface> */
3435
private $eventDispatcher;
3536

37+
/** @var ObjectProphecy<ElementRepository> */
38+
private $elementRepository;
39+
3640
protected function setUp(): void
3741
{
3842
$this->cacheInvalidator = $this->prophesize(CacheInvalidator::class);
3943
$this->eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
44+
$this->elementRepository = $this->prophesize(ElementRepository::class);
4045
$this->invalidateElementListener = new InvalidateElementListener(
4146
$this->cacheInvalidator->reveal(),
4247
$this->eventDispatcher->reveal(),
48+
$this->elementRepository->reveal(),
4349
);
4450

4551
$this->eventDispatcher->dispatch(Argument::type(ElementInvalidationEvent::class))
@@ -109,7 +115,7 @@ public function onUpdate_should_invalidate_elements(ElementEventInterface $event
109115
/**
110116
* @test
111117
*/
112-
public function onUpdate_should_invalidate_dependent_elements_from_objects(): void
118+
public function onUpdate_should_invalidate_dependent_elements(): void
113119
{
114120
$element = $this->prophesize(DataObject\TestDataObject::class);
115121
$dependency = $this->prophesize(Dependency::class);
@@ -119,7 +125,8 @@ public function onUpdate_should_invalidate_dependent_elements_from_objects(): vo
119125
$element->getId()->willReturn(42);
120126
$element->getDependencies()->willReturn($dependency->reveal());
121127
$dependentElement->getId()->willReturn(23);
122-
$dependency->getRequiredBy()->willReturn([$dependentElement->reveal()]);
128+
$dependency->getRequiredBy()->willReturn([['id' => 23, 'type' => 'object']]);
129+
$this->elementRepository->findObject(23)->willReturn($dependentElement->reveal());
123130

124131
$this->invalidateElementListener->onUpdate($event);
125132

@@ -202,7 +209,7 @@ public function onDelete_should_invalidate_elements(ElementEventInterface $event
202209
/**
203210
* @test
204211
*/
205-
public function onDelete_should_invalidate_dependent_elements_from_objects(): void
212+
public function onDelete_should_invalidate_dependent_elements(): void
206213
{
207214
$element = $this->prophesize(DataObject\TestDataObject::class);
208215
$dependency = $this->prophesize(Dependency::class);
@@ -212,7 +219,8 @@ public function onDelete_should_invalidate_dependent_elements_from_objects(): vo
212219
$element->getId()->willReturn(42);
213220
$element->getDependencies()->willReturn($dependency->reveal());
214221
$dependentElement->getId()->willReturn(23);
215-
$dependency->getRequiredBy()->willReturn([$dependentElement->reveal()]);
222+
$dependency->getRequiredBy()->willReturn([['id' => 23, 'type' => 'object']]);
223+
$this->elementRepository->findObject(23)->willReturn($dependentElement->reveal());
216224

217225
$this->invalidateElementListener->onDelete($event);
218226

@@ -266,16 +274,19 @@ public function onDelete_should_invalidate_additional_tags_when_requested(Elemen
266274

267275
public function elementProvider(): iterable
268276
{
277+
$dependency = $this->prophesize(Dependency::class);
278+
269279
$asset = $this->prophesize(Asset::class);
270280
$asset->getId()->willReturn(42);
281+
$asset->getDependencies()->willReturn($dependency->reveal());
271282
yield 'Asset' => ['event' => new AssetEvent($asset->reveal())];
272283

273284
$document = $this->prophesize(Document::class);
274285
$document->getId()->willReturn(42);
286+
$document->getDependencies()->willReturn($dependency->reveal());
275287
yield 'Document' => ['event' => new DocumentEvent($document->reveal())];
276288

277289
$dataObject = $this->prophesize(DataObject::class);
278-
$dependency = $this->prophesize(Dependency::class);
279290
$dataObject->getId()->willReturn(42);
280291
$dataObject->getDependencies()->willReturn($dependency->reveal());
281292
$dependency->getRequiredBy()->willReturn([]);

tests/app/config/pimcore/classes/definition_TestDataObject.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,34 @@
7676
'showCharCount' => false,
7777
'defaultValueGenerator' => '',
7878
]),
79+
1 => Pimcore\Model\DataObject\ClassDefinition\Data\ManyToOneRelation::__set_state([
80+
'name' => 'relatedObjects',
81+
'title' => 'Related Objects',
82+
'tooltip' => '',
83+
'mandatory' => false,
84+
'noteditable' => false,
85+
'index' => false,
86+
'locked' => false,
87+
'style' => '',
88+
'permissions' => null,
89+
'datatype' => 'data',
90+
'invisible' => false,
91+
'visibleGridView' => false,
92+
'visibleSearch' => false,
93+
'classes' => [
94+
0 => [
95+
'classes' => 'TestDataObject',
96+
],
97+
],
98+
'pathFormatterClass' => '',
99+
'width' => '',
100+
'assetUploadPath' => '',
101+
'objectsAllowed' => true,
102+
'assetsAllowed' => false,
103+
'assetTypes' => [],
104+
'documentsAllowed' => false,
105+
'documentTypes' => [],
106+
]),
79107
],
80108
'locked' => false,
81109
'blockedVarsForExport' => [

0 commit comments

Comments
 (0)