*/ private array $factories = array(); /** * Resolved singleton instances. * * @var array */ private array $instances = array(); /** * Register a service factory. * * The factory receives the container as its only argument, enabling * constructor injection of other services. * * @param string $id Service identifier (typically FQCN). * @param callable $factory Factory closure: fn(ServiceContainer): object. * * @return void */ public function register( string $id, callable $factory ): void { $this->factories[ $id ] = $factory; // Clear any previously resolved instance so the new factory takes effect. unset( $this->instances[ $id ] ); } /** * Resolve a service by its identifier. * * Services are lazily instantiated and cached as singletons for the * lifetime of the container. * * @param string $id Service identifier. * * @return object * * @throws InvalidArgumentException If the service is not registered. */ public function get( string $id ): object { if ( isset( $this->instances[ $id ] ) ) { return $this->instances[ $id ]; } if ( ! isset( $this->factories[ $id ] ) ) { throw new InvalidArgumentException( esc_html( sprintf( 'Service "%s" is not registered in the container.', $id ) ) ); } $this->instances[ $id ] = ( $this->factories[ $id ] )( $this ); return $this->instances[ $id ]; } /** * Check whether a service is registered. * * @param string $id Service identifier. * * @return bool */ public function has( string $id ): bool { return isset( $this->factories[ $id ] ); } /** * Set an already-constructed instance directly. * * Useful for binding values that don't need a factory (e.g., config arrays * wrapped in an object, or test doubles). * * @param string $id Service identifier. * @param object $instance The pre-built instance. * * @return void */ public function set( string $id, object $instance ): void { $this->instances[ $id ] = $instance; // Remove factory if one existed — the explicit instance takes precedence. unset( $this->factories[ $id ] ); } }