array, 'html' => string]` per the * {@see \Affilun\Application\Cache\RenderCache::get()} * contract; on hit the same shape is returned verbatim. * * MVP limitation: {@see self::forgetByPrefix()} delegates to * {@see TransientsCacheStore::forgetByPrefix()}, which is a * documented Phase-9-MVP no-op returning 0. The §14 * `CacheInvalidator` MVP overshoot already routes broad-flush * callers through whole-cache flush behavior on this backend. * * @package Affilun\Infrastructure\Cache */ declare(strict_types=1); namespace Affilun\Infrastructure\Cache; use Affilun\Application\Cache\RenderCache; /** * Transients-backed {@see RenderCache} adapter. * * See the file-level docblock for the entry shape, the spec * citations, and the documented Phase-9-MVP limitation on * {@see self::forgetByPrefix()}. */ final class TransientsRenderCache implements RenderCache { /** * Backing generic transient store; carries the transient-name * hashing, the envelope serialization, the clock, and the * TTL eviction logic shared across cache layers. */ private TransientsCacheStore $store; /** * Construct the adapter. * * @param TransientsCacheStore $store Backing transient store. */ public function __construct( TransientsCacheStore $store ) { $this->store = $store; } /** * Read the cached `['view_model' => array, 'html' => string]` * entry, or null on miss / expired / malformed envelope. * * @param string $key Cache key. * * @return array{view_model: array, html: string}|null */ public function get( string $key ): ?array { $entry = $this->store->getRaw( $key ); if ( null === $entry ) { return null; } $value = $entry->value(); if ( ! is_array( $value ) ) { return null; } if ( ! array_key_exists( 'view_model', $value ) || ! array_key_exists( 'html', $value ) ) { return null; } $viewModel = $value['view_model']; $html = $value['html']; if ( ! is_array( $viewModel ) || ! is_string( $html ) ) { return null; } /** @var array $viewModel */ return array( 'view_model' => $viewModel, 'html' => $html, ); } /** * Write the rendered entry with the given TTL in seconds. * * The stored value is the two-key associative array * `['view_model' => $viewModel, 'html' => $html]` per the * {@see RenderCache::get()} contract. Delegates TTL validation * to {@see TransientsCacheStore::putRaw()}. * * @param string $key Cache key. * @param array $viewModel View model snapshot. * @param string $html Rendered HTML. * @param int $ttlSeconds TTL in seconds; must be > 0. */ public function put( string $key, array $viewModel, string $html, int $ttlSeconds ): void { $this->store->putRaw( $key, array( 'view_model' => $viewModel, 'html' => $html, ), $ttlSeconds ); } /** * Remove the entry stored under `$key`. Idempotent. * * @param string $key Cache key. */ public function forget( string $key ): void { $this->store->forget( $key ); } /** * Phase-9-MVP no-op (delegates to * {@see TransientsCacheStore::flush()}, which documents why * Transients cannot be enumerated without a `$wpdb`-backed * sweep that this slice does not introduce). */ public function flush(): void { $this->store->flush(); } /** * Phase-9-MVP no-op returning 0 (delegates to * {@see TransientsCacheStore::forgetByPrefix()}). Targeted * prefix sweeps require options-table enumeration that this * slice does not implement. * * @param string $prefix Cache-key prefix (ignored). * * @return int Always 0 for the Phase-9-MVP backend. */ public function forgetByPrefix( string $prefix ): int { return $this->store->forgetByPrefix( $prefix ); } }