diff --git a/lib/private/AppFramework/App.php b/lib/private/AppFramework/App.php index b18c95a2f0db6..d32df15017430 100644 --- a/lib/private/AppFramework/App.php +++ b/lib/private/AppFramework/App.php @@ -112,7 +112,7 @@ public static function getAppIdForClass(string $className, string $topNamespace * @param string $controllerName the name of the controller under which it is * stored in the DI container * @param string $methodName the method that you want to call - * @param DIContainer $container an instance of a pimple container. + * @param DIContainer $container an instance of a container. * @param array $urlParams list of URL parameters (optional) * @throws HintException */ @@ -254,7 +254,7 @@ public static function main(string $controllerName, string $methodName, DIContai * stored in the DI container * @param string $methodName the method that you want to call * @param array $urlParams an array with variables extracted from the routes - * @param DIContainer $container an instance of a pimple container. + * @param DIContainer $container an instance of container. */ public static function part(string $controllerName, string $methodName, array $urlParams, DIContainer $container) { diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index 93365582864e1..0606266924eeb 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -34,6 +34,7 @@ namespace OC\AppFramework\DependencyInjection; use OC; +use OC\AppFramework\App; use OC\AppFramework\Http; use OC\AppFramework\Http\Dispatcher; use OC\AppFramework\Http\Output; @@ -81,6 +82,7 @@ */ class DIContainer extends SimpleContainer implements IAppContainer { private string $appName; + private string $nameSpace; /** * @var array @@ -97,8 +99,8 @@ class DIContainer extends SimpleContainer implements IAppContainer { * @param ServerContainer|null $server */ public function __construct(string $appName, array $urlParams = [], ServerContainer $server = null) { - parent::__construct(); $this->appName = $appName; + $this->nameSpace = App::buildAppNamespace($this->appName); $this['appName'] = $appName; $this['urlParams'] = $urlParams; @@ -431,17 +433,19 @@ public function query(string $name, bool $autoload = true) { if ($name === 'AppName' || $name === 'appName') { return $this->appName; } + $name = $this->sanitizeName($name); + $name = $this->resolveAlias($name); $isServerClass = str_starts_with($name, 'OCP\\') || str_starts_with($name, 'OC\\'); - if ($isServerClass && !$this->has($name)) { - return $this->getServer()->query($name, $autoload); + if ($isServerClass && !self::has($name)) { + return $this->server->queryNoApps($name, $autoload); } try { return $this->queryNoFallback($name); } catch (QueryException $firstException) { try { - return $this->getServer()->query($name, $autoload); + return $this->server->query($name, $autoload); } catch (QueryException $secondException) { if ($firstException->getCode() === 1) { throw $secondException; @@ -456,20 +460,22 @@ public function query(string $name, bool $autoload = true) { * @return mixed * @throws QueryException if the query could not be resolved */ - public function queryNoFallback($name) { - $name = $this->sanitizeName($name); - + public function queryNoFallback(string $name) { if ($this->offsetExists($name)) { return parent::query($name); } elseif ($this->appName === 'settings' && str_starts_with($name, 'OC\\Settings\\')) { return parent::query($name); } elseif ($this->appName === 'core' && str_starts_with($name, 'OC\\Core\\')) { return parent::query($name); - } elseif (str_starts_with($name, \OC\AppFramework\App::buildAppNamespace($this->appName) . '\\')) { + } elseif (str_starts_with($name, $this->nameSpace)) { return parent::query($name); } throw new QueryException('Could not resolve ' . $name . '!' . ' Class can not be instantiated', 1); } + + protected function resolveAlias(string $name): string { + return parent::resolveAlias($this->server->resolveAlias($name)); + } } diff --git a/lib/private/AppFramework/Utility/ServiceFactory.php b/lib/private/AppFramework/Utility/ServiceFactory.php new file mode 100644 index 0000000000000..c6e405f5975c1 --- /dev/null +++ b/lib/private/AppFramework/Utility/ServiceFactory.php @@ -0,0 +1,19 @@ +container = $container; + $this->factory = $factory; + } + + public function get() { + return ($this->factory)($this->container); + } +} diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php index 83aed4381b342..daa8c79e6354e 100644 --- a/lib/private/AppFramework/Utility/SimpleContainer.php +++ b/lib/private/AppFramework/Utility/SimpleContainer.php @@ -33,7 +33,6 @@ use Closure; use OCP\AppFramework\QueryException; use OCP\IContainer; -use Pimple\Container; use Psr\Container\ContainerInterface; use ReflectionClass; use ReflectionException; @@ -42,15 +41,11 @@ use function class_exists; /** - * SimpleContainer is a simple implementation of a container on basis of Pimple + * SimpleContainer is a simple implementation of a container */ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { - /** @var Container */ - private $container; - - public function __construct() { - $this->container = new Container(); - } + protected array $items = []; + protected array $aliases = []; /** * @template T @@ -66,12 +61,13 @@ public function get(string $id) { public function has(string $id): bool { // If a service is no registered but is an existing class, we can probably load it - return isset($this->container[$id]) || class_exists($id); + return isset($this->items[$id]) || class_exists($id); } /** - * @param ReflectionClass $class the class to instantiate - * @return \stdClass the created class + * @template T of object + * @param ReflectionClass $class the class to instantiate + * @return T the created class * @suppress PhanUndeclaredClassInstanceof */ private function buildClass(ReflectionClass $class) { @@ -86,7 +82,7 @@ private function buildClass(ReflectionClass $class) { $resolveName = $parameter->getName(); // try to find out if it is a class or a simple parameter - if ($parameterType !== null && ($parameterType instanceof ReflectionNamedType) && !$parameterType->isBuiltin()) { + if (($parameterType instanceof ReflectionNamedType) && !$parameterType->isBuiltin()) { $resolveName = $parameterType->getName(); } @@ -100,7 +96,7 @@ private function buildClass(ReflectionClass $class) { return $parameter->getDefaultValue(); } - if ($parameterType !== null && ($parameterType instanceof ReflectionNamedType) && !$parameterType->isBuiltin()) { + if (($parameterType instanceof ReflectionNamedType) && !$parameterType->isBuiltin()) { $resolveName = $parameter->getName(); try { return $this->query($resolveName); @@ -138,15 +134,21 @@ public function resolve($name) { public function query(string $name, bool $autoload = true) { $name = $this->sanitizeName($name); - if (isset($this->container[$name])) { - return $this->container[$name]; + $name = $this->resolveAlias($name); + + if (isset($this->items[$name])) { + $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->registerService($name, function () use ($object) { - return $object; - }); + $this->items[$name] = $object; return $object; } @@ -158,7 +160,8 @@ public function query(string $name, bool $autoload = true) { * @param mixed $value */ public function registerParameter($name, $value) { - $this[$name] = $value; + $this->items[$name] = $value; + unset($this->aliases[$name]); } /** @@ -171,17 +174,12 @@ public function registerParameter($name, $value) { * @param bool $shared */ public function registerService($name, Closure $closure, $shared = true) { - $wrapped = function () use ($closure) { - return $closure($this); - }; $name = $this->sanitizeName($name); - if (isset($this[$name])) { - unset($this[$name]); - } + unset($this->aliases[$name]); if ($shared) { - $this[$name] = $wrapped; + $this->items[$name] = $closure; } else { - $this[$name] = $this->container->factory($wrapped); + $this->items[$name] = new ServiceFactory($this, $closure); } } @@ -193,9 +191,12 @@ public function registerService($name, Closure $closure, $shared = true) { * @param string $target the target that should be resolved instead */ public function registerAlias($alias, $target) { - $this->registerService($alias, function (ContainerInterface $container) use ($target) { - return $container->get($target); - }, false); + $alias = $this->sanitizeName($alias); + $target = $this->sanitizeName($target); + if ($alias === $target) { + throw new QueryNotFoundException('Can\'t alias to self'); + } + $this->aliases[$alias] = $target; } /* @@ -203,17 +204,14 @@ public function registerAlias($alias, $target) { * @return string */ protected function sanitizeName($name) { - if (isset($name[0]) && $name[0] === '\\') { - return ltrim($name, '\\'); - } - return $name; + return ltrim($name, '\\'); } /** * @deprecated 20.0.0 use \Psr\Container\ContainerInterface::has */ public function offsetExists($id): bool { - return $this->container->offsetExists($id); + return isset($this->items[$id]) || isset($this->aliases[$id]); } /** @@ -222,20 +220,43 @@ public function offsetExists($id): bool { */ #[\ReturnTypeWillChange] public function offsetGet($id) { - return $this->container->offsetGet($id); + return $this->query($id); } /** * @deprecated 20.0.0 use \OCP\IContainer::registerService */ public function offsetSet($id, $service): void { - $this->container->offsetSet($id, $service); + $this->items[$id] = $service; } /** * @deprecated 20.0.0 */ public function offsetUnset($offset): void { - $this->container->offsetUnset($offset); + unset($this->items[$offset]); + unset($this->aliases[$offset]); + } + + /** + * Check if we already have a resolved instance of $name + */ + public function isResolved($name): bool { + if (!isset($this->items[$name])) { + return false; + } + $item = $this->items[$name]; + if ($item instanceof ServiceFactory) { + return false; + } else { + return !is_callable($item); + } + } + + protected function resolveAlias(string $name): string { + while (isset($this->aliases[$name])) { + $name = $this->aliases[$name]; + } + return $name; } } diff --git a/lib/private/ServerContainer.php b/lib/private/ServerContainer.php index daa480a75d11d..02295af5fd43e 100644 --- a/lib/private/ServerContainer.php +++ b/lib/private/ServerContainer.php @@ -52,7 +52,6 @@ class ServerContainer extends SimpleContainer { * ServerContainer constructor. */ public function __construct() { - parent::__construct(); $this->appContainers = []; $this->namespaces = []; $this->hasNoAppContainer = []; @@ -137,16 +136,12 @@ public function has($id, bool $noRecursion = false): bool { * @deprecated 20.0.0 use \Psr\Container\ContainerInterface::get */ public function query(string $name, bool $autoload = true) { - $name = $this->sanitizeName($name); - - if (str_starts_with($name, 'OCA\\')) { - // Skip server container query for app namespace classes - try { - return parent::query($name, false); - } catch (QueryException $e) { - // Continue with general autoloading then - } + // if we have a resolved instance ourselves, there is no need to try and delegate + // we check this before doing any sanitization, because usually the name already is sanitized + if ($this->isResolved($name)) { + return $this->items[$name]; } + $name = $this->sanitizeName($name); // In case the service starts with OCA\ we try to find the service in // the apps container first. @@ -173,6 +168,13 @@ public function query(string $name, bool $autoload = true) { return parent::query($name, $autoload); } + public function queryNoApps(string $name, bool $autoload = true) { + if ($this->isResolved($name)) { + return $this->items[$name]; + } + return parent::query($name, $autoload); + } + /** * @internal * @param string $id