From cfbcd4a72c2a5519f1bb2ac0e262e5eb49322dc1 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Wed, 28 Oct 2026 14:41:34 +0900 Subject: [PATCH 1/7] Update PHPStan to 2.3 and reset the fake purger through a method (#244) --- tests/FakeCountingPurger.php | 5 +++++ tests/RefreshAnnotatedCommandTest.php | 4 +++- vendor-bin/tools/composer.lock | 21 ++++++++++++++++----- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/tests/FakeCountingPurger.php b/tests/FakeCountingPurger.php index 750a2eac..359ef0c2 100644 --- a/tests/FakeCountingPurger.php +++ b/tests/FakeCountingPurger.php @@ -16,4 +16,9 @@ public function __invoke(string $tag): void { $this->tags[] = $tag; } + + public function reset(): void + { + $this->tags = []; + } } diff --git a/tests/RefreshAnnotatedCommandTest.php b/tests/RefreshAnnotatedCommandTest.php index 895f42ed..16f69eb1 100644 --- a/tests/RefreshAnnotatedCommandTest.php +++ b/tests/RefreshAnnotatedCommandTest.php @@ -60,7 +60,9 @@ public function testRefreshToCacheableDestinationWritesExactlyOnce(): void { $this->resource->get('app://self/user/profile', ['user_id' => 1]); $this->logger->events = []; - $this->purger->tags = []; + // A method call, not an assignment: PHPStan would keep the property narrowed to + // array{} across put(), which it cannot see appending to it. + $this->purger->reset(); $this->resource->put('app://self/user', ['id' => 1, 'name' => 'x', 'age' => 5]); diff --git a/vendor-bin/tools/composer.lock b/vendor-bin/tools/composer.lock index d7fac38b..cad57899 100644 --- a/vendor-bin/tools/composer.lock +++ b/vendor-bin/tools/composer.lock @@ -2198,11 +2198,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.31", + "version": "2.3.3", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ead89849d879fe203ce9292c6ef5e7e76f867b96", - "reference": "ead89849d879fe203ce9292c6ef5e7e76f867b96", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/4b57dc8ca7456b635d1661845f44bc060b02c6b0", + "reference": "4b57dc8ca7456b635d1661845f44bc060b02c6b0", "shasum": "" }, "require": { @@ -2225,6 +2225,17 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], "description": "PHPStan - PHP Static Analysis Tool", "keywords": [ "dev", @@ -2247,7 +2258,7 @@ "type": "github" } ], - "time": "2025-10-10T14:14:11+00:00" + "time": "2026-10-27T14:06:26+00:00" }, { "name": "psr/container", @@ -4176,5 +4187,5 @@ "platform-overrides": { "php": "8.4.99" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } From 7ce623bac10f359dad47b26d737023ae03014f2a Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Wed, 28 Oct 2026 14:42:21 +0900 Subject: [PATCH 2/7] State in llms-full.txt that only a cached embedded child is a dependency (#241) --- docs/llms-full.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 2af9efd5..560b0f25 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -133,24 +133,24 @@ new AkamaiModule($host, $clientToken, $clientSecret, $accessToken); ## Cache Dependencies -Resources automatically track dependencies. When resource A embeds resource B, invalidating B also invalidates A. +A child taken in with `#[Embed]` becomes a dependency of its parent when the child is itself cached, i.e. it carries an ETag or a `Surrogate-Key`: invalidating the child then invalidates the parent. A child without a cache attribute carries neither header and adds no dependency, so purging its URI leaves the parent as stored; give such a child a cache attribute. A value fetched eagerly in `onGet()` (`$this->resource->get(...)`) is not an embedded request and is not tracked. ```php #[Cacheable] class BlogPost extends ResourceObject { + #[Embed(rel: 'comments', src: 'app://self/comments{?id}')] public function onGet(int $id): static { - $this->body = [ - 'post' => $this->getPost($id), - 'comments' => $this->resource->get('app://self/comments', ['post_id' => $id]), - ]; + $this->body['post'] = $this->getPost($id); return $this; } } ``` -When comments are updated, BlogPost cache is automatically invalidated via surrogate keys. +With `app://self/comments` itself `#[Cacheable]`, updating the comments invalidates the BlogPost entry through its surrogate keys. + +On a `#[CacheableResponse]`/`#[DonutCache]` page the same rule applies to a child whose value `onGet()` reads: the whole template is dropped on purge. A child the page only renders as a hole is recomposed instead. ## Donut Caching From f2f86f1f6542dc63fd4f979db858eb13eb3bb965 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Wed, 28 Oct 2026 14:51:20 +0900 Subject: [PATCH 3/7] Drop the unused symfony/cache-contracts requirement and suggest the optional backends --- composer.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 4b03019a..24bd7e8f 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,6 @@ "ray/di": "^2.20", "ray/psr-cache-module": "^1.5.1", "symfony/cache": "^5.3 || ^6.0 || ^7.3", - "symfony/cache-contracts": "^2.4 || ^3.0", "symfony/polyfill-php83": "^v1.32.0" }, "require-dev": { @@ -40,12 +39,16 @@ }, "suggest": { "ext-redis": "Required for Redis cache storage backend", - "ext-memcached": "Required for Memcached cache storage backend" + "ext-memcached": "Required for Memcached cache storage backend", + "bear/fastly-module": "Required for FastlyModule (Fastly CDN purge)", + "mobiledetect/mobiledetectlib": "Required for MobileEtagModule (device-aware ETags)", + "predis/predis": "Redis client for StorageRedisDsnModule without ext-redis", + "relay/relay": "Redis client for StorageRedisDsnModule without ext-redis" }, "autoload": { "psr-4": { "BEAR\\QueryRepository\\": ["src/", "src-deprecated/"], - "BEAR\\RepositoryModule\\Annotation\\": ["src-annotation/", "src-annotation-deprecated"] + "BEAR\\RepositoryModule\\Annotation\\": ["src-annotation/", "src-annotation-deprecated/"] } }, "autoload-dev": { From 64c638004647a73a3d8df6789b093ded5d5ef43a Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Wed, 28 Oct 2026 14:53:53 +0900 Subject: [PATCH 4/7] Allow symfony/cache 8 --- CHANGELOG.md | 1 + composer.json | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f6d8bda..4373c647 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `ResourceDonut::FORMAT` is no longer public; use `ResourceDonut::placeholder()`. - Requires `koriym/semantic-logger` `^0.11`. Written logs use its `log` document shape, and diagnostic `kind` is now `code`. - Requires `bear/resource` `^1.22.4`. +- Allows `symfony/cache` 8 (PHP 8.4 or later), and requires `ray/psr-cache-module` `^1.6`, which supports it. - `ResourceDonut::refresh()`: a hole that throws while an already-cached `#[DonutCache]` page is recomposed now degrades to a fresh render instead of failing the request directly from the refresh, the same way a cache-store failure already does. A hole that keeps throwing still fails the request, now from the fresh render instead of the refresh. - `CacheDependency::depends()` no longer unsets the child's own `Surrogate-Key` header after copying it into the parent: a child that is both read by the parent and later rendered as a hole keeps its own key for its own independent cache entry. - `KeepMutationsAndFailures` no longer keeps a session only because it contains a `semantic_logger_error` diagnostic; a session is kept for a mutation, a failed effect or the sample. diff --git a/composer.json b/composer.json index 24bd7e8f..4626256c 100644 --- a/composer.json +++ b/composer.json @@ -23,8 +23,8 @@ "psr/log": "^1.1 || ^2.0 || ^3.0", "ray/aop": "^2.19.1", "ray/di": "^2.20", - "ray/psr-cache-module": "^1.5.1", - "symfony/cache": "^5.3 || ^6.0 || ^7.3", + "ray/psr-cache-module": "^1.6", + "symfony/cache": "^5.3 || ^6.0 || ^7.3 || ^8.0", "symfony/polyfill-php83": "^v1.32.0" }, "require-dev": { @@ -34,7 +34,7 @@ "mobiledetect/mobiledetectlib": "^3.74 || ^4.8", "phpunit/phpunit": "^11.0", "predis/predis": "^2.4", - "symfony/process": "^6.1 || ^7.1", + "symfony/process": "^6.1 || ^7.1 || ^8.0", "twig/twig": "^3.4.3" }, "suggest": { From f25363b8673c37b1e24e0ff9ad9d78aa327eb0fb Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Wed, 28 Oct 2026 15:45:35 +0900 Subject: [PATCH 5/7] Release 1.18.0 in the CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4373c647..c8bcb24e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [1.18.0] - 2026-10-28 ### Changed - Deploy this version with a new `CacheVersionModule` version (an app that binds its own cache adapter rather than this package's `Storage*Module`s must clear its pools instead): entries written by earlier versions may hold per-user content, and an earlier version reading this version's donut cache shows raw `[le2:...]` placeholders during a rolling deploy. From dd7663a5610296f69ccd9c2c56b261827b0b285f Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Wed, 28 Oct 2026 16:12:11 +0900 Subject: [PATCH 6/7] Suggest ext-relay rather than the unrelated relay/relay package, and drop the unreachable symfony/cache 5 range --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 4626256c..6011c3a2 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "ray/aop": "^2.19.1", "ray/di": "^2.20", "ray/psr-cache-module": "^1.6", - "symfony/cache": "^5.3 || ^6.0 || ^7.3 || ^8.0", + "symfony/cache": "^6.0 || ^7.3 || ^8.0", "symfony/polyfill-php83": "^v1.32.0" }, "require-dev": { @@ -43,7 +43,7 @@ "bear/fastly-module": "Required for FastlyModule (Fastly CDN purge)", "mobiledetect/mobiledetectlib": "Required for MobileEtagModule (device-aware ETags)", "predis/predis": "Redis client for StorageRedisDsnModule without ext-redis", - "relay/relay": "Redis client for StorageRedisDsnModule without ext-redis" + "ext-relay": "Redis client for StorageRedisDsnModule without ext-redis" }, "autoload": { "psr-4": { From c136ff02415219f6e46bcec901c9eee5c95a97ee Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Thu, 29 Oct 2026 01:37:04 +0900 Subject: [PATCH 7/7] Suggest only the packages this library's own modules need --- composer.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 6011c3a2..d68ae94d 100644 --- a/composer.json +++ b/composer.json @@ -41,9 +41,7 @@ "ext-redis": "Required for Redis cache storage backend", "ext-memcached": "Required for Memcached cache storage backend", "bear/fastly-module": "Required for FastlyModule (Fastly CDN purge)", - "mobiledetect/mobiledetectlib": "Required for MobileEtagModule (device-aware ETags)", - "predis/predis": "Redis client for StorageRedisDsnModule without ext-redis", - "ext-relay": "Redis client for StorageRedisDsnModule without ext-redis" + "mobiledetect/mobiledetectlib": "Required for MobileEtagModule (device-aware ETags)" }, "autoload": { "psr-4": {