Skip to content

Scope cache activation - #48

Open
jdreesen wants to merge 11 commits into
mainfrom
scope-cache-activation
Open

Scope cache activation#48
jdreesen wants to merge 11 commits into
mainfrom
scope-cache-activation

Conversation

@jdreesen

@jdreesen jdreesen commented Jul 6, 2026

Copy link
Copy Markdown
Member

Introduces CacheScope, a runtime switch that controls when cache tagging and invalidation happen.

Tagging requires the scope to be explicitly activated for the current request or console command:

  • In console mode, the scope is activated automatically for every command.
  • In HTTP mode, the new scope config option controls when activation happens:
    • request: activated from the very beginning of the request (the previous, pre-CacheScope behavior).
    • controller (new default): activated only once the controller is invoked, so it ignores everything "generic" that happens before. This means, e.g.:
      • a document route's parent documents don't get tagged anymore (Pimcore may load all of them to determine the document's language)
      • the root document doesn't get tagged anymore for a non-document route (Pimcore loads it as a "fallback" document automatically)

Invalidation is active by default everywhere — including non-cacheable and admin-context requests, such as saving an element via the Pimcore admin UI or through a custom POST endpoint — independent of whether tagging is currently activated (see CacheScope::isInvalidating()).

Both tagging and invalidation stop once CacheScope::disable() is called, for the current request or command.

CacheActivator is deprecated in favor of CacheScope.

Summary by CodeRabbit

  • New Features

    • Added configurable cache scopes for request and controller execution.
    • Added automatic cache tagging for documents and data objects, with optional fallback document tagging.
    • Added cache-scope controls for enabling, disabling, resetting, and checking cache behavior.
    • Cache invalidation continues during non-cacheable requests unless explicitly disabled.
  • Deprecations

    • Legacy cache activation methods are deprecated; use the new cache-scope controls instead.
  • Documentation

    • Added guidance for cache scopes, disabling behavior, profiling, error handling, and contribution workflows.

Until now, the cache tagging/invalidation was active by default, and you could disable and enable it again.

From now on, the cache tagging/invalidation is inactive by default. It will be enabled for certain scopes automatically:
- in console mode it will be activated via a `ConsoleEvents::COMMAND` listener, so basically nothing changes here
- in HTTP mode, users can switch between "request" and "controller" modes
  - the "request" mode is basically the previous behavior
  - the "controller" mode scopes the cache to be only active once the controller gets called, so it ignores everything "generic" that happens before

The "controller" mode is the new default, as it skips tagging of elements that are loaded *before* the request-specific stuff happens, which means that e.g.,
- the parent documents don't get tagged anymore if you call a document route (as Pimcore may load all of them to determine e.g., the document's language)
- the root document doesn't get tagged anymore if you call a non-document route (as Pimcore loads it as a "fallback" document automatically)
@jdreesen
jdreesen requested a review from jan888adams July 6, 2026 16:47
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d647a841-7f9a-454b-97cd-4330bdcd06a7

📥 Commits

Reviewing files that changed from the base of the PR and between 0ab1e5f and 001d2db.

📒 Files selected for processing (24)
  • CHANGELOG.md
  • config/services.php
  • doc/2-configuration.md
  • doc/8-cache-scope.md
  • src/Cache/CacheInvalidator/OnlyWhenInvalidatingCacheInvalidator.php
  • src/Cache/ResponseTagger/OnlyWhenEnabledResponseTagger.php
  • src/CacheActivator.php
  • src/CacheScope.php
  • tests/Integration/Fixtures/update_object_route.php
  • tests/Integration/Helpers/ArrangeCacheTest.php
  • tests/Integration/Invalidation/InvalidateAssetTest.php
  • tests/Integration/Invalidation/InvalidateDocumentTest.php
  • tests/Integration/Invalidation/InvalidateFromNonCacheableRequestTest.php
  • tests/Integration/Invalidation/InvalidateObjectTest.php
  • tests/Integration/Tagging/CollectTagsDataTest.php
  • tests/Integration/Tagging/TagAdditionalTagTest.php
  • tests/Integration/Tagging/TagAssetTest.php
  • tests/Integration/Tagging/TagDocumentTest.php
  • tests/Integration/Tagging/TagObjectTest.php
  • tests/Unit/Cache/CacheInvalidator/OnlyWhenInvalidatingCacheInvalidatorTest.php
  • tests/Unit/Cache/ResponseTagger/OnlyWhenEnabledResponseTaggerTest.php
  • tests/Unit/CacheActivatorTest.php
  • tests/Unit/CacheScopeTest.php
  • tests/app/src/Controller/UpdateObjectController.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • doc/2-configuration.md

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


Walkthrough

The PR introduces CacheScope with separate tagging and invalidation states, adds HTTP and console listeners, deprecates CacheActivator, updates service wiring and configuration, and expands tests and documentation.

Changes

CacheScope state and decorators

Layer / File(s) Summary
CacheScope state model
src/CacheScope.php, src/CacheActivator.php, tests/Unit/CacheScopeTest.php, tests/Unit/CacheActivatorTest.php
CacheScope controls enablement, invalidation, disabling, and reset behavior. CacheActivator delegates its deprecated methods to CacheScope.
Cache-gated cache services
src/Cache/CacheInvalidator/..., src/Cache/ResponseTagger/..., tests/Unit/Cache/...
Cache invalidation checks isInvalidating(). Response tagging checks isEnabled().

HTTP and console activation

Layer / File(s) Summary
Cache scope listeners
src/EventListener/..., tests/Unit/EventListener/...
Console commands and cacheable HTTP requests activate the scope. Controller events tag documents or data objects and can apply fallback document tags.
Configuration and service wiring
src/DependencyInjection/..., config/services.php, tests/app/config/services.php
Adds scope and tag_fallback_document options. Registers CacheScope and both listeners. Wires the updated decorators and removes the test activator alias.

Validation and documentation

Layer / File(s) Summary
Integration coverage
tests/Integration/..., tests/app/src/Controller/...
Updates fixture setup for CacheScope and verifies tagging, invalidation, request scope, and invalidation from a non-cacheable POST request.
Documentation and release notes
README.md, CHANGELOG.md, doc/...
Documents CacheScope behavior, configuration, disabling rules, error handling, profiling, and contribution steps.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: 🔵 Low · up to 001d2

The PR changes when cache tagging and invalidation run. A legacy activation path can clear an explicit disable and unexpectedly resume cache operations when both APIs are used together, creating a bounded correctness risk; the change is mergeable with explicit owner follow-up.

Sequence Diagram(s)

sequenceDiagram
  participant Kernel
  participant HttpCacheScopeListener
  participant CacheScope
  participant ResponseTagger
  participant DocumentResolver

  Kernel->>HttpCacheScopeListener: Dispatch request or controller event
  HttpCacheScopeListener->>CacheScope: Enable scope for eligible HTTP flow
  HttpCacheScopeListener->>ResponseTagger: Tag route document or data object
  HttpCacheScopeListener->>DocumentResolver: Resolve fallback document when enabled
  DocumentResolver-->>HttpCacheScopeListener: Return document or null
  HttpCacheScopeListener->>ResponseTagger: Tag fallback document
Loading

Suggested reviewers: jan888adams

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.83% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 96 functions across 26 files. (3 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: introducing cache scope activation. It is concise and directly related to the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 70.83% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 96 functions across 26 files. (3 skipped: 3 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch scope-cache-activation

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jdreesen
jdreesen force-pushed the scope-cache-activation branch from 9a5671b to e4ccc29 Compare July 6, 2026 16:54

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/Integration/Invalidation/CancelInvalidationTest.php (1)

44-136: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Enable the cache scope before each mutation/delete.
InvalidateElementListener is wired through OnlyWhenActiveCacheInvalidator, so with CacheScope left inactive these assertions never reach invalidateTags() and the cancel path is not exercised.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Integration/Invalidation/CancelInvalidationTest.php` around lines 44 -
136, Enable the cache scope in each of the CancelInvalidationTest
mutation/delete cases before calling the relevant factory save,
setContent/setKey/setData, or delete operations. The assertions currently depend
on InvalidateElementListener being triggered through
OnlyWhenActiveCacheInvalidator, so activate CacheScope within each test (or in
shared setup for these scenarios) to ensure invalidateTags() is actually reached
and the cancel path is exercised.
🧹 Nitpick comments (1)
src/EventListener/HttpCacheScopeListener.php (1)

35-43: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider documenting the priority value.

priority: 1024 is a fairly unusual/high value with no inline explanation of why this listener must run before others on kernel.request. A short comment would help future maintainers avoid accidentally lowering it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/EventListener/HttpCacheScopeListener.php` around lines 35 - 43, The
HttpCacheScopeListener::onKernelRequest event listener uses a high
AsEventListener priority value without explaining why it must run early. Add a
short inline comment near the priority: 1024 declaration to document that this
listener needs to execute before other kernel.request listeners, so future
changes to the priority do not break cache scope enabling behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@config/services.php`:
- Around line 36-43: The deprecated cache_activator service is currently
instantiated separately from cache_scope, so its deactivateCaching state does
not affect the shared cache state used elsewhere. Update the service wiring in
services.php so CacheActivator resolves to the same CacheScope instance as
neusta_pimcore_http_cache.cache_scope, keeping the deprecated alias while
delegating behavior to the shared service. Focus on the service definitions for
CacheActivator and CacheScope so both names point to one underlying object.

In `@src/CacheActivator.php`:
- Around line 5-33: `CacheActivator` currently extends `CacheScope` but is wired
as a separate service, so its `activateCaching()` and `deactivateCaching()`
calls mutate only an isolated instance. Update `CacheActivator` to delegate to
the shared `CacheScope` service instance used elsewhere (for example via
composition or service aliasing), and keep the legacy methods
(`isCachingActive()`, `activateCaching()`, `deactivateCaching()`) forwarding to
that shared scope so they control the same cache state as the
listeners/decorators.

---

Outside diff comments:
In `@tests/Integration/Invalidation/CancelInvalidationTest.php`:
- Around line 44-136: Enable the cache scope in each of the
CancelInvalidationTest mutation/delete cases before calling the relevant factory
save, setContent/setKey/setData, or delete operations. The assertions currently
depend on InvalidateElementListener being triggered through
OnlyWhenActiveCacheInvalidator, so activate CacheScope within each test (or in
shared setup for these scenarios) to ensure invalidateTags() is actually reached
and the cancel path is exercised.

---

Nitpick comments:
In `@src/EventListener/HttpCacheScopeListener.php`:
- Around line 35-43: The HttpCacheScopeListener::onKernelRequest event listener
uses a high AsEventListener priority value without explaining why it must run
early. Add a short inline comment near the priority: 1024 declaration to
document that this listener needs to execute before other kernel.request
listeners, so future changes to the priority do not break cache scope enabling
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f4ac6839-32a0-4b33-be86-bce938c3bf93

📥 Commits

Reviewing files that changed from the base of the PR and between b72c9a8 and 9a5671b.

📒 Files selected for processing (28)
  • config/services.php
  • doc/8-disable-caching-behavior.md
  • src/Cache/CacheInvalidator/OnlyWhenActiveCacheInvalidator.php
  • src/Cache/ResponseTagger/OnlyWhenActiveResponseTagger.php
  • src/CacheActivator.php
  • src/CacheScope.php
  • src/DependencyInjection/Configuration.php
  • src/DependencyInjection/NeustaPimcoreHttpCacheExtension.php
  • src/EventListener/ConsoleCacheScopeListener.php
  • src/EventListener/HttpCacheScopeListener.php
  • tests/Integration/Configuration/CollectConfigurationDataTest.php
  • tests/Integration/Helpers/ArrangeCacheTest.php
  • tests/Integration/Invalidation/CancelInvalidationTest.php
  • tests/Integration/Invalidation/InvalidateAdditionalTagTest.php
  • tests/Integration/Invalidation/InvalidateAssetTest.php
  • tests/Integration/Invalidation/InvalidateDocumentTest.php
  • tests/Integration/Invalidation/InvalidateObjectTest.php
  • tests/Integration/Tagging/CollectTagsDataTest.php
  • tests/Integration/Tagging/TagAdditionalTagTest.php
  • tests/Integration/Tagging/TagAssetTest.php
  • tests/Integration/Tagging/TagDocumentTest.php
  • tests/Integration/Tagging/TagObjectTest.php
  • tests/Unit/Cache/CacheInvalidator/OnlyWhenActiveCacheInvalidatorTest.php
  • tests/Unit/Cache/ResponseTagger/OnlyWhenActiveResponseTaggerTest.php
  • tests/Unit/CacheActivatorTest.php
  • tests/Unit/CacheScopeTest.php
  • tests/Unit/EventListener/HttpCacheScopeListenerTest.php
  • tests/app/config/services.php
💤 Files with no reviewable changes (2)
  • tests/Integration/Helpers/ArrangeCacheTest.php
  • tests/app/config/services.php

Comment thread config/services.php Outdated
Comment thread src/CacheActivator.php
@jdreesen

jdreesen commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@jdreesen
jdreesen force-pushed the scope-cache-activation branch 3 times, most recently from 1f9ee1f to 3d924c8 Compare July 8, 2026 15:14
@jdreesen
jdreesen force-pushed the scope-cache-activation branch from 3d924c8 to 9fd3192 Compare July 8, 2026 15:15

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/CacheScope.php (1)

21-24: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider guarding startCollecting() against disabledForRequest for docblock consistency.

The docblock states "Has no effect if disableForRequest() was already called," but the method unconditionally sets $enabled = true. While isActive() correctly returns false in that case, adding an early return would make the internal state match the documented contract.

♻️ Optional refactor
     public function startCollecting(): void
     {
+        if ($this->disabledForRequest) {
+            return;
+        }
         $this->enabled = true;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/CacheScope.php` around lines 21 - 24, Update startCollecting() to return
immediately when disabledForRequest indicates collection was disabled for the
request; otherwise set enabled to true, keeping the internal state consistent
with the method’s documented no-op behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/CacheScope.php`:
- Around line 16-24: Fix the malformed PHPDoc `@see` annotation in
CacheScope::startCollecting by replacing the closing ] after disableForRequest()
with the required }, producing a valid {`@see` ...} reference.

---

Nitpick comments:
In `@src/CacheScope.php`:
- Around line 21-24: Update startCollecting() to return immediately when
disabledForRequest indicates collection was disabled for the request; otherwise
set enabled to true, keeping the internal state consistent with the method’s
documented no-op behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 98afd819-3fc3-4410-a714-df879ee263dc

📥 Commits

Reviewing files that changed from the base of the PR and between 34ae2a2 and 0ab1e5f.

📒 Files selected for processing (15)
  • CHANGELOG.md
  • README.md
  • doc/10-error-handling.md
  • doc/11-profiling.md
  • doc/12-contribution.md
  • doc/2-configuration.md
  • doc/8-cache-scope.md
  • doc/9-disable-caching-behavior.md
  • src/CacheActivator.php
  • src/CacheScope.php
  • src/EventListener/ConsoleCacheScopeListener.php
  • src/EventListener/HttpCacheScopeListener.php
  • tests/Integration/Invalidation/InvalidateAdditionalTagTest.php
  • tests/Unit/CacheScopeTest.php
  • tests/Unit/EventListener/HttpCacheScopeListenerTest.php
✅ Files skipped from review due to trivial changes (8)
  • doc/10-error-handling.md
  • doc/2-configuration.md
  • doc/12-contribution.md
  • doc/11-profiling.md
  • README.md
  • doc/8-cache-scope.md
  • CHANGELOG.md
  • doc/9-disable-caching-behavior.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • tests/Unit/CacheScopeTest.php
  • src/EventListener/ConsoleCacheScopeListener.php
  • src/CacheActivator.php
  • src/EventListener/HttpCacheScopeListener.php
  • tests/Integration/Invalidation/InvalidateAdditionalTagTest.php
  • tests/Unit/EventListener/HttpCacheScopeListenerTest.php

Comment thread src/CacheScope.php
Rename `Active` references to `Enabled` throughout codebase

Rename `disableForRequest` method to `disable` again

Update the docs

Refactor `CacheScopeTest` to improve readability and ensure consistent spacing

Replace `enable` and `disable` methods with `startCollecting` and `disableForRequest`
@jdreesen
jdreesen force-pushed the scope-cache-activation branch from 0f42eac to 884148d Compare July 10, 2026 18:38
jdreesen and others added 3 commits August 31, 2026 00:56
isCachingActive() pointed to a nonexistent CacheScope::isActive(),
the trigger_deprecation() package name was missing the "team" prefix,
and the deprecation version was stamped 0.7 even though 0.7.0/0.7.1
already shipped without it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CacheScope::isEnabled() gated both tag collection and invalidation, but
HttpCacheScopeListener only enables it for cacheable, non-admin GET/HEAD
requests. That meant invalidation was silently skipped for exactly the
requests that trigger it most (an admin-UI save, or any non-GET endpoint).

Add CacheScope::isInvalidating(), which is active by default everywhere
and only stops once disable() is called, independently of whether tag
collection is enabled for the current request. Wire the invalidator chain
to it (OnlyWhenEnabledCacheInvalidator -> OnlyWhenInvalidatingCacheInvalidator)
and map the deprecated CacheActivator::isCachingActive() to it, restoring
the pre-0.7 CacheActivator default.

Revive the pre-rewrite ArrangeCacheTest test helper: now that invalidation
runs unconditionally, fixture creation in integration tests needs to
suppress it explicitly, the same way it did before CacheScope existed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
reset() runs before enable() to preserve the legacy CacheActivator
semantics (deactivate then reactivate always works, per the BC test),
but it also clears a prior CacheScope::disable() on the same instance
since "disabled" carries no information about which API set it. This
can't be fixed without breaking one of the two behaviors, so it's
called out instead: don't mix this deprecated API with CacheScope on
the same instance.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@jdreesen

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant