= 0). */ private int $searchCacheTtlSeconds; /** * Lookup cache TTL in seconds (>= 0). */ private int $lookupCacheTtlSeconds; /** * Whether the render-output cache is enabled. */ private bool $renderCacheEnabled; /** * Render cache TTL in seconds (>= 0). */ private int $renderCacheTtlSeconds; /** * Provider-health cache TTL in seconds (>= 0). */ private int $providerHealthCacheTtlSeconds; /** * Construct a cache-settings snapshot with explicit values * for every field. * * @param bool $searchCacheEnabled Whether the search cache is enabled. * @param int $searchCacheTtlSeconds Search cache TTL in seconds. * @param int $lookupCacheTtlSeconds Lookup cache TTL in seconds. * @param bool $renderCacheEnabled Whether the render cache is enabled. * @param int $renderCacheTtlSeconds Render cache TTL in seconds. * @param int $providerHealthCacheTtlSeconds Provider-health cache TTL in seconds. * * @throws InvalidArgumentException When any TTL is negative. */ public function __construct( bool $searchCacheEnabled, int $searchCacheTtlSeconds, int $lookupCacheTtlSeconds, bool $renderCacheEnabled, int $renderCacheTtlSeconds, int $providerHealthCacheTtlSeconds ) { self::validateNonNegative( 'searchCacheTtlSeconds', $searchCacheTtlSeconds ); self::validateNonNegative( 'lookupCacheTtlSeconds', $lookupCacheTtlSeconds ); self::validateNonNegative( 'renderCacheTtlSeconds', $renderCacheTtlSeconds ); self::validateNonNegative( 'providerHealthCacheTtlSeconds', $providerHealthCacheTtlSeconds ); $this->searchCacheEnabled = $searchCacheEnabled; $this->searchCacheTtlSeconds = $searchCacheTtlSeconds; $this->lookupCacheTtlSeconds = $lookupCacheTtlSeconds; $this->renderCacheEnabled = $renderCacheEnabled; $this->renderCacheTtlSeconds = $renderCacheTtlSeconds; $this->providerHealthCacheTtlSeconds = $providerHealthCacheTtlSeconds; } /** * Construct a cache-settings snapshot with the canonical * §13.8 baseline values. */ public static function defaults(): self { return new self( true, 3600, 86400, false, 60, 300 ); } /** * Whether the search cache is enabled. */ public function searchCacheEnabled(): bool { return $this->searchCacheEnabled; } /** * Search cache TTL in seconds. */ public function searchCacheTtlSeconds(): int { return $this->searchCacheTtlSeconds; } /** * Lookup cache TTL in seconds. */ public function lookupCacheTtlSeconds(): int { return $this->lookupCacheTtlSeconds; } /** * Whether the render cache is enabled. */ public function renderCacheEnabled(): bool { return $this->renderCacheEnabled; } /** * Render cache TTL in seconds. */ public function renderCacheTtlSeconds(): int { return $this->renderCacheTtlSeconds; } /** * Provider-health cache TTL in seconds. */ public function providerHealthCacheTtlSeconds(): int { return $this->providerHealthCacheTtlSeconds; } /** * Produce a copy with `searchCacheEnabled` replaced. * * @param bool $searchCacheEnabled New flag. */ public function withSearchCacheEnabled( bool $searchCacheEnabled ): self { $clone = clone $this; $clone->searchCacheEnabled = $searchCacheEnabled; return $clone; } /** * Produce a copy with `searchCacheTtlSeconds` replaced. * * @param int $searchCacheTtlSeconds New TTL. * * @throws InvalidArgumentException When negative. */ public function withSearchCacheTtlSeconds( int $searchCacheTtlSeconds ): self { self::validateNonNegative( 'searchCacheTtlSeconds', $searchCacheTtlSeconds ); $clone = clone $this; $clone->searchCacheTtlSeconds = $searchCacheTtlSeconds; return $clone; } /** * Produce a copy with `lookupCacheTtlSeconds` replaced. * * @param int $lookupCacheTtlSeconds New TTL. * * @throws InvalidArgumentException When negative. */ public function withLookupCacheTtlSeconds( int $lookupCacheTtlSeconds ): self { self::validateNonNegative( 'lookupCacheTtlSeconds', $lookupCacheTtlSeconds ); $clone = clone $this; $clone->lookupCacheTtlSeconds = $lookupCacheTtlSeconds; return $clone; } /** * Produce a copy with `renderCacheEnabled` replaced. * * @param bool $renderCacheEnabled New flag. */ public function withRenderCacheEnabled( bool $renderCacheEnabled ): self { $clone = clone $this; $clone->renderCacheEnabled = $renderCacheEnabled; return $clone; } /** * Produce a copy with `renderCacheTtlSeconds` replaced. * * @param int $renderCacheTtlSeconds New TTL. * * @throws InvalidArgumentException When negative. */ public function withRenderCacheTtlSeconds( int $renderCacheTtlSeconds ): self { self::validateNonNegative( 'renderCacheTtlSeconds', $renderCacheTtlSeconds ); $clone = clone $this; $clone->renderCacheTtlSeconds = $renderCacheTtlSeconds; return $clone; } /** * Produce a copy with `providerHealthCacheTtlSeconds` * replaced. * * @param int $providerHealthCacheTtlSeconds New TTL. * * @throws InvalidArgumentException When negative. */ public function withProviderHealthCacheTtlSeconds( int $providerHealthCacheTtlSeconds ): self { self::validateNonNegative( 'providerHealthCacheTtlSeconds', $providerHealthCacheTtlSeconds ); $clone = clone $this; $clone->providerHealthCacheTtlSeconds = $providerHealthCacheTtlSeconds; return $clone; } /** * Report whether this snapshot equals the supplied one. * * Equality is structural: every field must match under * strict comparison. * * @param self $other Other snapshot to compare against. */ public function equals( self $other ): bool { return $this->searchCacheEnabled === $other->searchCacheEnabled && $this->searchCacheTtlSeconds === $other->searchCacheTtlSeconds && $this->lookupCacheTtlSeconds === $other->lookupCacheTtlSeconds && $this->renderCacheEnabled === $other->renderCacheEnabled && $this->renderCacheTtlSeconds === $other->renderCacheTtlSeconds && $this->providerHealthCacheTtlSeconds === $other->providerHealthCacheTtlSeconds; } /** * Serialize this snapshot for OptionStore persistence. * * @return array */ public function toArray(): array { return array( 'search_cache_enabled' => $this->searchCacheEnabled, 'search_cache_ttl_seconds' => $this->searchCacheTtlSeconds, 'lookup_cache_ttl_seconds' => $this->lookupCacheTtlSeconds, 'render_cache_enabled' => $this->renderCacheEnabled, 'render_cache_ttl_seconds' => $this->renderCacheTtlSeconds, 'provider_health_cache_ttl_seconds' => $this->providerHealthCacheTtlSeconds, ); } /** * Construct a snapshot from a {@see self::toArray()} payload. * * @param array $data Persisted payload. * * @throws InvalidArgumentException When `$data` is malformed. */ public static function fromArray( array $data ): self { return new self( self::requireBool( $data, 'search_cache_enabled' ), self::requireInt( $data, 'search_cache_ttl_seconds' ), self::requireInt( $data, 'lookup_cache_ttl_seconds' ), self::requireBool( $data, 'render_cache_enabled' ), self::requireInt( $data, 'render_cache_ttl_seconds' ), self::requireInt( $data, 'provider_health_cache_ttl_seconds' ) ); } /** * Validate a TTL field against the non-negative rule. * * @param string $field Field name for the error message. * @param int $value Raw value. * * @throws InvalidArgumentException When `$value` is negative. */ private static function validateNonNegative( string $field, int $value ): void { if ( $value < 0 ) { $message = sprintf( 'CacheSettings %s must be >= 0.', $field ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new InvalidArgumentException( $message ); } } /** * Read a required bool field from a {@see self::fromArray()} * payload. * * @param array $data Persisted payload. * @param string $key Field name. * * @throws InvalidArgumentException When the field is missing * or not a bool. */ private static function requireBool( array $data, string $key ): bool { if ( ! array_key_exists( $key, $data ) || ! is_bool( $data[ $key ] ) ) { $message = sprintf( 'CacheSettings payload must carry a bool `%s`.', $key ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new InvalidArgumentException( $message ); } return $data[ $key ]; } /** * Read a required int field from a {@see self::fromArray()} * payload. * * @param array $data Persisted payload. * @param string $key Field name. * * @throws InvalidArgumentException When the field is missing * or not an int. */ private static function requireInt( array $data, string $key ): int { if ( ! array_key_exists( $key, $data ) || ! is_int( $data[ $key ] ) ) { $message = sprintf( 'CacheSettings payload must carry an int `%s`.', $key ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new InvalidArgumentException( $message ); } return $data[ $key ]; } }