You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Cache observability rebuilt on Koriym.SemanticLogger (#178): typed, schema-validated open/event/close log tree replaces free-text RepositoryLoggerInterface messages. Off by default - install DevQueryRepositoryLogModule (dev) or ProdQueryRepositoryLogModule (prod) to record. See docs/reading-the-log.md, docs/why-the-log-records-everything.md, docs/what-the-log-proves.md (Japanese translations included).
New log contexts: cache_policy (#186), cache_error, pool_error, put_skipped, pre_write_cleanup, cdn_headers, conditional_request, invalidate (tri-state cdn, plus durationMs), command (source field), semantic_logger_error (core). Schemas in docs/schemas/context/.
New fields on existing save contexts: saved, tags, requestedTtl (save_etag), durationMs on the cache_hit/cache_miss close.
#[CacheLog] qualifier resolves the cache logger. SafeSemanticLogger/SessionStoreInterface/ProcessSession keep it safe across a serialized injector (#179). TopLevelAwareInterface lets a custom logger opt into manual-call scope rooting.
Direct (non-AOP) put()/putStatic()/putDonut()/purge()/invalidateTags() calls now open manual_store/manual_purge/manual_invalidate scopes.
LogSinkInterface/ShutdownFlush, LogWriterInterface (LogFileWriter/LogStreamWriter/PsrLogWriter), ConcurrentRuntimeInterface/HostRuntime (refuses to arm under RoadRunner/a Swoole coroutine). Detection is not exhaustive: FrankenPHP worker mode, ReactPHP, Amp, a long-lived CLI consumer, and a Swoole worker whose logger is built outside a coroutine must bind their own ConcurrentRuntimeInterface/SessionStoreInterface/LogSinkInterface.
ProdQueryRepositoryLogModule: buffers a session and applies RetentionPolicyInterface/KeepMutationsAndFailures at flush.
DevQueryRepositoryLogModule: writes one file per request plus latest.json for vendor/bin/stree.
demo/run-degraded.php and DemoLogCoverageTest: the demos now cover every context, schema enum value and command source, and self-validate against docs/schemas/context.
UriScopedHttpCacheInterface::isNotModifiedFor() / ScopedValidatorInterface::hasEtagFor(): opt-in ETag scoped to the requested URI (#197, #201). Entries written before this version cannot be scoped: each client pays one full (non-304) response after the upgrade, once.
Negative TTL is clamped to 0 at the QueryRepository/ResourceStorage boundary.
Dependency evidence in docs/reading-the-log.md is now stated per parent declaration (#188).
Deprecated
RepositoryLogger, RepositoryLoggerInterface, StructuredRepositoryLoggerInterface, NullRepositoryLogger: bound for BC but receive no internal events.
The update parameter of #[Cacheable]: it has no effect.
Removed
docs/schemas/repository-log.json (superseded by per-context schemas in docs/schemas/context/).
BEAR\QueryRepository\Log\NullSemanticLogger (koriym/semantic-logger 0.9 ships its own).
Cache logging call sites now emit typed contexts through SemanticLoggerInterface/#[CacheLog] instead of RepositoryLoggerInterface::log().
SaveDonutContext/SaveDonutViewContext: sMaxAge field renamed to requestedTtl.
SaveEtagContext/SaveDonutViewContext: surrogateKeys field renamed to tags.
A failed command write (4xx) now closes command_result instead of vanishing from the log.
Removed the post-save assert() in ResourceStorage::saveDonut() (it threw after saved: false was already logged).
Pre-write cleanup is recorded at the source (pre_write_cleanup marker) instead of inferred from tag correlation.
A donut refresh no longer appends an r marker to the ETag, so an unchanged recomposition still revalidates via If-None-Match.
New runtime dependency: koriym/semantic-logger.
Breaking: recording is off by default - #[CacheLog] binds to NullSemanticLogger unless a log module is installed.
Breaking (#190): Exception\CacheStoreFailure now marks the cache-failure boundary. An unreadable ETag pool answers the conditional request in full instead of failing it, and a donut write the store refuses now serves the rendered page instead of a 500.
Breaking (#208): a CDN purge failure now fails closed. Local pools invalidate first, the outcome is logged as invalidate.cdn: failed, then the purge exception propagates instead of being swallowed - a CDN outage can no longer look like a successful purge.
#[Refresh] no longer double-writes its own #[Cacheable] destination after a regenerating GET.
Breaking: renamed ResourceDonut::FOMRAT → FORMAT, EtagSetter::getEtagByEitireView() → getEtagByEntireView(), and the misspelled parameters $httpCacche → $httpCache (EtagSetter::getEtagByPartialBody()), $concheControlMaxAge → $cacheControlMaxAge (HeaderSetter::__invoke()) - a named-argument caller has to follow.
Fixed
Donut caching could not handle a non-200 response: saveView() required a 200-only validator, and DonutRepository/ResourceDonut did not restore the stored status code (#206, #207). Behaviour change: a #[CacheableResponse]/#[DonutCache] page answering 2xx or 3xx is now served with that status instead of crashing or degrading to 200 - a redirect that was reachable only once now persists until its cache entry is invalidated, so such a page needs the surrogate keys that invalidate it.
onPost on a #[Cacheable] class ran with no interceptor at all, silently dropping #[Refresh]/#[Purge] (#212, #214).
onPost on a #[Cacheable]/donut class missing an onGet-required parameter threw UnmatchedQuery uncaught - a regression from #214 above (#219, #220).
A write could be answered from cache without running, when a command method carried #[RefreshCache] or a method-level #[CacheableResponse]; both now bind DonutCommandInterceptor. DonutCacheModule also missed onPost in its write matcher.
DevEtagSetter/MobileEtagSetter set a validator on a non-200 response, where EtagSetter always skipped it; CdnCacheControlHeaderSetterInterface likewise now applies to 200 only.
A client If-None-Match token containing a PSR-6 reserved character reached the ETag pool as a cache key and threw a 500; such tokens (and *) are now dropped and the request answered in full.
An embedded child was rendered twice per parent store instead of reusing the execution the renderer already paid for.
A donut refresh advanced Last-Modified even for byte-identical content; Age is now derived from storedAt instead of Last-Modified.
putStatic()/putDonut() logged a negative lifetime verbatim while storage clamped what it stored.
A donut write failure on the AOP path (pool outage, renderer error) is now recorded as cache_error{operation: write}.
Behaviour change for 1.16.x installations: fixed a 1.16.0 regression where a resource declaring its own Surrogate-Key lost embed dependency tracking and kept serving purged children.
Deduplicated the Surrogate-Key header when a resource is written twice in one request.
ResourceStorage::saveEtag() used a hard-coded new UriTag() instead of the injected UriTagInterface.
Behaviour change for existing installations (#185): a donut template with no Surrogate-Key was untagged and unreachable by purge(); it now rebuilds on its first purge. Entries written before this version need a rewrite or a cache clear to become reachable.
A #[Cacheable] value entry with no renderer previously degraded every store to a warning and left the cache empty; it is now stored without rendering. The ETag validator falls back from the view to the body, a stored value entry no longer carries the renderer's Content-Type, and a custom EtagSetterInterface now receives $ro->view === null on the value path and must read the body instead.
A value-entry body PHP cannot serialize() (a Closure, for instance) is a separate, known limitation: the write's serialize()Exception propagates uncaught rather than degrading to CacheStoreFailure (#208's rule: only the store's own failure is swallowed). Keep such values out of a value entry's body, or catch Exception at the call site.
CliHttpCache::isNotModifiedFor() ignored the CLI request form's If-None-Match argument.
MatchQuery threw for an onGet parameter with a default value that a write's query omitted.
Two donut placeholders on one template line were captured as a single URI by a greedy pattern.
CDN-Cache-Control directives are now comma-separated per RFC 9213 (were space-separated).
Under AkamaiModule, a #[CacheableResponse] page lost its embedded children's invalidation tags (ResourceStorageInterface::saveDonutView() gained an optional $tags parameter).
MobileEtagSetter serialized the raw body and threw on any #[Embed]; it now hashes the materialized copy the way EtagSetter does.