*/ private array $factories = array(); /** * Resolved service instances keyed by id. * * @var array */ private array $instances = array(); /** * Ids currently being resolved, to detect circular dependencies. * * @var array */ private array $resolving = array(); /** * Register a service factory. * * @param string $id Service id. * @param callable(self):mixed $factory Receives the container, returns the service. * @throws LogicException When the id is already registered. */ public function set( string $id, callable $factory ): void { if ( $this->has( $id ) ) { throw new LogicException( sprintf( 'Service "%s" is already registered.', $id ) ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } $this->factories[ $id ] = $factory; } /** * Whether a service is registered. * * @param string $id Service id. */ public function has( string $id ): bool { return isset( $this->factories[ $id ] ); } /** * Resolve a service, building it on first use. * * @param string $id Service id. * @return mixed * @throws OutOfBoundsException When the id is not registered. * @throws LogicException When services depend on each other in a cycle. */ public function get( string $id ) { if ( array_key_exists( $id, $this->instances ) ) { return $this->instances[ $id ]; } if ( ! $this->has( $id ) ) { throw new OutOfBoundsException( sprintf( 'Service "%s" is not registered.', $id ) ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } if ( isset( $this->resolving[ $id ] ) ) { throw new LogicException( sprintf( 'Circular dependency while resolving "%s".', $id ) ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } $this->resolving[ $id ] = true; try { $this->instances[ $id ] = ( $this->factories[ $id ] )( $this ); } finally { unset( $this->resolving[ $id ] ); } return $this->instances[ $id ]; } }