*/ private array $providersByKey; /** * Construct a registry with the supplied provider map. * * Validation: * * - Empty map is allowed. * * - Every key MUST match {@see self::KEY_PATTERN}. * Non-string keys, empty-string keys, and keys with * uppercase / non-ASCII characters all raise * {@see InvalidArgumentException}. * * - Every value MUST implement {@see ProviderInterface}. * Other types raise {@see InvalidArgumentException}. * * - The array key MUST equal the entry's * {@see ProviderInterface::key()} value. The cross-check * rules out registries whose array key disagrees with the * provider's own canonical key — a class of programming * error that would otherwise surface only at the §10.3 * enumeration boundary. * * @param array $providersByKey Provider map. * * @throws InvalidArgumentException When any registry entry * violates the validation * rules above. */ public function __construct( array $providersByKey ) { $normalized = array(); foreach ( $providersByKey as $key => $provider ) { if ( ! is_string( $key ) ) { $message = sprintf( 'ProviderRegistry: provider keys must be strings; got %s.', gettype( $key ) ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new InvalidArgumentException( $message ); } if ( preg_match( self::KEY_PATTERN, $key ) !== 1 ) { $message = sprintf( 'ProviderRegistry: provider key "%s" must match ^[a-z][a-z0-9_]*$.', $key ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new InvalidArgumentException( $message ); } if ( ! $provider instanceof ProviderInterface ) { $message = sprintf( 'ProviderRegistry: provider["%s"] must implement ProviderInterface; got %s.', $key, is_object( $provider ) ? get_class( $provider ) : gettype( $provider ) ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new InvalidArgumentException( $message ); } $providerKey = $provider->key(); if ( $providerKey !== $key ) { $message = sprintf( 'ProviderRegistry: registry key "%s" must match provider key "%s".', $key, $providerKey ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new InvalidArgumentException( $message ); } $normalized[ $key ] = $provider; } $this->providersByKey = $normalized; } /** * Return the full provider map in registration order. * * @return array */ public function all(): array { return $this->providersByKey; } /** * Report whether a provider is registered under the given key. * * @param string $key §4 stable provider key. */ public function has( string $key ): bool { return array_key_exists( $key, $this->providersByKey ); } /** * Resolve the provider registered under the given key. * * @param string $key §4 stable provider key. * * @throws InvalidArgumentException When no provider is * registered under `$key`. */ public function get( string $key ): ProviderInterface { if ( ! array_key_exists( $key, $this->providersByKey ) ) { $message = sprintf( 'ProviderRegistry: no provider registered under "%s".', $key ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new InvalidArgumentException( $message ); } return $this->providersByKey[ $key ]; } /** * Return the registered keys in registration order. * * @return list */ public function keys(): array { return array_keys( $this->providersByKey ); } }