Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
9dfee3b to
cfd6107
Compare
7fc57dd to
6aedd15
Compare
|
|
||
| $this->invalidateElement($element); | ||
|
|
||
| if (ElementType::Object === ElementType::tryFrom($element->getType())) { |
There was a problem hiding this comment.
It wouldn’t make sense in every case.
If an object is required by a document, why should all sites that use this object be invalidated?
If a snippet is required by a page, why should all pages containing that snippet be invalidated?
I think we need to carefully consider in which cases this behavior actually makes sense.
There was a problem hiding this comment.
It wouldn’t make sense in every case.
You don't know that. Only the app will know, right?
If an object is required by a document, why should all sites that use this object be invalidated?
Probably because it is somehow used in the document, so when it's not invalidated it displays outdated data?
If a snippet is required by a page, why should all pages containing that snippet be invalidated?
Same reason: why should the page require the snippet if it doesn't use it? And if it does, and it isn't invalidated, it shows outdated data.
I think we need to carefully consider in which cases this behavior actually makes sense.
Here I'm with you. Maybe we need an (easy) mechanism that controls whether the dependencies should be invalidated or not (also: which ones?) 🤔
There was a problem hiding this comment.
Yeah, you are right. The examples were not selected carefully.
Indeed, it should be: a Document is required by an Object. That may or may not make sense, but it’s a case the user of the bundle could have control over.
Also, a Page being required by a Snippet doesn’t sound like a standard case. If we want to do that, it should be considered more carefully.
So the idea was: cases where an Object is required by another Object or by a Document are clear. The other cases are not standard and should not be included in our default behavior.
The best way to handle them is, as you said, through configuration.
There was a problem hiding this comment.
It is now possible to flexibly configure all element types. Disabled by default.
ea6878b to
af41501
Compare
| $element = match (ElementType::tryFrom($required['type'])) { | ||
| ElementType::Object => $this->elementRepository->findObject($required['id']), | ||
| ElementType::Document => $this->elementRepository->findDocument($required['id']), | ||
| ElementType::Asset => $this->elementRepository->findAsset($required['id']), |
There was a problem hiding this comment.
We need to check if invalidating an asset makes sense.
There was a problem hiding this comment.
We kept assets in the dependency traversal, though honestly we're not 100% certain this will ever be needed in practice.
Here's the reasoning: standard Pimcore asset metadata supports an object field type, which means an asset can hold a reference to a data object. When Pimcore saves that asset, it records
the relation as a dependency — so object.getDependencies()->getRequiredBy() can return assets. Whether anyone actually uses this in the wild, we don't know. But the infrastructure supports
it, so ignoring assets felt like an arbitrary gap.
What makes invalidating an asset's cache tag useful here is the cascade effect. When a page renders and loads an asset, this bundle tags that page response with the asset's tag (e.g. a42).
So purging a42 doesn't just clear the asset's own cached file response — it also clears every page that loaded the asset. If those pages render information from the asset's object
metadata (a caption, a credit, a classification), they'd otherwise serve stale content after the object changes.
We'll revisit this later. The plan is to make the dependency traversal configurable in the bundle config, so projects can opt in or out per element type rather than having it hardcoded.
There was a problem hiding this comment.
done, assets can be enabled if needed in config
38d1ff8 to
d58cf4f
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
src/DependencyInjection/NeustaPimcoreHttpCacheExtension.php (1)
74-76: Consider reusing the existing$invalidateListenervariable.The definition is already retrieved on line 36. You could reuse it instead of fetching again:
♻️ Suggested simplification
- $container->getDefinition('neusta_pimcore_http_cache.element.invalidate_listener') - ->setArgument('$config', $config); + $invalidateListener + ->setArgument('$config', $config);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/DependencyInjection/NeustaPimcoreHttpCacheExtension.php` around lines 74 - 76, The code fetches the 'neusta_pimcore_http_cache.element.invalidate_listener' definition twice; instead reuse the previously retrieved $invalidateListener variable (from where it's defined earlier) and call ->setArgument('$config', $config) on $invalidateListener instead of calling $container->getDefinition(...) again to avoid redundant retrieval and improve clarity.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Element/InvalidateElementListener.php`:
- Around line 36-43: After calling invalidateElement in
invalidateWithDependencies, ensure you do not traverse/invalidate dependents if
the source invalidation was canceled: update invalidateWithDependencies to check
the element's cancellation state (e.g. a boolean like isInvalidationCanceled()
or a return value from invalidateElement) and only call
isDependencyTraversalEnabled and invalidateDependencies when the invalidation
was not canceled; use the existing methods invalidateElement,
isDependencyTraversalEnabled, and invalidateDependencies to locate where to add
this guard.
- Around line 40-43: The code currently calls
ElementType::tryFrom($element->getType()), which fails for subtype strings like
"image" or "page" and causes dependency traversal in
isDependencyTraversalEnabled/ invalidateDependencies to be skipped; replace the
tryFrom(getType()) call with ElementType::fromElement($element) (which uses
Pimcore\Model\Element\Service::getElementType()) so $type resolves to the
correct generic type before invoking isDependencyTraversalEnabled($type) and
invalidateDependencies($element->getDependencies(), $type).
In `@tests/Integration/Tagging/CollectTagsDataTest.php`:
- Line 49: The test calls TestDocumentFactory::simplePage(5)->save() after
arrange() returns, which causes the save to run with caching active; move the
save() call inside the closure passed to self::arrange so the document is
created and saved during arrange. Update the call in CollectTagsDataTest to
self::arrange(fn () => TestDocumentFactory::simplePage(5)->save()) so that
TestDocumentFactory::simplePage and its save() are executed within arrange().
In `@tests/Integration/Tagging/TagAdditionalTagTest.php`:
- Around line 107-124: The assertion in the test method
response_is_tagged_with_additional_tag_when_object_is_loaded (class
TagAdditionalTagTest) is checking the primary object's tag 'o5' instead of the
additional tag added by the ElementTaggingEvent listener; update the final
assertion that inspects the X-Cache-Tags header to expect 'o12' (the additional
tag produced by CacheTag::fromString('12', new
ElementCacheType(ElementType::Object))) instead of 'o5' so the test aligns with
the method intent and the asset/document variants.
In `@tests/Integration/Tagging/TagDocumentTest.php`:
- Around line 103-112: The assertion checks for tag 'd29' but the test arranges
a hardlink with id 12 via TestDocumentFactory::simpleHardLink(12)->save() and
then requests '/get-document?id=12', so update the assertion to check for the
correct tag for id 12 (i.e. assert that X-Cache-Tags does not contain 'd12' or
adjust to the expected tag string for that hardlink) by modifying the final
assertion that inspects $response->headers->get('X-Cache-Tags') to reference the
hardlink id used in TestDocumentFactory::simpleHardLink.
---
Nitpick comments:
In `@src/DependencyInjection/NeustaPimcoreHttpCacheExtension.php`:
- Around line 74-76: The code fetches the
'neusta_pimcore_http_cache.element.invalidate_listener' definition twice;
instead reuse the previously retrieved $invalidateListener variable (from where
it's defined earlier) and call ->setArgument('$config', $config) on
$invalidateListener instead of calling $container->getDefinition(...) again to
avoid redundant retrieval and improve clarity.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (23)
config/services.phpdoc/2-configuration.mddoc/3-pimcore-elements.mdsrc/DependencyInjection/Configuration.phpsrc/DependencyInjection/NeustaPimcoreHttpCacheExtension.phpsrc/Element/InvalidateElementListener.phptests/Integration/Configuration/CollectConfigurationDataTest.phptests/Integration/Helpers/TestAssetFactory.phptests/Integration/Helpers/TestDocumentFactory.phptests/Integration/Helpers/TestObjectFactory.phptests/Integration/Invalidation/CancelInvalidationTest.phptests/Integration/Invalidation/InvalidateAdditionalTagTest.phptests/Integration/Invalidation/InvalidateAssetTest.phptests/Integration/Invalidation/InvalidateDocumentTest.phptests/Integration/Invalidation/InvalidateObjectTest.phptests/Integration/Tagging/CollectTagsDataTest.phptests/Integration/Tagging/TagAdditionalTagTest.phptests/Integration/Tagging/TagAssetTest.phptests/Integration/Tagging/TagDocumentTest.phptests/Integration/Tagging/TagObjectTest.phptests/Unit/Element/InvalidateElementListenerTest.phptests/app/config/pimcore/classes/definition_TestObject.phptests/app/src/Controller/GetObjectController.php
There was a problem hiding this comment.
🧹 Nitpick comments (3)
src/Element/InvalidateElementListener.php (1)
67-90: Consider batching invalidation for high fan-out dependencies.For elements with many
requiredByentries, per-element invalidation calls may become expensive. If this becomes a hotspot, aggregating tags and issuing fewer invalidator calls would improve throughput.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Element/InvalidateElementListener.php` around lines 67 - 90, The invalidateDependencies method currently calls invalidateElement for each dependent in a loop which can be expensive for high fan-out; refactor invalidateDependencies (and possibly add a helper like collectAndInvalidateBatch) to aggregate elements/tags per dependent ElementType (use ElementType::tryFrom, configKey($dependentType), and elementRepository->findObject/findDocument/findAsset) into batches and then call the invalidator once per batch instead of per element (or use an invalidateElements(array $elements) / invalidateTags(array $tags) variant if available) so fewer invalidator calls are issued while preserving the existing type filters and the final call to invalidateElement for remaining singletons.src/DependencyInjection/Configuration.php (1)
40-55: Consider extracting the duplicatedinvalidate_dependenciessubtree builder.The three blocks are functionally aligned, but copy/paste here is easy to drift over time. A small private helper to append this subtree would improve maintainability.
Also applies to: 73-88, 114-129
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/DependencyInjection/Configuration.php` around lines 40 - 55, The invalidate_dependencies subtree is duplicated in multiple places; extract its construction into a private helper method (e.g., private function appendInvalidateDependencies(NodeDefinition $node) or private function addInvalidateDependencies(ArrayNodeDefinition $parent)) that encapsulates the ->arrayNode('invalidate_dependencies')->info(...)->canBeEnabled()->addDefaultsIfNotSet()->children()->arrayNode('types')->info(...)->addDefaultsIfNotSet()->children()->booleanNode('assets')->defaultFalse()->end()->booleanNode('documents')->defaultFalse()->end()->booleanNode('objects')->defaultFalse()->end()->end()->end()->end() logic, then replace the three inline copies with calls to this helper from the Configuration builder (referencing the existing arrayNode('invalidate_dependencies') occurrences). Ensure the helper returns or mutates the passed node consistently and add a small unit/test if present to verify the subtree is still added.tests/Unit/Element/InvalidateElementListenerTest.php (1)
191-208: Use list-shaped dependency fixtures to mirror runtime payloads.
getRequiredBy()is mocked as a single associative array in this provider. The listener iterates over a list of dependency records, so these fixtures should use list form to avoid false confidence in future changes.♻️ Suggested fixture shape adjustment
- $dependency->getRequiredBy()->willReturn(['id' => 23, 'type' => 'object']); + $dependency->getRequiredBy()->willReturn([['id' => 23, 'type' => 'object']]); @@ - $dependency->getRequiredBy()->willReturn(['id' => 23, 'type' => 'object']); + $dependency->getRequiredBy()->willReturn([['id' => 23, 'type' => 'object']]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/Unit/Element/InvalidateElementListenerTest.php` around lines 191 - 208, The provider notObjectElementProvider() uses Dependency::getRequiredBy() to return a single associative array, but the listener expects a list of dependency records; update both Asset and Document fixtures so Dependency::getRequiredBy() returns a list-shaped value (i.e., an array/iterable whose elements are associative records like ['id' => 23, 'type' => 'object']) so the tests mirror the runtime payload processed by AssetEvent and DocumentEvent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/DependencyInjection/Configuration.php`:
- Around line 40-55: The invalidate_dependencies subtree is duplicated in
multiple places; extract its construction into a private helper method (e.g.,
private function appendInvalidateDependencies(NodeDefinition $node) or private
function addInvalidateDependencies(ArrayNodeDefinition $parent)) that
encapsulates the
->arrayNode('invalidate_dependencies')->info(...)->canBeEnabled()->addDefaultsIfNotSet()->children()->arrayNode('types')->info(...)->addDefaultsIfNotSet()->children()->booleanNode('assets')->defaultFalse()->end()->booleanNode('documents')->defaultFalse()->end()->booleanNode('objects')->defaultFalse()->end()->end()->end()->end()
logic, then replace the three inline copies with calls to this helper from the
Configuration builder (referencing the existing
arrayNode('invalidate_dependencies') occurrences). Ensure the helper returns or
mutates the passed node consistently and add a small unit/test if present to
verify the subtree is still added.
In `@src/Element/InvalidateElementListener.php`:
- Around line 67-90: The invalidateDependencies method currently calls
invalidateElement for each dependent in a loop which can be expensive for high
fan-out; refactor invalidateDependencies (and possibly add a helper like
collectAndInvalidateBatch) to aggregate elements/tags per dependent ElementType
(use ElementType::tryFrom, configKey($dependentType), and
elementRepository->findObject/findDocument/findAsset) into batches and then call
the invalidator once per batch instead of per element (or use an
invalidateElements(array $elements) / invalidateTags(array $tags) variant if
available) so fewer invalidator calls are issued while preserving the existing
type filters and the final call to invalidateElement for remaining singletons.
In `@tests/Unit/Element/InvalidateElementListenerTest.php`:
- Around line 191-208: The provider notObjectElementProvider() uses
Dependency::getRequiredBy() to return a single associative array, but the
listener expects a list of dependency records; update both Asset and Document
fixtures so Dependency::getRequiredBy() returns a list-shaped value (i.e., an
array/iterable whose elements are associative records like ['id' => 23, 'type'
=> 'object']) so the tests mirror the runtime payload processed by AssetEvent
and DocumentEvent.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (24)
config/services.phpdoc/2-configuration.mddoc/3-pimcore-elements.mdsrc/DependencyInjection/Configuration.phpsrc/DependencyInjection/NeustaPimcoreHttpCacheExtension.phpsrc/Element/ElementType.phpsrc/Element/InvalidateElementListener.phptests/Integration/Configuration/CollectConfigurationDataTest.phptests/Integration/Helpers/TestAssetFactory.phptests/Integration/Helpers/TestDocumentFactory.phptests/Integration/Helpers/TestObjectFactory.phptests/Integration/Invalidation/CancelInvalidationTest.phptests/Integration/Invalidation/InvalidateAdditionalTagTest.phptests/Integration/Invalidation/InvalidateAssetTest.phptests/Integration/Invalidation/InvalidateDocumentTest.phptests/Integration/Invalidation/InvalidateObjectTest.phptests/Integration/Tagging/CollectTagsDataTest.phptests/Integration/Tagging/TagAdditionalTagTest.phptests/Integration/Tagging/TagAssetTest.phptests/Integration/Tagging/TagDocumentTest.phptests/Integration/Tagging/TagObjectTest.phptests/Unit/Element/InvalidateElementListenerTest.phptests/app/config/pimcore/classes/definition_TestObject.phptests/app/src/Controller/GetObjectController.php
| # Unless you disable data objects completely | ||
| # Invalidate dependent elements when an object changes (disabled by default). | ||
| # Note: a dependent element type must also be enabled above for invalidation to take effect. | ||
| invalidate_dependencies: |
There was a problem hiding this comment.
I think element types alone might not be sufficient. We should probably consider subtypes as well.
…ests - Make ElementRepository a final class (drop redundant @Final annotation) - Add test: dependency traversal is not triggered when a document is updated - Add test: dependency traversal is not triggered when an asset is updated Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cking) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nce objects Assets are static files (images, PDFs) and do not reference objects via Pimcore's dependency system. Even if they did, invalidating an asset's HTTP cache because referenced object data changed would be semantically wrong — the file itself is unchanged. Only objects and documents are valid dependents. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Standard Pimcore assets support metadata fields of type "object", which Pimcore tracks as dependencies. When an object O changes and an asset A has a metadata relation to O, O.getRequiredBy() includes A. Invalidating A's cache tag (e.g. a42) is valid: all page responses tagged with a42 (because they rendered the asset) are also purged, which is the desired cascading behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds an `invalidate_dependencies` config node (off by default) under each element type (assets, documents, objects). When enabled, a `types` sub-node controls which dependent element types get purged. The `InvalidateElementListener` now checks the config before traversing and before invalidating each dependent type, making the previously hardcoded object-cascade opt-in. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Cast $required['id'] to int before passing to repository methods, since Pimcore returns DB rows with string values - Rename unit test to reflect that traversal is now config-driven, not element-type-driven - Fix wrong Pimcore\Image import in TestObjectFactory (→ Asset\Image) - Add positive integration tests for assets.invalidate_dependencies and documents.invalidate_dependencies (update and deletion) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds invalidate_dependencies to the full config reference in doc/2 and a dedicated section in doc/3 explaining the feature, its opt-in nature, the one-level-deep traversal, and example configs for objects, assets, and documents as sources. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract shared invalidateWithDependencies() from onUpdate/onDelete - Clarify doc/2 that enabled: false is mutually exclusive with other options - Add note to doc/3 that dependent element types must also be enabled in the main elements config for invalidation to take effect Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
If the source element's invalidation event is canceled, skip dependency traversal entirely. Previously, canceling the event only suppressed the cacheInvalidator call but dependency traversal still ran, potentially invalidating dependent elements the caller explicitly opted out of. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getType() returns subtypes like "image" or "page", which ElementType::tryFrom() cannot match, silently skipping traversal for the most common element types. Add ElementType::tryFromElement() which delegates to Service::getElementType() (instanceof-based, always returns the generic type) and use it in invalidateWithDependencies() instead of tryFrom(getType()). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
save() was called after arrange() returned, so the element was persisted with caching active instead of during the setup phase. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The object variant of response_is_tagged_with_additional_tag_when_X_is_loaded was asserting the primary element tag (o5) instead of the additional tag (o12) added by the listener, inconsistent with the asset (a29) and document (d12) variants. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The test arranges and requests hardlink id 12, so the assertion should verify d12 is absent from the response tags, not the unrelated d29. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The mapping from element type to config key belongs on the enum itself, not on the listener that happens to need it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Groups the skip conditions in one named place, making onUpdate() readable and providing an obvious extension point if more conditions are added. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the raw array $config injected into InvalidateElementListener, AssetCacheTagChecker, DocumentCacheTagChecker, and ObjectCacheTagChecker. All config access is now through typed methods (isEnabled, isTypeEnabled, isDependencyTraversalEnabled, etc.) rather than nested array lookups. The DI extension sets ElementsConfig once; all consumers share the instance. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ases - Document that dependency traversal is intentionally one level deep to prevent cycles - Add unit test for when the dependent element type is disabled in config - Add unit test for when the dependent element's own invalidation is canceled Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move dependency traversal logic into its own dedicated class with a single public method, keeping InvalidateElementListener focused on dispatching events and delegating to the cache invalidator. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix @param return type annotation: callable returns mixed, not bool - Add onDelete cancellation test for DependencyInvalidator delegation - Add multi-dependent test to verify the foreach processes all entries Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9ffba3d to
00f70ab
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/DependencyInjection/Configuration.php (1)
40-55: Reduce duplication ininvalidate_dependenciessubtree definitions.The same subtree is repeated three times (assets/documents/objects). Extracting a small helper would reduce drift risk and future maintenance overhead.
♻️ Refactor sketch
+use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; + +private function addInvalidateDependenciesNode(ArrayNodeDefinition $node, string $subject): void +{ + $node + ->arrayNode('invalidate_dependencies') + ->info(sprintf('Enable/disable invalidation of dependent elements when %s is updated or deleted.', $subject)) + ->canBeEnabled() + ->addDefaultsIfNotSet() + ->children() + ->arrayNode('types') + ->info('Enable/disable invalidation of dependent element types.') + ->addDefaultsIfNotSet() + ->children() + ->booleanNode('assets')->defaultFalse()->end() + ->booleanNode('documents')->defaultFalse()->end() + ->booleanNode('objects')->defaultFalse()->end() + ->end() + ->end() + ->end() + ->end(); +}Also applies to: 73-88, 114-129
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/DependencyInjection/Configuration.php` around lines 40 - 55, The invalidate_dependencies subtree repeats identical boolean child node definitions for 'assets', 'documents', and 'objects' under the 'types' ArrayNode; refactor by extracting a small helper method (e.g., addTypeBooleanNodes or addBooleanChildrenToTypes) that takes the parent ArrayNodeDefinition (the 'types' node) and an array of names and adds the booleanNode(...)->defaultFalse()->end() entries, then replace each repeated block (the three occurrences around invalidate_dependencies/types) with calls to that helper to reduce duplication and future drift.doc/2-configuration.md (1)
15-58: Clarify opt-in intent in the YAML example to avoid accidental enablement.The comments say “disabled by default,” but the example sets all
invalidate_dependencies.enabledflags totrue. In a full config snippet, this can be misread as recommended default config.Suggested docs tweak
- invalidate_dependencies: - enabled: true + invalidate_dependencies: + enabled: true # opt-in example; default is false types: objects: true documents: true @@ - invalidate_dependencies: - enabled: true + invalidate_dependencies: + enabled: true # opt-in example; default is false types: objects: true @@ - invalidate_dependencies: - enabled: true + invalidate_dependencies: + enabled: true # opt-in example; default is false types: objects: true documents: true assets: true🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@doc/2-configuration.md` around lines 15 - 58, The YAML example currently shows invalidate_dependencies.enabled set to true for assets, documents and objects which contradicts the “disabled by default” comment; update the snippet so each invalidate_dependencies.enabled is set to false (or clearly mark the shown true values as an explicit opt-in example) and add a short inline comment next to invalidate_dependencies.enabled (e.g., "# default: false — opt-in only") to prevent accidental enablement; refer to the invalidate_dependencies.enabled keys under assets, documents and objects and adjust those entries accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tests/Unit/Element/DependencyInvalidatorTest.php`:
- Around line 41-42: The test is mocking TestObject->getType(), but
DependencyInvalidator::invalidate uses ElementType::tryFromElement which calls
Pimcore\Model\Element\Service::getElementType and relies on instanceof checks,
so the Prophecy mock causes getElementType() to return null and the test exits
early; fix by either (A) replacing the Prophecy mocks of TestObject with real
Pimcore element instances created via your test factories (the same approach
used in integration tests) so instanceof checks pass, or (B) if you want a pure
unit test, stub/mocks Pimcore\Model\Element\Service::getElementType to return
the appropriate ElementType for your fake element before calling
DependencyInvalidator::invalidate; apply the same change to all similar cases
referenced (lines around the other getType() mocks).
---
Nitpick comments:
In `@doc/2-configuration.md`:
- Around line 15-58: The YAML example currently shows
invalidate_dependencies.enabled set to true for assets, documents and objects
which contradicts the “disabled by default” comment; update the snippet so each
invalidate_dependencies.enabled is set to false (or clearly mark the shown true
values as an explicit opt-in example) and add a short inline comment next to
invalidate_dependencies.enabled (e.g., "# default: false — opt-in only") to
prevent accidental enablement; refer to the invalidate_dependencies.enabled keys
under assets, documents and objects and adjust those entries accordingly.
In `@src/DependencyInjection/Configuration.php`:
- Around line 40-55: The invalidate_dependencies subtree repeats identical
boolean child node definitions for 'assets', 'documents', and 'objects' under
the 'types' ArrayNode; refactor by extracting a small helper method (e.g.,
addTypeBooleanNodes or addBooleanChildrenToTypes) that takes the parent
ArrayNodeDefinition (the 'types' node) and an array of names and adds the
booleanNode(...)->defaultFalse()->end() entries, then replace each repeated
block (the three occurrences around invalidate_dependencies/types) with calls to
that helper to reduce duplication and future drift.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (33)
config/services.phpdoc/2-configuration.mddoc/3-pimcore-elements.mdsrc/Cache/CacheTagChecker/Element/AssetCacheTagChecker.phpsrc/Cache/CacheTagChecker/Element/DocumentCacheTagChecker.phpsrc/Cache/CacheTagChecker/Element/ObjectCacheTagChecker.phpsrc/DependencyInjection/Configuration.phpsrc/DependencyInjection/NeustaPimcoreHttpCacheExtension.phpsrc/Element/DependencyInvalidator.phpsrc/Element/ElementType.phpsrc/Element/ElementsConfig.phpsrc/Element/InvalidateElementListener.phptests/Integration/Configuration/CollectConfigurationDataTest.phptests/Integration/Helpers/TestAssetFactory.phptests/Integration/Helpers/TestDocumentFactory.phptests/Integration/Helpers/TestObjectFactory.phptests/Integration/Invalidation/CancelInvalidationTest.phptests/Integration/Invalidation/InvalidateAdditionalTagTest.phptests/Integration/Invalidation/InvalidateAssetTest.phptests/Integration/Invalidation/InvalidateDocumentTest.phptests/Integration/Invalidation/InvalidateObjectTest.phptests/Integration/Tagging/CollectTagsDataTest.phptests/Integration/Tagging/TagAdditionalTagTest.phptests/Integration/Tagging/TagAssetTest.phptests/Integration/Tagging/TagDocumentTest.phptests/Integration/Tagging/TagObjectTest.phptests/Unit/Cache/CacheTagChecker/Element/AssetCacheTagCheckerTest.phptests/Unit/Cache/CacheTagChecker/Element/DocumentCacheTagCheckerTest.phptests/Unit/Cache/CacheTagChecker/Element/ObjectCacheTagCheckerTest.phptests/Unit/Element/DependencyInvalidatorTest.phptests/Unit/Element/InvalidateElementListenerTest.phptests/app/config/pimcore/classes/definition_TestObject.phptests/app/src/Controller/GetObjectController.php
| $element = $this->prophesize(TestObject::class); | ||
| $element->getType()->willReturn(ElementType::Object->value); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's find and examine the ElementType class
find . -name "ElementType.php" -type fRepository: teamneusta/pimcore-http-cache-bundle
Length of output: 109
🏁 Script executed:
# Also check the test file to understand the context
find . -name "DependencyInvalidatorTest.php" -type fRepository: teamneusta/pimcore-http-cache-bundle
Length of output: 130
🏁 Script executed:
# Search for Service::getElementType to understand how it works
rg "getElementType" --type php -B2 -A5Repository: teamneusta/pimcore-http-cache-bundle
Length of output: 833
🏁 Script executed:
# Find the Service class that's being used
rg "class Service" --type phpRepository: teamneusta/pimcore-http-cache-bundle
Length of output: 62
🏁 Script executed:
# Look for the getElementType method definition
rg "function getElementType" --type php -B2 -A10Repository: teamneusta/pimcore-http-cache-bundle
Length of output: 62
🏁 Script executed:
# Check what Service class is imported in ElementType.php
head -30 src/Element/ElementType.phpRepository: teamneusta/pimcore-http-cache-bundle
Length of output: 876
🏁 Script executed:
# Let's examine the test file to see the full context
cat -n tests/Unit/Element/DependencyInvalidatorTest.php | head -80Repository: teamneusta/pimcore-http-cache-bundle
Length of output: 3364
🏁 Script executed:
# Find and examine the DependencyInvalidator class itself
find . -name "DependencyInvalidator.php" -type fRepository: teamneusta/pimcore-http-cache-bundle
Length of output: 119
🏁 Script executed:
# Check if there are any stubs or mocks for Pimcore Service in the tests
rg "Service" tests/Unit/Element/DependencyInvalidatorTest.php -B2 -A2Repository: teamneusta/pimcore-http-cache-bundle
Length of output: 62
🏁 Script executed:
# Examine the DependencyInvalidator implementation
cat -n src/Element/DependencyInvalidator.phpRepository: teamneusta/pimcore-http-cache-bundle
Length of output: 2101
🏁 Script executed:
# Check what TestObject is - is it a real Pimcore class or a test stub?
rg "class TestObject" --type php -B2 -A5Repository: teamneusta/pimcore-http-cache-bundle
Length of output: 707
🏁 Script executed:
# Let's search for how Pimcore's Service::getElementType is typically implemented
# Check if there's any documentation or references to it in the codebase
rg "Service::getElementType" --type php -B3 -A3Repository: teamneusta/pimcore-http-cache-bundle
Length of output: 769
🏁 Script executed:
# Check the composer.json to see what version of Pimcore is being used
cat composer.json | grep -i pimcoreRepository: teamneusta/pimcore-http-cache-bundle
Length of output: 606
🏁 Script executed:
# Let's also check if there are any integration tests that actually use real Pimcore objects
cat tests/Integration/Invalidation/CancelInvalidationTest.php | head -100Repository: teamneusta/pimcore-http-cache-bundle
Length of output: 3281
Mock strategy incompatible with Service::getElementType().
Line 41-42 mocks $element->getType()->willReturn(...), but DependencyInvalidator::invalidate() calls ElementType::tryFromElement($source), which internally calls Pimcore\Model\Element\Service::getElementType($element). Pimcore's Service::getElementType() determines element type via instanceof checks on actual Pimcore objects, not by calling getType().
A Prophecy mock of TestObject won't pass these instanceof checks, causing Service::getElementType() to return null. The test then returns early at line 24 (if ($type === null || ...), passing for the wrong reason without testing the actual invalidation logic.
Use real Pimcore objects (via test factories, as in integration tests) instead of mocks, or mock Service::getElementType() directly if unit-testing this class in isolation.
Also applies to: lines 61-64, 86-89, 110-113, 134-137, 158-161, 185-188, 216-219, 241-245
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/Unit/Element/DependencyInvalidatorTest.php` around lines 41 - 42, The
test is mocking TestObject->getType(), but DependencyInvalidator::invalidate
uses ElementType::tryFromElement which calls
Pimcore\Model\Element\Service::getElementType and relies on instanceof checks,
so the Prophecy mock causes getElementType() to return null and the test exits
early; fix by either (A) replacing the Prophecy mocks of TestObject with real
Pimcore element instances created via your test factories (the same approach
used in integration tests) so instanceof checks pass, or (B) if you want a pure
unit test, stub/mocks Pimcore\Model\Element\Service::getElementType to return
the appropriate ElementType for your fake element before calling
DependencyInvalidator::invalidate; apply the same change to all similar cases
referenced (lines around the other getType() mocks).
Replace the mixed 'dependency traversal' / 'dependent' / 'DependencyInvalidator' naming with a consistent 'dependent element' vocabulary throughout: - DependencyInvalidator → DependentElementInvalidator - isDependencyTraversalEnabled() → isDependentElementsEnabled() - isDependentTypeEnabled() → isDependentAssetInvalidationEnabled() / isDependentDocumentInvalidationEnabled() / isDependentObjectInvalidationEnabled() - $assetDependencyTraversalEnabled → $assetDependentElementsEnabled - $assetDependencyTypes → $assetDependentElementTypes - Config key: invalidate_dependencies → invalidate_dependent_elements - Service ID: .dependency_invalidator → .dependent_element_invalidator - Test: DependencyInvalidatorTest → DependentElementInvalidatorTest Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The class never performed invalidation itself — it only resolved which elements should be invalidated. Renaming to DependentElementFinder with a findFor() method makes the responsibility clear and removes the callable indirection from InvalidateElementListener. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e_dependent_elements Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously, DependentElementFinder returned elements whose subtype or class was disabled in the main config, relying on RemoveDisabledTagsCacheInvalidator to silently drop the tags downstream. Now the finder checks isTypeEnabled() and isClassEnabled() after loading each element and skips it early, avoiding unnecessary event dispatches and invalidation calls. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each dependent element type (assets, documents, objects) under
invalidate_dependent_elements.types now accepts either a boolean
shorthand (assets: true) or a full config with types and classes:
invalidate_dependent_elements:
enabled: true
types:
assets: true
documents: false
objects:
enabled: true
types:
folder: false
classes:
MyIgnoredClass: false
Introduces DependentTypeConfig as a focused value object replacing the
flat boolean arrays in ElementsConfig. DependentElementFinder now checks
both the global element config and the dependent-specific config via
getDependentTypeConfig(), giving per-type control without duplicating
the global type/class filter logic.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ilter tests - Add 'Fine-grained control per dependent type' section to docs with mixed shorthand/full YAML example - Add two interaction tests verifying that global and dependent-specific subtype configs both apply independently (neither alone is sufficient) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ed71e85 to
949ecb6
Compare
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ElementsConfig::isDependentElementEnabled() to encapsulate two-layer type/class filtering, removing isElementEnabled from DependentElementFinder - Make ElementsConfig constructor private (force use of fromArray) - Make getDependentTypeConfig private (internal implementation detail) - Rename isClassEnabled -> isObjectClassEnabled to reflect object-only scope - Remove unused ElementType::configKey() - Move array @param annotations inline on promoted constructor params Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add default IDs to test factories so no-argument calls work - Replace TestDataObject with TestObject (the actual Pimcore class name) - Add 7 missing unit tests for asset/document subtype filtering in DependentElementFinder - Add integration tests for dependent element invalidation with type/class config - Update docs: show available subtypes per element type; expand fine-grained examples for assets/documents; fix dead link in README Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ve subtype tests
- Remove ->arg('$config', []) placeholder from services.php — the Extension
always sets the real value via setArgument(), so the placeholder was dead code
- Add positive-case unit tests for asset/document subtype allow-through in
DependentElementFinderTest to complement the existing negative/skip tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Accept ?string in isObjectClassEnabled (getClassName() returns string|null) - Add isDependentTypeEnabled pre-check in DependentElementFinder to skip repository lookups when the dependent type is entirely disabled - Fix integration tests: add enabled:true to objects config so source object tags pass through RemoveDisabledTagsCacheInvalidator - Fix TagDocumentTest assertion: test_document_link -> test_document_email Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use $this->image (Asset\Image) instead of $this->asset (Asset) in dependent_elements_are_not_invalidated_when_asset_is_updated: the manyToManyRelation field only allows asset type 'image' - Use assertEqualsCanonicalizing for document tags in collect_tags_for_type_document: tag insertion order (d1 vs d5) is non-deterministic across Pimcore versions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cb12102 to
09b29b1
Compare
…derTest ElementType::tryFromElement() delegates to Service::getElementType(), which uses instanceof checks — not getType(). Prophecy mocks extend the real class so instanceof works, but getType() stubs on source elements are never called. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary by CodeRabbit
New Features
Documentation