|string $id * @return T|mixed * @psalm-template S as class-string|string * @psalm-param S $id * @psalm-return (S is class-string ? T : mixed) */ public function get(string $id): mixed { return $this->query($id); } public function has(string $id): bool { // If a service is no registered but is an existing class, we can probably load it return array_key_exists($id, $this->items) || array_key_exists($id, $this->aliases) || class_exists($id); } /** * @template T of object * @param ReflectionClass $class the class to instantiate * @return T the created class * @suppress PhanUndeclaredClassInstanceof */ private function buildClass(ReflectionClass $class): object { $constructor = $class->getConstructor(); if ($constructor === null) { /* No constructor, return a instance directly */ return $class->newInstance(); } if (PHP_VERSION_ID >= 80400 && self::$useLazyObjects) { /* For PHP>=8.4, use a lazy ghost to delay constructor and dependency resolving */ /** @psalm-suppress UndefinedMethod */ return $class->newLazyGhost(function (object $object) use ($constructor): void { /** @psalm-suppress DirectConstructorCall For lazy ghosts we have to call the constructor directly */ $object->__construct(...$this->buildClassConstructorParameters($constructor)); }); } else { return $class->newInstanceArgs($this->buildClassConstructorParameters($constructor)); } } private function buildClassConstructorParameters(\ReflectionMethod $constructor): array { return array_map(function (ReflectionParameter $parameter) { $parameterType = $parameter->getType(); $resolveName = $parameter->getName(); // try to find out if it is a class or a simple parameter if (($parameterType instanceof ReflectionNamedType) && !$parameterType->isBuiltin()) { $resolveName = $parameterType->getName(); } try { $builtIn = $parameterType !== null && ($parameterType instanceof ReflectionNamedType) && $parameterType->isBuiltin(); return $this->query($resolveName, !$builtIn); } catch (ContainerExceptionInterface $e) { // Service not found, use the default value when available if ($parameter->isDefaultValueAvailable()) { return $parameter->getDefaultValue(); } if (($parameterType instanceof ReflectionNamedType) && !$parameterType->isBuiltin()) { $resolveName = $parameter->getName(); try { return $this->query($resolveName); } catch (ContainerExceptionInterface $e2) { // Pass null if typed and nullable if ($parameter->allowsNull() && ($parameterType instanceof ReflectionNamedType)) { return null; } // don't lose the error we got while trying to query by type throw new QueryException($e->getMessage(), (int)$e->getCode(), $e); } } throw $e; } }, $constructor->getParameters()); } public function resolve($name) { $baseMsg = 'Could not resolve ' . $name . '!'; try { $class = new ReflectionClass($name); if ($class->isInstantiable()) { return $this->buildClass($class); } else { throw new QueryException($baseMsg . ' Class can not be instantiated'); } } catch (ReflectionException $e) { // Class does not exist throw new QueryNotFoundException($baseMsg . ' ' . $e->getMessage()); } } public function query(string $name, bool $autoload = true) { $name = $this->sanitizeName($name); $name = $this->resolveAlias($name); if (array_key_exists($name, $this->items)) { $item = $this->items[$name]; if ($item instanceof ServiceFactory) { return $item->get(); } elseif (is_callable($item)) { $this->items[$name] = $item($this); } return $this->items[$name]; } if ($autoload) { $object = $this->resolve($name); $this->items[$name] = $object; return $object; } throw new QueryNotFoundException('Could not resolve ' . $name . '!' . get_class($this)); } /** * @param string $name * @param mixed $value */ public function registerParameter($name, $value) { $this->items[$name] = $value; unset($this->aliases[$name]); } /** * The given closure is call the first time the given service is queried. * The closure has to return the instance for the given service. * Created instance will be cached in case $shared is true. * * @param string $name name of the service to register another backend for * @param Closure $closure the closure to be called on service creation * @param bool $shared */ public function registerService($name, Closure $closure, $shared = true) { $name = $this->sanitizeName($name); unset($this->aliases[$name]); if ($shared) { $this->items[$name] = $closure; } else { $this->items[$name] = new ServiceFactory($this, $closure); } } /** * Shortcut for returning a service from a service under a different key, * e.g. to tell the container to return a class when queried for an * interface * @param string $alias the alias that should be registered * @param string $target the target that should be resolved instead */ public function registerAlias($alias, $target) { $alias = $this->sanitizeName($alias); $target = $this->sanitizeName($target); if ($alias === $target) { throw new QueryNotFoundException('Can\'t alias to self'); } unset($this->items[$alias]); $this->aliases[$alias] = $target; } /** * @param string $name * @return string */ protected function sanitizeName($name) { return ltrim($name, '\\'); } /** * @deprecated 20.0.0 use \Psr\Container\ContainerInterface::has */ public function offsetExists($id): bool { return array_key_exists($id, $this->items) || array_key_exists($id, $this->aliases); } /** * @deprecated 20.0.0 use \Psr\Container\ContainerInterface::get * @return mixed */ #[\ReturnTypeWillChange] public function offsetGet($id) { return $this->query($id); } /** * @deprecated 20.0.0 use \OCP\IContainer::registerService */ public function offsetSet($offset, $value): void { $this->items[$offset] = $value; } /** * @deprecated 20.0.0 */ public function offsetUnset($offset): void { unset($this->items[$offset]); unset($this->aliases[$offset]); } /** * Check if we already have a resolved instance of $name */ public function isResolved($name): bool { if (!array_key_exists($name, $this->items)) { return false; } $item = $this->items[$name]; if ($item instanceof ServiceFactory) { return false; } else { return !is_callable($item); } } protected function resolveAlias(string $name): string { while (array_key_exists($name, $this->aliases)) { $name = $this->aliases[$name]; } return $name; } }