Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
- feature: Introduced `CacheScope` service for controlling when and whether cache tags are collected. Cache invalidation is active by default everywhere (including non-cacheable and admin-context requests) and only stops once `CacheScope::disable()` is called; tag collection instead requires the scope to be explicitly enabled for the current request
- feature: New `scope` configuration option (`controller` / `request`) to control when tag collection begins within a request
- feature: New `tag_fallback_document` configuration option to tag responses with fallback documents
- feature: New `CacheScope::withoutCollecting()`/`withCollecting()` methods to temporarily pause/resume tag collection for a block of code, and `isCollecting()` to check the current state
- feature: New `CacheScope::withoutInvalidating()`/`withInvalidating()` methods to temporarily pause/resume cache invalidation for a block of code, e.g. around a bulk import
- deprecation: `CacheActivator` is deprecated; use `CacheScope` instead
- deprecation: `CacheActivator::isCachingActive()` is deprecated; use `CacheScope::isInvalidating()` instead
- deprecation: `CacheActivator::activateCaching()` is deprecated; use `CacheScope::enable()` instead
Expand Down
4 changes: 2 additions & 2 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTagChecker\ElementCacheTagChecker;
use Neusta\Pimcore\HttpCacheBundle\Cache\CacheTagChecker\StaticCacheTagChecker;
use Neusta\Pimcore\HttpCacheBundle\Cache\ResponseTagger;
use Neusta\Pimcore\HttpCacheBundle\Cache\ResponseTagger\OnlyWhenEnabledResponseTagger;
use Neusta\Pimcore\HttpCacheBundle\Cache\ResponseTagger\OnlyWhenCollectingResponseTagger;
use Neusta\Pimcore\HttpCacheBundle\Cache\ResponseTagger\RemoveDisabledTagsResponseTagger;
use Neusta\Pimcore\HttpCacheBundle\Cache\ResponseTagger\TraceableResponseTagger;
use Neusta\Pimcore\HttpCacheBundle\CacheActivator;
Expand Down Expand Up @@ -75,7 +75,7 @@
->decorate('neusta_pimcore_http_cache.response_tagger', null, -99)
->args([service('.inner'), service('neusta_pimcore_http_cache.cache_tag_checker')]);

$services->set(null, OnlyWhenEnabledResponseTagger::class)
$services->set(null, OnlyWhenCollectingResponseTagger::class)
->decorate('neusta_pimcore_http_cache.response_tagger', null, -100)
->args([service('.inner'), service('neusta_pimcore_http_cache.cache_scope')]);

Expand Down
178 changes: 171 additions & 7 deletions doc/8-cache-scope.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ The cache scope controls whether and from which point in the request lifecycle c

Tagging and invalidation are gated independently:

- **Tagging** only happens while the scope is **enabled**. Tagging only ever makes sense for a request whose response
can actually be cached, so the scope must be explicitly activated (see [Automatic activation](#automatic-activation)).
- **Tagging** only happens while the scope is **enabled** and collection isn't paused (see
[Temporarily pausing collection](#temporarily-pausing-collection)). Tagging only ever makes sense for a request
whose response can actually be cached, so the scope must be explicitly activated (see
[Automatic activation](#automatic-activation)).
- **Invalidation** is active by default — everywhere, including non-cacheable requests such as an admin-UI save or a
custom `POST` endpoint — and only stops once `disable()` has been called. Invalidation does not depend on the scope
being enabled.
custom `POST` endpoint — and only stops once `disable()` has been called, or while temporarily paused via
`withoutInvalidating()` (see [Temporarily pausing invalidation](#temporarily-pausing-invalidation)). Invalidation
does not depend on the scope being enabled or on tag collection being paused.

Calling `disable()` always stops both tagging and invalidation, regardless of the above.

Expand Down Expand Up @@ -46,14 +49,14 @@ use Neusta\Pimcore\HttpCacheBundle\CacheScope;
final class MyService
{
public function __construct(
private readonly CacheScope $cacheScope,
private CacheScope $cacheScope,
) {
}

public function doSomething(): void
{
if ($this->cacheScope->isEnabled()) {
// tagging is currently enabled
if ($this->cacheScope->isCollecting()) {
// tagging is currently active
}

if ($this->cacheScope->isInvalidating()) {
Expand All @@ -66,5 +69,166 @@ final class MyService
}
```

### Temporarily pausing collection

Use `withoutCollecting()` to temporarily pause tag collection for a specific block of code.

> [!NOTE]
> `withoutCollecting()` only pauses response tag collection.
> It does not disable cache invalidation.
> Use `disable()` if you want to disable cache-related behavior entirely.

```php
use Neusta\Pimcore\HttpCacheBundle\CacheScope;

final class MyService
{
public function __construct(
private CacheScope $cacheScope,
) {
}

public function doSomething(): mixed
{
return $this->cacheScope->withoutCollecting(function (CacheScope $scope): mixed {
// cache tags are not collected here

return $this->loadSomething();
});
}

private function loadSomething(): mixed
{
// ...
}
}
```

### Temporarily resuming collection

Use `withCollecting()` to temporarily enable collection within a callback.
This can be useful inside `withoutCollecting()` when only a smaller inner section should collect cache tags again.

> [!NOTE]
> `withCollecting()` still respects `disable()`.
> If the scope was disabled for the current request or command, `withCollecting()` will not make the scope enabled.

```php
use Neusta\Pimcore\HttpCacheBundle\CacheScope;

final class MyService
{
public function __construct(
private readonly CacheScope $cacheScope,
) {
}

public function doSomething(): mixed
{
return $this->cacheScope->withoutCollecting(function (CacheScope $scope): mixed {
// cache tags are not collected here

$result = $scope->withCollecting(function (): mixed {
// cache tags are collected here again,
// unless the scope was disabled for the request or command

return $this->loadSomethingThatShouldBeTagged();
});

// cache tags are not collected here again

return $result;
});
}

private function loadSomethingThatShouldBeTagged(): mixed
{
// ...
}
}
```

### Temporarily pausing invalidation

Use `withoutInvalidating()` to temporarily pause cache invalidation for a specific block of code, e.g. around a bulk
import that saves many elements you don't want to trigger invalidation for.

> [!NOTE]
> `withoutInvalidating()` only pauses cache invalidation.
> It does not disable response tagging.
> Use `disable()` if you want to disable cache-related behavior entirely.

```php
use Neusta\Pimcore\HttpCacheBundle\CacheScope;

final class MyService
{
public function __construct(
private CacheScope $cacheScope,
) {
}

public function doSomething(): mixed
{
return $this->cacheScope->withoutInvalidating(function (CacheScope $scope): mixed {
// cache invalidation does not happen here

return $this->importElements();
});
}

private function importElements(): mixed
{
// ...
}
}
```

### Temporarily resuming invalidation

Use `withInvalidating()` to temporarily enable invalidation within a callback.
This can be useful inside `withoutInvalidating()` when only a smaller inner section should invalidate again.

> [!NOTE]
> `withInvalidating()` still respects `disable()`.
> If the scope was disabled for the current request or command, `withInvalidating()` will not make invalidation active.

```php
use Neusta\Pimcore\HttpCacheBundle\CacheScope;

final class MyService
{
public function __construct(
private readonly CacheScope $cacheScope,
) {
}

public function doSomething(): mixed
{
return $this->cacheScope->withoutInvalidating(function (CacheScope $scope): mixed {
// cache invalidation does not happen here

$result = $scope->withInvalidating(function (): mixed {
// cache invalidation happens here again,
// unless the scope was disabled for the request or command

return $this->saveElementThatShouldInvalidate();
});

// cache invalidation does not happen here again

return $result;
});
}

private function saveElementThatShouldInvalidate(): mixed
{
// ...
}
}
```

### Disabling the scope

To disable cache-related behavior for the current request or command, see
[Disabling caching behavior](9-disable-caching-behavior.md).
27 changes: 20 additions & 7 deletions doc/9-disable-caching-behavior.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
## Disable caching behavior

You may encounter situations where you need to disable the default caching behavior for specific requests.
You may encounter situations where you need to disable the default caching behavior for specific requests or commands.
This is especially useful for testing purposes.
To achieve this, you can use the `CacheScope` to disable tagging and invalidation behavior entirely.

To achieve this, you can use `CacheScope::disable()` to disable tagging and invalidation behavior entirely
for the current request or command.

> [!IMPORTANT]
> Once disabled, the cache scope cannot be re-enabled during the same request or command.
> Calling `enable()` afterward has no effect; the disabled state is only cleared when the scope is reset.
> Once disabled, the cache scope cannot be re-enabled by calling `enable()` or `withCollecting()`/`withInvalidating()` during the same request or command.
> The disabled state is only cleared when the scope is reset.

### Example for disabling caching behavior in a specific test case

```php
use Neusta\Pimcore\HttpCacheBundle\CacheScope;
use Pimcore\Test\KernelTestCase;

/** @test */
public function my_awesome_test_case(): void
final class MyAwesomeTest extends KernelTestCase
{
/** @test */
public function my_awesome_test_case(): void
{
// Disable the caching behavior
self::getContainer()->get(CacheScope::class)->disable();

// Your test code here

self::assertSame('this is amazing!', $result);
}
}
```

### Difference to temporarily pausing collection or invalidation

`disable()` disables cache-related behavior for the whole request or command.
If you only want to temporarily pause or resume tag collection or invalidation for a specific block of code, use the
cache scope methods described in [Cache Scope](8-cache-scope.md#temporarily-pausing-collection) instead.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use Neusta\Pimcore\HttpCacheBundle\Cache\ResponseTagger;
use Neusta\Pimcore\HttpCacheBundle\CacheScope;

final class OnlyWhenEnabledResponseTagger implements ResponseTagger
final class OnlyWhenCollectingResponseTagger implements ResponseTagger
{
public function __construct(
private readonly ResponseTagger $inner,
Expand All @@ -16,7 +16,7 @@ public function __construct(

public function tag(CacheTags $tags): void
{
if ($this->cacheScope->isEnabled()) {
if ($this->cacheScope->isCollecting()) {
$this->inner->tag($tags);
}
}
Expand Down
Loading