keyFactoryOverride = $keyFactoryOverride; $this->searchCacheOverride = $searchCacheOverride; $this->lookupCacheOverride = $lookupCacheOverride; $this->renderCacheOverride = $renderCacheOverride; $this->providerHealthCacheOverride = $providerHealthCacheOverride; $this->invalidatorOverride = $invalidatorOverride; $this->wpdb = $wpdb; } /** * Bind the §6.3 key factory, the four §4 cache layers, and * the §14 invalidator into the registry. * * Resolution order: * * 1. Resolve a {@see Clock} from the registry under * {@see self::PERSISTENCE_CLOCK_BINDING} when published; * otherwise default-construct {@see SystemClock}. * * 2. Build (or accept the override for) each cache adapter * with its own dedicated {@see TransientsCacheStore}. * * 3. Build (or accept the override for) the * {@see CacheInvalidator}, wiring the four cache layers * and the key factory. * * 4. Bind every collaborator under the corresponding * `BINDING_*` key. * * @param ServiceProviderRegistry $registry Registry driving * this bootstrap pass. * * @throws LogicException When the persistence provider has * published a Clock binding that does * not implement {@see Clock}. */ public function register( ServiceProviderRegistry $registry ): void { $this->requirePersistenceProvider( $registry ); $clock = $this->resolveClock( $registry ); $keyFactory = $this->keyFactoryOverride ?? new CacheKeyFactory(); $searchCache = $this->searchCacheOverride ?? new TransientsSearchCache( new TransientsCacheStore( $clock, null, null, null, 'search', $this->wpdb ) ); $lookupCache = $this->lookupCacheOverride ?? new TransientsLookupCache( new TransientsCacheStore( $clock, null, null, null, 'lookup', $this->wpdb ) ); $renderCache = $this->renderCacheOverride ?? new TransientsRenderCache( new TransientsCacheStore( $clock, null, null, null, 'render', $this->wpdb ) ); $providerHealthCache = $this->providerHealthCacheOverride ?? new TransientsProviderHealthCache( new TransientsCacheStore( $clock, null, null, null, 'phealth', $this->wpdb ) ); $invalidator = $this->invalidatorOverride ?? new CacheInvalidator( $searchCache, $lookupCache, $renderCache, $providerHealthCache, $keyFactory, $this->resolveEmbedItemRepository( $registry ) ); $registry->bind( self::BINDING_CACHE_KEY_FACTORY, $keyFactory ); $registry->bind( self::BINDING_SEARCH_CACHE, $searchCache ); $registry->bind( self::BINDING_LOOKUP_CACHE, $lookupCache ); $registry->bind( self::BINDING_RENDER_CACHE, $renderCache ); $registry->bind( self::BINDING_PROVIDER_HEALTH_CACHE, $providerHealthCache ); $registry->bind( self::BINDING_CACHE_INVALIDATOR, $invalidator ); } /** * No-op — no WordPress hooks are registered by this provider. * * Cache adapters are passive collaborators consumed by other * providers (REST controllers, sync handlers, render * pipeline) at request-handling time; there is no * `cache_init`-shaped hook to subscribe to. * * @param HookRegistrar $hooks Shared hook registrar. */ public function boot( HookRegistrar $hooks ): void { unset( $hooks ); } /** * Resolve the optional {@see EmbedItemRepository} binding for * the §14.2 KI-002 per-product render-cache sweep, returning * null when the persistence provider has not (yet) published * the binding so the invalidator falls back on the Phase 9 * broad render-cache flush. * * @param ServiceProviderRegistry $registry Bootstrap registry. */ private function resolveEmbedItemRepository( ServiceProviderRegistry $registry ): ?EmbedItemRepository { $key = PersistenceServiceProvider::BINDING_EMBED_ITEM_REPOSITORY; if ( ! $registry->has( $key ) ) { return null; } $value = $registry->get( $key ); return $value instanceof EmbedItemRepository ? $value : null; } /** * Verify that {@see PersistenceServiceProvider} has registered * before this provider. * * Phase 9's persistence provider does not currently publish a * dedicated Clock binding, but it always publishes the * §13 option-store binding * ({@see PersistenceServiceProvider::BINDING_OPTION_STORE}). * That binding is used here as a sentinel so a misordered * bootstrap pipeline (CacheServiceProvider registered before * PersistenceServiceProvider) fails loudly during * {@see ServiceProviderRegistry::registerAll()} rather than at * the first cache miss. * * @param ServiceProviderRegistry $registry Bootstrap registry. * * @throws LogicException When the persistence binding is missing. */ private function requirePersistenceProvider( ServiceProviderRegistry $registry ): void { if ( ! $registry->has( PersistenceServiceProvider::BINDING_OPTION_STORE ) ) { $message = 'CacheServiceProvider requires PersistenceServiceProvider to register before it' . ' (missing option-store sentinel binding).'; // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new LogicException( $message ); } } /** * Resolve a {@see Clock} from the registry, falling back to * a default-constructed {@see SystemClock} when no binding is * published. * * Phase 9's {@see PersistenceServiceProvider} does not * currently publish a Clock binding, so the fallback is the * production default. Once that binding is published this * method picks it up automatically without further wiring * changes. * * @param ServiceProviderRegistry $registry Bootstrap registry. * * @throws LogicException When a binding exists but is not a * {@see Clock}. */ private function resolveClock( ServiceProviderRegistry $registry ): Clock { if ( ! $registry->has( self::PERSISTENCE_CLOCK_BINDING ) ) { return new SystemClock(); } $clock = $registry->get( self::PERSISTENCE_CLOCK_BINDING ); if ( ! $clock instanceof Clock ) { $message = sprintf( 'CacheServiceProvider expected Clock under "%s"; got %s.', self::PERSISTENCE_CLOCK_BINDING, is_object( $clock ) ? get_class( $clock ) : gettype( $clock ) ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new LogicException( $message ); } return $clock; } }