From ad771617a15d1f3f150f231d1e604263c4e87d97 Mon Sep 17 00:00:00 2001 From: Jacob Dreesen Date: Thu, 29 Jan 2026 16:56:30 +0100 Subject: [PATCH 1/5] Add `EventType` info to `ElementInvalidationEvent` --- src/Element/ElementInvalidationEvent.php | 4 +++- src/Element/EventType.php | 10 ++++++++++ src/Element/InvalidateElementListener.php | 8 ++++---- 3 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 src/Element/EventType.php diff --git a/src/Element/ElementInvalidationEvent.php b/src/Element/ElementInvalidationEvent.php index 1e0fcaf..2052e48 100644 --- a/src/Element/ElementInvalidationEvent.php +++ b/src/Element/ElementInvalidationEvent.php @@ -12,15 +12,17 @@ final class ElementInvalidationEvent extends Event public bool $cancel = false; private function __construct( + public readonly EventType $type, public readonly ElementInterface $element, public readonly ElementType $elementType, private CacheTags $cacheTags, ) { } - public static function fromElement(ElementInterface $element): self + public static function fromElement(ElementInterface $element, EventType $type): self { return new self( + $type, $element, ElementType::fromElement($element), CacheTags::fromElement($element), diff --git a/src/Element/EventType.php b/src/Element/EventType.php new file mode 100644 index 0000000..b65285e --- /dev/null +++ b/src/Element/EventType.php @@ -0,0 +1,10 @@ +invalidateElement($event->getElement()); + $this->invalidateElement($event->getElement(), EventType::Update); } public function onDelete(ElementEventInterface $event): void { - $this->invalidateElement($event->getElement()); + $this->invalidateElement($event->getElement(), EventType::Delete); } - private function invalidateElement(ElementInterface $element): void + private function invalidateElement(ElementInterface $element, EventType $type): void { - $invalidationEvent = $this->dispatcher->dispatch(ElementInvalidationEvent::fromElement($element)); + $invalidationEvent = $this->dispatcher->dispatch(ElementInvalidationEvent::fromElement($element, $type)); \assert($invalidationEvent instanceof ElementInvalidationEvent); if ($invalidationEvent->cancel) { From 9fc3ce9a2d0fceef5abea48c308fcc085ac52753 Mon Sep 17 00:00:00 2001 From: Jan Adams Date: Sun, 1 Mar 2026 09:53:36 +0100 Subject: [PATCH 2/5] Fix review findings: style, naming, and EventType test coverage - Fix blank line after --- src/Element/EventType.php | 3 +- .../TraceableResponseTaggerTest.php | 2 +- .../Element/InvalidateElementListenerTest.php | 37 +++++++++++++++++-- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/Element/EventType.php b/src/Element/EventType.php index b65285e..9d89f6c 100644 --- a/src/Element/EventType.php +++ b/src/Element/EventType.php @@ -1,5 +1,4 @@ - */ private ObjectProphecy $innerTagger; diff --git a/tests/Unit/Element/InvalidateElementListenerTest.php b/tests/Unit/Element/InvalidateElementListenerTest.php index 21d541b..7b3a1b0 100644 --- a/tests/Unit/Element/InvalidateElementListenerTest.php +++ b/tests/Unit/Element/InvalidateElementListenerTest.php @@ -6,6 +6,7 @@ use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTag; use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTags; use Neusta\Pimcore\HttpCacheBundle\Element\ElementInvalidationEvent; +use Neusta\Pimcore\HttpCacheBundle\Element\EventType; use Neusta\Pimcore\HttpCacheBundle\Element\InvalidateElementListener; use PHPUnit\Framework\TestCase; use Pimcore\Event\Model\AssetEvent; @@ -113,7 +114,7 @@ public function onUpdate_should_invalidate_elements(ElementEventInterface $event public function onUpdate_does_not_invalidate_when_event_was_canceled(ElementEventInterface $event): void { $element = $event->getElement(); - $invalidationEvent = ElementInvalidationEvent::fromElement($element); + $invalidationEvent = ElementInvalidationEvent::fromElement($element, EventType::Update); $invalidationEvent->cancel = true; $this->eventDispatcher->dispatch(Argument::type(ElementInvalidationEvent::class)) @@ -135,7 +136,7 @@ public function onUpdate_should_invalidate_additional_tags_when_requested(Elemen $element = $event->getElement(); $additionalTag = CacheTag::fromString('tag1'); $additionalTags = CacheTags::fromStrings(['tag2', 'tag3']); - $invalidationEvent = ElementInvalidationEvent::fromElement($element); + $invalidationEvent = ElementInvalidationEvent::fromElement($element, EventType::Update); $invalidationEvent->addTag($additionalTag); $invalidationEvent->addTags($additionalTags); $expected = CacheTags::fromElement($element)->with($additionalTag, $additionalTags); @@ -185,7 +186,7 @@ public function onDelete_should_invalidate_elements(ElementEventInterface $event public function onDelete_does_not_invalidate_when_event_was_canceled(ElementEventInterface $event): void { $element = $event->getElement(); - $invalidationEvent = ElementInvalidationEvent::fromElement($element); + $invalidationEvent = ElementInvalidationEvent::fromElement($element, EventType::Delete); $invalidationEvent->cancel = true; $this->eventDispatcher->dispatch(Argument::type(ElementInvalidationEvent::class)) @@ -207,7 +208,7 @@ public function onDelete_should_invalidate_additional_tags_when_requested(Elemen $element = $event->getElement(); $additionalTag = CacheTag::fromString('tag1'); $additionalTags = CacheTags::fromStrings(['tag2', 'tag3']); - $invalidationEvent = ElementInvalidationEvent::fromElement($element); + $invalidationEvent = ElementInvalidationEvent::fromElement($element, EventType::Delete); $invalidationEvent->addTag($additionalTag); $invalidationEvent->addTags($additionalTags); $expected = CacheTags::fromElement($element)->with($additionalTag, $additionalTags); @@ -221,6 +222,34 @@ public function onDelete_should_invalidate_additional_tags_when_requested(Elemen ->shouldHaveBeenCalledOnce(); } + /** + * @test + * + * @dataProvider elementProvider + */ + public function onUpdate_should_dispatch_event_with_update_type(ElementEventInterface $event): void + { + $this->invalidateElementListener->onUpdate($event); + + $this->eventDispatcher->dispatch(Argument::that( + static fn (ElementInvalidationEvent $e) => EventType::Update === $e->type, + ))->shouldHaveBeenCalledOnce(); + } + + /** + * @test + * + * @dataProvider elementProvider + */ + public function onDelete_should_dispatch_event_with_delete_type(ElementEventInterface $event): void + { + $this->invalidateElementListener->onDelete($event); + + $this->eventDispatcher->dispatch(Argument::that( + static fn (ElementInvalidationEvent $e) => EventType::Delete === $e->type, + ))->shouldHaveBeenCalledOnce(); + } + public function elementProvider(): iterable { $asset = $this->prophesize(Asset::class); From 91f84128b78be6e07771ce79e597717bf805df77 Mon Sep 17 00:00:00 2001 From: Jan Adams <56639925+jan888adams@users.noreply.github.com> Date: Mon, 31 Aug 2026 10:57:29 +0200 Subject: [PATCH 3/5] Invalidation event type proposal (#44) Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Jan Adams --- doc/4-events.md | 25 +++++++++++++++++++ .../TraceableResponseTaggerTest.php | 14 +++++------ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/doc/4-events.md b/doc/4-events.md index 231494d..318d669 100644 --- a/doc/4-events.md +++ b/doc/4-events.md @@ -8,3 +8,28 @@ You can listen to the following events: **ElementInvalidationEvent**: Triggered before a Pimcore element is invalidated; allows canceling the invalidation process or performing custom actions. This allows you to add additional tags, cancel the tagging/invalidation process, or implement custom logic. + +### ElementInvalidationEvent: distinguishing update from delete + +`ElementInvalidationEvent` now exposes a `type` property of type `EventType`, which tells you whether the invalidation was triggered by a save or a delete: + +```php +use Neusta\Pimcore\HttpCacheBundle\Element\ElementInvalidationEvent; +use Neusta\Pimcore\HttpCacheBundle\Element\EventType; + +#[AsEventListener] +final class MyInvalidationListener +{ + public function __invoke(ElementInvalidationEvent $event): void + { + if (EventType::Delete === $event->type) { + // React differently on delete vs update + } + } +} +``` + +| `EventType` | Triggered by | +|---|---| +| `EventType::Update` | Element saved (`POST_UPDATE`) | +| `EventType::Delete` | Element deleted (`PRE_DELETE`) | diff --git a/tests/Unit/Cache/ResponseTagger/TraceableResponseTaggerTest.php b/tests/Unit/Cache/ResponseTagger/TraceableResponseTaggerTest.php index 0b2c695..d9db01b 100644 --- a/tests/Unit/Cache/ResponseTagger/TraceableResponseTaggerTest.php +++ b/tests/Unit/Cache/ResponseTagger/TraceableResponseTaggerTest.php @@ -22,7 +22,7 @@ final class TraceableResponseTaggerTest extends TestCase protected function setUp(): void { $this->innerTagger = $this->prophesize(ResponseTagger::class); - $this->collectTagsResponseTagger = new TraceableResponseTagger($this->innerTagger->reveal()); + $this->traceableResponseTagger = new TraceableResponseTagger($this->innerTagger->reveal()); } /** @@ -30,7 +30,7 @@ protected function setUp(): void */ public function tag_should_collect_tags(): void { - $this->collectTagsResponseTagger->tag( + $this->traceableResponseTagger->tag( new CacheTags( CacheTag::fromString('tag1'), CacheTag::fromString('tag2'), @@ -38,7 +38,7 @@ public function tag_should_collect_tags(): void self::assertSame( 'tag1,tag2', - $this->collectTagsResponseTagger->recordedTags->toString(), + $this->traceableResponseTagger->recordedTags->toString(), ); } @@ -52,7 +52,7 @@ public function tag_should_forward_tags_to_inner_tagger(): void CacheTag::fromString('tag2'), ); - $this->collectTagsResponseTagger->tag($tags); + $this->traceableResponseTagger->tag($tags); $this->innerTagger->tag($tags)->shouldHaveBeenCalledOnce(); } @@ -62,16 +62,16 @@ public function tag_should_forward_tags_to_inner_tagger(): void */ public function reset_should_reset_collected_tags(): void { - $this->collectTagsResponseTagger->tag( + $this->traceableResponseTagger->tag( new CacheTags( CacheTag::fromString('tag1'), CacheTag::fromString('tag2'), )); - $this->collectTagsResponseTagger->reset(); + $this->traceableResponseTagger->reset(); self::assertTrue( - $this->collectTagsResponseTagger->recordedTags->isEmpty(), + $this->traceableResponseTagger->recordedTags->isEmpty(), ); } } From dd022b181489185c653cc05a3235dcf05f569dc2 Mon Sep 17 00:00:00 2001 From: Jacob Dreesen Date: Mon, 31 Aug 2026 11:21:36 +0200 Subject: [PATCH 4/5] Rename EventType to InvalidationType, put type param first Addresses code review feedback: InvalidationType stays unbacked (unlike ElementType, its value never ends up in a cache tag), and fromElement()/invalidateElement() now take the type before the element, consistent with the constructor's parameter order. --- doc/4-events.md | 13 +++++++------ src/Element/ElementInvalidationEvent.php | 4 ++-- src/Element/InvalidateElementListener.php | 8 ++++---- .../{EventType.php => InvalidationType.php} | 2 +- .../Unit/Element/InvalidateElementListenerTest.php | 14 +++++++------- 5 files changed, 21 insertions(+), 20 deletions(-) rename src/Element/{EventType.php => InvalidationType.php} (84%) diff --git a/doc/4-events.md b/doc/4-events.md index 318d669..27eba90 100644 --- a/doc/4-events.md +++ b/doc/4-events.md @@ -11,25 +11,26 @@ This allows you to add additional tags, cancel the tagging/invalidation process, ### ElementInvalidationEvent: distinguishing update from delete -`ElementInvalidationEvent` now exposes a `type` property of type `EventType`, which tells you whether the invalidation was triggered by a save or a delete: +`ElementInvalidationEvent` exposes a `type` property of type `InvalidationType`, which tells you whether the invalidation was triggered by a save or a delete: ```php use Neusta\Pimcore\HttpCacheBundle\Element\ElementInvalidationEvent; -use Neusta\Pimcore\HttpCacheBundle\Element\EventType; +use Neusta\Pimcore\HttpCacheBundle\Element\InvalidationType; +use Symfony\Component\EventDispatcher\Attribute\AsEventListener; #[AsEventListener] final class MyInvalidationListener { public function __invoke(ElementInvalidationEvent $event): void { - if (EventType::Delete === $event->type) { + if (InvalidationType::Delete === $event->type) { // React differently on delete vs update } } } ``` -| `EventType` | Triggered by | +| `InvalidationType` | Triggered by | |---|---| -| `EventType::Update` | Element saved (`POST_UPDATE`) | -| `EventType::Delete` | Element deleted (`PRE_DELETE`) | +| `InvalidationType::Update` | Element saved (`POST_UPDATE`) | +| `InvalidationType::Delete` | Element deleted (`PRE_DELETE`) | diff --git a/src/Element/ElementInvalidationEvent.php b/src/Element/ElementInvalidationEvent.php index 2052e48..2f0d55f 100644 --- a/src/Element/ElementInvalidationEvent.php +++ b/src/Element/ElementInvalidationEvent.php @@ -12,14 +12,14 @@ final class ElementInvalidationEvent extends Event public bool $cancel = false; private function __construct( - public readonly EventType $type, + public readonly InvalidationType $type, public readonly ElementInterface $element, public readonly ElementType $elementType, private CacheTags $cacheTags, ) { } - public static function fromElement(ElementInterface $element, EventType $type): self + public static function fromElement(InvalidationType $type, ElementInterface $element): self { return new self( $type, diff --git a/src/Element/InvalidateElementListener.php b/src/Element/InvalidateElementListener.php index d4718c1..bc52507 100644 --- a/src/Element/InvalidateElementListener.php +++ b/src/Element/InvalidateElementListener.php @@ -21,17 +21,17 @@ public function onUpdate(ElementEventInterface $event): void return; } - $this->invalidateElement($event->getElement(), EventType::Update); + $this->invalidateElement(InvalidationType::Update, $event->getElement()); } public function onDelete(ElementEventInterface $event): void { - $this->invalidateElement($event->getElement(), EventType::Delete); + $this->invalidateElement(InvalidationType::Delete, $event->getElement()); } - private function invalidateElement(ElementInterface $element, EventType $type): void + private function invalidateElement(InvalidationType $type, ElementInterface $element): void { - $invalidationEvent = $this->dispatcher->dispatch(ElementInvalidationEvent::fromElement($element, $type)); + $invalidationEvent = $this->dispatcher->dispatch(ElementInvalidationEvent::fromElement($type, $element)); \assert($invalidationEvent instanceof ElementInvalidationEvent); if ($invalidationEvent->cancel) { diff --git a/src/Element/EventType.php b/src/Element/InvalidationType.php similarity index 84% rename from src/Element/EventType.php rename to src/Element/InvalidationType.php index 9d89f6c..6c8505c 100644 --- a/src/Element/EventType.php +++ b/src/Element/InvalidationType.php @@ -2,7 +2,7 @@ namespace Neusta\Pimcore\HttpCacheBundle\Element; -enum EventType +enum InvalidationType { case Update; case Delete; diff --git a/tests/Unit/Element/InvalidateElementListenerTest.php b/tests/Unit/Element/InvalidateElementListenerTest.php index 7b3a1b0..0ee59bc 100644 --- a/tests/Unit/Element/InvalidateElementListenerTest.php +++ b/tests/Unit/Element/InvalidateElementListenerTest.php @@ -6,7 +6,7 @@ use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTag; use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTags; use Neusta\Pimcore\HttpCacheBundle\Element\ElementInvalidationEvent; -use Neusta\Pimcore\HttpCacheBundle\Element\EventType; +use Neusta\Pimcore\HttpCacheBundle\Element\InvalidationType; use Neusta\Pimcore\HttpCacheBundle\Element\InvalidateElementListener; use PHPUnit\Framework\TestCase; use Pimcore\Event\Model\AssetEvent; @@ -114,7 +114,7 @@ public function onUpdate_should_invalidate_elements(ElementEventInterface $event public function onUpdate_does_not_invalidate_when_event_was_canceled(ElementEventInterface $event): void { $element = $event->getElement(); - $invalidationEvent = ElementInvalidationEvent::fromElement($element, EventType::Update); + $invalidationEvent = ElementInvalidationEvent::fromElement(InvalidationType::Update, $element); $invalidationEvent->cancel = true; $this->eventDispatcher->dispatch(Argument::type(ElementInvalidationEvent::class)) @@ -136,7 +136,7 @@ public function onUpdate_should_invalidate_additional_tags_when_requested(Elemen $element = $event->getElement(); $additionalTag = CacheTag::fromString('tag1'); $additionalTags = CacheTags::fromStrings(['tag2', 'tag3']); - $invalidationEvent = ElementInvalidationEvent::fromElement($element, EventType::Update); + $invalidationEvent = ElementInvalidationEvent::fromElement(InvalidationType::Update, $element); $invalidationEvent->addTag($additionalTag); $invalidationEvent->addTags($additionalTags); $expected = CacheTags::fromElement($element)->with($additionalTag, $additionalTags); @@ -186,7 +186,7 @@ public function onDelete_should_invalidate_elements(ElementEventInterface $event public function onDelete_does_not_invalidate_when_event_was_canceled(ElementEventInterface $event): void { $element = $event->getElement(); - $invalidationEvent = ElementInvalidationEvent::fromElement($element, EventType::Delete); + $invalidationEvent = ElementInvalidationEvent::fromElement(InvalidationType::Delete, $element); $invalidationEvent->cancel = true; $this->eventDispatcher->dispatch(Argument::type(ElementInvalidationEvent::class)) @@ -208,7 +208,7 @@ public function onDelete_should_invalidate_additional_tags_when_requested(Elemen $element = $event->getElement(); $additionalTag = CacheTag::fromString('tag1'); $additionalTags = CacheTags::fromStrings(['tag2', 'tag3']); - $invalidationEvent = ElementInvalidationEvent::fromElement($element, EventType::Delete); + $invalidationEvent = ElementInvalidationEvent::fromElement(InvalidationType::Delete, $element); $invalidationEvent->addTag($additionalTag); $invalidationEvent->addTags($additionalTags); $expected = CacheTags::fromElement($element)->with($additionalTag, $additionalTags); @@ -232,7 +232,7 @@ public function onUpdate_should_dispatch_event_with_update_type(ElementEventInte $this->invalidateElementListener->onUpdate($event); $this->eventDispatcher->dispatch(Argument::that( - static fn (ElementInvalidationEvent $e) => EventType::Update === $e->type, + static fn (ElementInvalidationEvent $e) => InvalidationType::Update === $e->type, ))->shouldHaveBeenCalledOnce(); } @@ -246,7 +246,7 @@ public function onDelete_should_dispatch_event_with_delete_type(ElementEventInte $this->invalidateElementListener->onDelete($event); $this->eventDispatcher->dispatch(Argument::that( - static fn (ElementInvalidationEvent $e) => EventType::Delete === $e->type, + static fn (ElementInvalidationEvent $e) => InvalidationType::Delete === $e->type, ))->shouldHaveBeenCalledOnce(); } From 9aa13a35c54f3b30cb7e1215d788800e7f2fcce8 Mon Sep 17 00:00:00 2001 From: Jacob Dreesen Date: Mon, 31 Aug 2026 11:25:09 +0200 Subject: [PATCH 5/5] Fix import order (php-cs-fixer) --- tests/Unit/Element/InvalidateElementListenerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/Element/InvalidateElementListenerTest.php b/tests/Unit/Element/InvalidateElementListenerTest.php index 0ee59bc..1167e08 100644 --- a/tests/Unit/Element/InvalidateElementListenerTest.php +++ b/tests/Unit/Element/InvalidateElementListenerTest.php @@ -6,8 +6,8 @@ use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTag; use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTags; use Neusta\Pimcore\HttpCacheBundle\Element\ElementInvalidationEvent; -use Neusta\Pimcore\HttpCacheBundle\Element\InvalidationType; use Neusta\Pimcore\HttpCacheBundle\Element\InvalidateElementListener; +use Neusta\Pimcore\HttpCacheBundle\Element\InvalidationType; use PHPUnit\Framework\TestCase; use Pimcore\Event\Model\AssetEvent; use Pimcore\Event\Model\DataObjectEvent;