fix: Add proper methods in IAppManager for namespace handling

Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
This commit is contained in:
Côme Chilliet 2026-04-28 17:54:52 +02:00 committed by Côme Chilliet
parent 7c7b53163a
commit bdfe8ed77e
10 changed files with 81 additions and 37 deletions

View file

@ -76,6 +76,9 @@ class AppManager implements IAppManager {
/** @var array<string, true> */
private array $loadedApps = [];
/** @var string[] */
private $namespaceCache = [];
private ?AppConfig $appConfig = null;
private ?IURLGenerator $urlGenerator = null;
private ?INavigationManager $navigationManager = null;
@ -1197,4 +1200,44 @@ class AppManager implements IAppManager {
public function isAppCompatible(string $serverVersion, array $appInfo, bool $ignoreMax = false): bool {
return count($this->dependencyAnalyzer->analyzeServerVersion($serverVersion, $appInfo, $ignoreMax)) === 0;
}
#[\Override]
public function getAppNamespace(string $appId): string {
$topNamespace = 'OCA\\';
// Hit the cache!
if (isset($this->namespaceCache[$appId])) {
return $topNamespace . $this->namespaceCache[$appId];
}
$appInfo = $this->getAppInfo($appId);
if (isset($appInfo['namespace'])) {
$this->namespaceCache[$appId] = trim($appInfo['namespace']);
} else {
// If the tag is not found, fall back to uppercasing the first letter
$this->namespaceCache[$appId] = ucfirst($appId);
}
return $topNamespace . $this->namespaceCache[$appId];
}
#[\Override]
public function getAppFromNamespace(string $className): ?string {
$topNamespace = 'OCA\\';
if (str_starts_with($className, 'OC\\Core')) {
return 'core';
}
if (!str_starts_with($className, $topNamespace)) {
return null;
}
foreach ($this->namespaceCache as $appId => $namespace) {
if (str_starts_with($className, $topNamespace . $namespace . '\\')) {
return $appId;
}
}
return null;
}
}

View file

@ -30,9 +30,6 @@ use OCP\Server;
* Handles all the dependency injection, controllers and output flow
*/
class App {
/** @var string[] */
private static $nameSpaceCache = [];
/**
* Turns an app id into a namespace by either reading the appinfo.xml's
* namespace tag or uppercasing the appid's first letter
@ -40,36 +37,22 @@ class App {
* @param string $topNamespace the namespace which should be prepended to
* the transformed app id, defaults to OCA\
* @return string the starting namespace for the app
* @deprecated 34.0.0 use IAppManager::getAppNamespace
*/
public static function buildAppNamespace(string $appId, string $topNamespace = 'OCA\\'): string {
// Hit the cache!
if (isset(self::$nameSpaceCache[$appId])) {
return $topNamespace . self::$nameSpaceCache[$appId];
$appManager = Server::get(IAppManager::class);
$namespace = $appManager->getAppNamespace($appId);
if ($topNamespace !== 'OCA\\') {
return $topNamespace . substr($namespace, strlen('OCA\\'));
}
$appInfo = Server::get(IAppManager::class)->getAppInfo($appId);
if (isset($appInfo['namespace'])) {
self::$nameSpaceCache[$appId] = trim($appInfo['namespace']);
} else {
// if the tag is not found, fall back to uppercasing the first letter
self::$nameSpaceCache[$appId] = ucfirst($appId);
}
return $topNamespace . self::$nameSpaceCache[$appId];
return $namespace;
}
/**
* @deprecated 34.0.0 use IAppManager::getAppFromNamespace
*/
public static function getAppIdForClass(string $className, string $topNamespace = 'OCA\\'): ?string {
if (!str_starts_with($className, $topNamespace)) {
return null;
}
foreach (self::$nameSpaceCache as $appId => $namespace) {
if (str_starts_with($className, $topNamespace . $namespace . '\\')) {
return $appId;
}
}
return null;
return Server::get(IAppManager::class)->getAppFromNamespace($className);
}
/**

View file

@ -90,7 +90,7 @@ class Coordinator {
if ($appId === 'core') {
$appNameSpace = 'OC\\Core';
} else {
$appNameSpace = App::buildAppNamespace($appId);
$appNameSpace = $this->appManager->getAppNamespace($appId);
}
$applicationClassName = $appNameSpace . '\\AppInfo\\Application';
@ -147,7 +147,7 @@ class Coordinator {
}
$this->bootedApps[$appId] = true;
$appNameSpace = App::buildAppNamespace($appId);
$appNameSpace = $this->appManager->getAppNamespace($appId);
$applicationClassName = $appNameSpace . '\\AppInfo\\Application';
if (!class_exists($applicationClassName)) {
// Nothing to boot
@ -181,8 +181,8 @@ class Coordinator {
$this->eventLogger->end('bootstrap:boot_app:' . $appId);
}
public function isBootable(string $appId) {
$appNameSpace = App::buildAppNamespace($appId);
public function isBootable(string $appId): bool {
$appNameSpace = $this->appManager->getAppNamespace($appId);
$applicationClassName = $appNameSpace . '\\AppInfo\\Application';
return class_exists($applicationClassName)
&& in_array(IBootstrap::class, class_implements($applicationClassName), true);

View file

@ -76,6 +76,7 @@ use Psr\Log\LoggerInterface;
class DIContainer extends SimpleContainer implements IAppContainer {
private array $middleWares = [];
private ServerContainer $server;
private IAppManager $appManager;
public function __construct(
protected string $appName,
@ -93,6 +94,7 @@ class DIContainer extends SimpleContainer implements IAppContainer {
$server = \OC::$server;
}
$this->server = $server;
$this->appManager = $this->server->get(IAppManager::class);
$this->server->registerAppContainer($this->appName, $this);
// aliases
@ -363,6 +365,7 @@ class DIContainer extends SimpleContainer implements IAppContainer {
/**
* @param string $name
* @param list<class-string> $chain
* @return mixed
* @throws QueryException if the query could not be resolved
*/
@ -375,7 +378,7 @@ class DIContainer extends SimpleContainer implements IAppContainer {
return parent::query($name, chain: $chain);
} elseif ($this->appName === 'core' && str_starts_with($name, 'OC\\Core\\')) {
return parent::query($name, chain: $chain);
} elseif (str_starts_with($name, App::buildAppNamespace($this->appName) . '\\')) {
} elseif (str_starts_with($name, $this->appManager->getAppNamespace($this->appName) . '\\')) {
return parent::query($name, chain: $chain);
} elseif (
str_starts_with($name, 'OC\\AppFramework\\Services\\')

View file

@ -63,7 +63,7 @@ class MigrationService {
} else {
$appManager = Server::get(IAppManager::class);
$appPath = $appManager->getAppPath($this->appName);
$namespace = App::buildAppNamespace($this->appName);
$namespace = $appManager->getAppNamespace($this->appName);
$this->migrationsPath = "$appPath/lib/Migration";
$this->migrationsNamespace = $namespace . '\\Migration';

View file

@ -483,7 +483,7 @@ class Router implements IRouter {
} catch (AppPathNotFoundException) {
return [];
}
$appNameSpace = App::buildAppNamespace($app);
$appNameSpace = $this->appManager->getAppNamespace($app);
}
if (!file_exists($appControllerPath)) {
@ -553,7 +553,7 @@ class Router implements IRouter {
}
private function getApplicationClass(string $appName) {
$appNameSpace = App::buildAppNamespace($appName);
$appNameSpace = $this->appManager->getAppNamespace($appName);
$applicationClassName = $appNameSpace . '\\AppInfo\\Application';

View file

@ -115,7 +115,7 @@ class OC_App {
self::$alreadyRegistered[$key] = true;
// Register on PSR-4 composer autoloader
$appNamespace = App::buildAppNamespace($app);
$appNamespace = Server::get(IAppManager::class)->getAppNamespace($app);
\OC::$server->registerNamespace($app, $appNamespace);
if (file_exists($path . '/composer/autoload.php')) {

View file

@ -373,4 +373,18 @@ interface IAppManager {
* @since 32.0.0
*/
public function isAppCompatible(string $serverVersion, array $appInfo, bool $ignoreMax = false): bool;
/**
* Get the app namespace
*
* @since 34.0.0
*/
public function getAppNamespace(string $appId): string;
/**
* Get the app id for this namespace
*
* @since 34.0.0
*/
public function getAppFromNamespace(string $className): ?string;
}

View file

@ -36,6 +36,7 @@ class App {
* the transformed app id, defaults to OCA\
* @return string the starting namespace for the app
* @since 8.0.0
* @deprecated 34.0.0 use IAppManager::getAppNamespace
*/
public static function buildAppNamespace(string $appId, string $topNamespace = 'OCA\\'): string {
return \OC\AppFramework\App::buildAppNamespace($appId, $topNamespace);

View file

@ -61,7 +61,7 @@ class InfoXmlTest extends TestCase {
\OC_App::registerAutoloading($app, $appPath);
//Add the appcontainer
$applicationClassName = App::buildAppNamespace($app) . '\\AppInfo\\Application';
$applicationClassName = $this->appManager->getAppNamespace($app) . '\\AppInfo\\Application';
if (class_exists($applicationClassName)) {
$application = new $applicationClassName();
$this->addToAssertionCount(1);