*/ private array $providers = array(); /** * Key/value bindings resolved by providers during register(). * * Keys are arbitrary strings chosen by the binding provider; values * may be any type. Consumers should treat unknown keys as missing * and handle them explicitly via {@see self::has()} or * {@see self::get()}. * * @var array */ private array $bindings = array(); /** * Whether {@see self::registerAll()} has already been invoked. */ private bool $registered = false; /** * Whether {@see self::bootAll()} has already been invoked. */ private bool $booted = false; /** * Append a provider to the pipeline. * * Providers are invoked during registerAll() / bootAll() in the order * they were added. Callers that require a specific ordering (for * example, a logging provider that must register before any other * provider tries to read the logger binding) should add that provider * first. * * @param ServiceProvider $provider Provider instance to append. * * @throws LogicException When called after registerAll() has run. */ public function add( ServiceProvider $provider ): void { if ( $this->registered ) { throw new LogicException( 'ServiceProviderRegistry::add() cannot be called after registerAll() has run.' ); } $this->providers[] = $provider; } /** * Return every provider that has been added, in insertion order. * * Primarily useful for tests and diagnostics; production code should * not need to enumerate providers directly. * * @return list */ public function providers(): array { return $this->providers; } /** * Report how many providers have been added. */ public function count(): int { return count( $this->providers ); } /** * Bind a value under the given key. * * Intended to be called from within a provider's register() method. * Re-binding an existing key is allowed and overwrites the previous * value; callers that want to detect collisions should check * {@see self::has()} first. * * @param string $key Binding key. * @param mixed $value Value to store. */ public function bind( string $key, $value ): void { $this->bindings[ $key ] = $value; } /** * Return whether a binding exists for the given key. * * @param string $key Binding key. */ public function has( string $key ): bool { return array_key_exists( $key, $this->bindings ); } /** * Resolve the value bound under the given key. * * @param string $key Binding key. * * @return mixed The stored value. * * @throws OutOfBoundsException When no binding exists for the key. */ public function get( string $key ) { if ( ! array_key_exists( $key, $this->bindings ) ) { throw new OutOfBoundsException( // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are developer-facing and never rendered to HTML. sprintf( 'No binding registered for key "%s".', $key ) ); } return $this->bindings[ $key ]; } /** * Report whether registerAll() has already been invoked. */ public function isRegistered(): bool { return $this->registered; } /** * Report whether bootAll() has already been invoked. */ public function isBooted(): bool { return $this->booted; } /** * Invoke register() on every provider in insertion order. * * Safe to call exactly once per registry. Subsequent calls are no-ops * so that accidental double-bootstrapping does not corrupt bindings. */ public function registerAll(): void { if ( $this->registered ) { return; } foreach ( $this->providers as $provider ) { $provider->register( $this ); } $this->registered = true; } /** * Invoke boot() on every provider in insertion order. * * Must be called after registerAll(); calling it first will throw so * that ordering bugs surface loudly instead of leaving half-wired * providers. Like registerAll(), repeat calls are no-ops. * * @param HookRegistrar $hooks Shared hook registrar passed to each * provider's boot() implementation. * * @throws LogicException When called before registerAll(). */ public function bootAll( HookRegistrar $hooks ): void { if ( ! $this->registered ) { throw new LogicException( 'ServiceProviderRegistry::bootAll() cannot be called before registerAll().' ); } if ( $this->booted ) { return; } foreach ( $this->providers as $provider ) { $provider->boot( $hooks ); } $this->booted = true; } }