From c8e4389b3cd9881bf505bf4ad86414826f226efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 10 Sep 2026 12:03:27 +0200 Subject: [PATCH 1/8] fix: Short-circuit aliases to avoid querying container for this MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/private/AppFramework/Utility/SimpleContainer.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php index 9d137117ef8f7..47b709ddad111 100644 --- a/lib/private/AppFramework/Utility/SimpleContainer.php +++ b/lib/private/AppFramework/Utility/SimpleContainer.php @@ -32,6 +32,9 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { protected Container $container; + /** @var array */ + private array $aliases = []; + public function __construct() { $this->container = new Container(); } @@ -152,6 +155,9 @@ public function resolve(string $name, array $chain = []): mixed { * @param list $chain */ protected function query(string $name, bool $autoload = true, array $chain = []): mixed { + if (isset($this->aliases[$name])) { + return $this->query($this->aliases[$name]); + } if (isset($this->container[$name])) { return $this->container[$name]; } @@ -210,11 +216,7 @@ public function registerService(string $name, Closure $closure, bool $shared = t * @param string $target the target that should be resolved instead */ public function registerAlias(string $alias, string $target): void { - $this->registerService( - $alias, - static fn (ContainerInterface $container): mixed => $container->get($target), - false, - ); + $this->aliases[$alias] = $target; } protected function registerDeprecatedAlias(string $alias, string $target): void { From 84b78fa8a69f7f6c1d3a5c937f0cac2b2cf6f98f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 10 Sep 2026 12:58:12 +0200 Subject: [PATCH 2/8] fixup! fix: Short-circuit aliases to avoid querying container for this MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- .../DependencyInjection/DIContainer.php | 2 ++ .../AppFramework/Utility/SimpleContainer.php | 13 +++++++++---- lib/private/ServerContainer.php | 1 + 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index f5680374add02..b6473c21d0be6 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -309,6 +309,7 @@ public function has($id): bool { */ #[\Override] protected function query(string $name, bool $autoload = true, array $chain = []): mixed { + $name = $this->resolveAlias($name); if ($name === 'AppName' || $name === 'appName') { return $this->appName; } @@ -340,6 +341,7 @@ protected function query(string $name, bool $autoload = true, array $chain = []) * @internal */ public function queryNoFallback($name, array $chain) { + $name = $this->resolveAlias($name); if (isset($this->container[$name])) { return $this->container[$name]; } elseif ($this->appName === 'settings' && str_starts_with($name, 'OC\\Settings\\')) { diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php index 47b709ddad111..73749f1281d83 100644 --- a/lib/private/AppFramework/Utility/SimpleContainer.php +++ b/lib/private/AppFramework/Utility/SimpleContainer.php @@ -52,7 +52,7 @@ public function get(string $id): mixed { #[\Override] 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->aliases[$id]) || isset($this->container[$id]) || class_exists($id); } /** @@ -155,9 +155,7 @@ public function resolve(string $name, array $chain = []): mixed { * @param list $chain */ protected function query(string $name, bool $autoload = true, array $chain = []): mixed { - if (isset($this->aliases[$name])) { - return $this->query($this->aliases[$name]); - } + $name = $this->resolveAlias($name); if (isset($this->container[$name])) { return $this->container[$name]; } @@ -219,6 +217,13 @@ public function registerAlias(string $alias, string $target): void { $this->aliases[$alias] = $target; } + protected function resolveAlias(string $name) : string { + if (isset($this->aliases[$name])) { + return $this->resolveAlias($this->aliases[$name]); + } + return $name; + } + protected function registerDeprecatedAlias(string $alias, string $target): void { $this->registerService($alias, function (ContainerInterface $container) use ($target, $alias): mixed { try { diff --git a/lib/private/ServerContainer.php b/lib/private/ServerContainer.php index 4c345e132b4cc..68b0f0292f54d 100644 --- a/lib/private/ServerContainer.php +++ b/lib/private/ServerContainer.php @@ -123,6 +123,7 @@ public function has($id, bool $noRecursion = false): bool { */ #[\Override] protected function query(string $name, bool $autoload = true, array $chain = []): mixed { + $name = $this->resolveAlias($name); if (str_starts_with($name, 'OCA\\')) { // Skip server container query for app namespace classes if (isset($this->container[$name])) { From 7ca9a35d5b012d7d00c28b4beb8a9a107f7e4223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 10 Sep 2026 13:59:45 +0200 Subject: [PATCH 3/8] fix: For OCA check app containers first, core after MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/private/ServerContainer.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/private/ServerContainer.php b/lib/private/ServerContainer.php index 68b0f0292f54d..63b1b7f26f333 100644 --- a/lib/private/ServerContainer.php +++ b/lib/private/ServerContainer.php @@ -125,11 +125,6 @@ public function has($id, bool $noRecursion = false): bool { protected function query(string $name, bool $autoload = true, array $chain = []): mixed { $name = $this->resolveAlias($name); if (str_starts_with($name, 'OCA\\')) { - // Skip server container query for app namespace classes - if (isset($this->container[$name])) { - return $this->container[$name]; - } - // Continue with general autoloading // In case the service starts with OCA\ we try to find the service in // the apps container first. if (($appContainer = $this->getAppContainerForService($name)) !== null) { From 0a84b58ba3ee6c37957040414ab4df82a7989a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 10 Sep 2026 14:02:29 +0200 Subject: [PATCH 4/8] chore: Remove useless try/catch for rethrowing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/private/ServerContainer.php | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/private/ServerContainer.php b/lib/private/ServerContainer.php index 63b1b7f26f333..08e7e25ec618f 100644 --- a/lib/private/ServerContainer.php +++ b/lib/private/ServerContainer.php @@ -125,17 +125,9 @@ public function has($id, bool $noRecursion = false): bool { protected function query(string $name, bool $autoload = true, array $chain = []): mixed { $name = $this->resolveAlias($name); if (str_starts_with($name, 'OCA\\')) { - // In case the service starts with OCA\ we try to find the service in - // the apps container first. + // In case the service starts with OCA\ we try to find the service in the apps container. if (($appContainer = $this->getAppContainerForService($name)) !== null) { - try { - return $appContainer->queryNoFallback($name, $chain); - } catch (QueryException $e) { - // Didn't find the service or the respective app container - // In this case the service won't be part of the core container, - // so we can throw directly - throw $e; - } + return $appContainer->queryNoFallback($name, $chain); } } From 6c45baca52cf0511ba3a1488941b84d000f24d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 10 Sep 2026 14:06:09 +0200 Subject: [PATCH 5/8] chore: Remove buggy short-circuit on server namespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit $this->has will almost always return true as it checks class_exists and also $this->server->has, so the if made no sense. Signed-off-by: Côme Chilliet --- lib/private/AppFramework/DependencyInjection/DIContainer.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index b6473c21d0be6..7702802805fed 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -314,11 +314,6 @@ protected function query(string $name, bool $autoload = true, array $chain = []) return $this->appName; } - $isServerClass = str_starts_with($name, 'OCP\\') || str_starts_with($name, 'OC\\'); - if ($isServerClass && !$this->has($name)) { - return $this->server->query($name, $autoload, $chain); - } - try { return $this->queryNoFallback($name, $chain); } catch (QueryException $firstException) { From bc6d4589371733d25324536e336466349469790f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 10 Sep 2026 14:21:53 +0200 Subject: [PATCH 6/8] fix: Avoid using an exception for a normal fallback situation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Building an exception object can be expensive, avoid building one only to fallback to server DI from applications. Signed-off-by: Côme Chilliet --- .../DependencyInjection/DIContainer.php | 20 +++++-------------- lib/private/ServerContainer.php | 6 ++++++ 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index 7702802805fed..e61b7ff52fe99 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -314,18 +314,11 @@ protected function query(string $name, bool $autoload = true, array $chain = []) return $this->appName; } - try { - return $this->queryNoFallback($name, $chain); - } catch (QueryException $firstException) { - try { - return $this->server->query($name, $autoload, $chain); - } catch (QueryException $secondException) { - if ($firstException->getCode() === 1) { - throw $secondException; - } - throw $firstException; - } + $result = $this->queryNoFallback($name, $chain); + if ($result !== null) { + return $result; } + return $this->server->query($name, $autoload, $chain); } /** @@ -336,7 +329,6 @@ protected function query(string $name, bool $autoload = true, array $chain = []) * @internal */ public function queryNoFallback($name, array $chain) { - $name = $this->resolveAlias($name); if (isset($this->container[$name])) { return $this->container[$name]; } elseif ($this->appName === 'settings' && str_starts_with($name, 'OC\\Settings\\')) { @@ -350,8 +342,6 @@ public function queryNoFallback($name, array $chain) { /* AppFramework services are scoped to the application */ return parent::query($name, chain: $chain); } - - throw new QueryException('Could not resolve ' . $name . '!' - . ' Class can not be instantiated', 1); + return null; } } diff --git a/lib/private/ServerContainer.php b/lib/private/ServerContainer.php index 08e7e25ec618f..6225ebf16431a 100644 --- a/lib/private/ServerContainer.php +++ b/lib/private/ServerContainer.php @@ -128,6 +128,12 @@ protected function query(string $name, bool $autoload = true, array $chain = []) // In case the service starts with OCA\ we try to find the service in the apps container. if (($appContainer = $this->getAppContainerForService($name)) !== null) { return $appContainer->queryNoFallback($name, $chain); + $result = $appContainer->queryNoFallback($name, $chain); + if ($result !== null) { + return $result; + } + throw new QueryException('Could not resolve ' . $name . '!' + . ' Class can not be instantiated', 1); } } From c53e284fde9ebf1dcec6dd45a00635ca65578db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 10 Sep 2026 17:20:33 +0200 Subject: [PATCH 7/8] fixup! fix: Short-circuit aliases to avoid querying container for this MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/private/AppFramework/Utility/SimpleContainer.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php index 73749f1281d83..e3ed185dce9ed 100644 --- a/lib/private/AppFramework/Utility/SimpleContainer.php +++ b/lib/private/AppFramework/Utility/SimpleContainer.php @@ -199,6 +199,9 @@ public function registerService(string $name, Closure $closure, bool $shared = t if (isset($this->container[$name])) { unset($this->container[$name]); } + if (isset($this->aliases[$name])) { + unset($this->aliases[$name]); + } if ($shared) { $this->container[$name] = $wrapped; } else { From 477b6506ebc453367bf64ce448b97571c8ebe2ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 10 Sep 2026 17:47:31 +0200 Subject: [PATCH 8/8] Resolve alias only once, before calling query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I’m not sure whether this is the good solution. What I’m not sure about is interactions between server and app containers when there are aliases, which may not be the same on all of them. Signed-off-by: Côme Chilliet --- .../AppFramework/DependencyInjection/DIContainer.php | 1 - lib/private/AppFramework/Utility/SimpleContainer.php | 9 ++++----- lib/private/ServerContainer.php | 1 - 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index e61b7ff52fe99..53570621ab756 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -309,7 +309,6 @@ public function has($id): bool { */ #[\Override] protected function query(string $name, bool $autoload = true, array $chain = []): mixed { - $name = $this->resolveAlias($name); if ($name === 'AppName' || $name === 'appName') { return $this->appName; } diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php index e3ed185dce9ed..e2a8c53347c43 100644 --- a/lib/private/AppFramework/Utility/SimpleContainer.php +++ b/lib/private/AppFramework/Utility/SimpleContainer.php @@ -46,7 +46,7 @@ public function __construct() { */ #[\Override] public function get(string $id): mixed { - return $this->query($this->sanitizeName($id)); + return $this->query($this->resolveAlias($this->sanitizeName($id))); } #[\Override] @@ -96,7 +96,7 @@ private function buildClassConstructorParameters(\ReflectionMethod $constructor, try { $builtIn = $parameterType !== null && ($parameterType instanceof ReflectionNamedType) && $parameterType->isBuiltin(); - return $this->query($resolveName, !$builtIn, $chain); + return $this->query($this->resolveAlias($resolveName), !$builtIn, $chain); } catch (ContainerExceptionInterface $e) { // Service not found, use the default value when available if ($parameter->isDefaultValueAvailable()) { @@ -106,7 +106,7 @@ private function buildClassConstructorParameters(\ReflectionMethod $constructor, if ($parameterType !== null && ($parameterType instanceof ReflectionNamedType) && !$parameterType->isBuiltin()) { $resolveName = $parameter->getName(); try { - return $this->query($resolveName, chain: $chain); + return $this->query($this->resolveAlias($resolveName), chain: $chain); } catch (ContainerExceptionInterface $e2) { // Pass null if typed and nullable if ($parameter->allowsNull() && ($parameterType instanceof ReflectionNamedType)) { @@ -151,11 +151,10 @@ public function resolve(string $name, array $chain = []): mixed { } /** - * @param string $name Already sanitized name + * @param string $name Already sanitized name and alias resolved * @param list $chain */ protected function query(string $name, bool $autoload = true, array $chain = []): mixed { - $name = $this->resolveAlias($name); if (isset($this->container[$name])) { return $this->container[$name]; } diff --git a/lib/private/ServerContainer.php b/lib/private/ServerContainer.php index 6225ebf16431a..cfee05694a14a 100644 --- a/lib/private/ServerContainer.php +++ b/lib/private/ServerContainer.php @@ -123,7 +123,6 @@ public function has($id, bool $noRecursion = false): bool { */ #[\Override] protected function query(string $name, bool $autoload = true, array $chain = []): mixed { - $name = $this->resolveAlias($name); if (str_starts_with($name, 'OCA\\')) { // In case the service starts with OCA\ we try to find the service in the apps container. if (($appContainer = $this->getAppContainerForService($name)) !== null) {