From bba4efad407f5d55c298d9b679fbc85a0d8bc7ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 16 Jul 2026 11:13:19 +0200 Subject: [PATCH 01/15] feat: Add a PhpDumpCache service for caching arrays as PHP files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This leverages opcache to be as fast as possible. Can be used for autoloading class maps, but also for other kind of pre-computed cached arrays (routes, occ commands, …). Signed-off-by: Côme Chilliet --- lib/private/PhpDumpCache.php | 111 +++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 lib/private/PhpDumpCache.php diff --git a/lib/private/PhpDumpCache.php b/lib/private/PhpDumpCache.php new file mode 100644 index 0000000000000..4bc4af2b3fbe8 --- /dev/null +++ b/lib/private/PhpDumpCache.php @@ -0,0 +1,111 @@ +tempDirectory = $dir; + return $this; + } + + /** + * Loads class list from cache. + */ + public function loadCache(array $cacheKey): ?array { + $file = $this->generateCacheFileName($cacheKey); + + // Solving atomicity to work everywhere + // 1) We want to do as little as possible IO calls on production and also directory and file can be not writable (#19) + // so on Linux we include the file directly without shared lock, therefore, the file must be created atomically by renaming. + // 2) On Windows file cannot be renamed-to while is open (ie by include() #11), so we have to acquire a lock. + $lock = defined('PHP_WINDOWS_VERSION_BUILD') + ? $this->acquireLock("$file.lock", LOCK_SH) + : null; + + try { + $data = @include $file; // @ file may not exist + if (is_array($data)) { + return $data; + } + + return null; + } finally { + if ($lock) { + flock($lock, LOCK_UN); // release shared lock + } + } + } + + /** + * Writes class list to cache. + * @param ?resource $lock + */ + public function saveCache(array $cacheKey, array $data, $lock = null): void { + // we have to acquire a lock to be able safely rename file + // on Linux: that another thread does not rename the same named file earlier + // on Windows: that the file is not read by another thread + $file = $this->generateCacheFileName($cacheKey); + $lock = $lock ?: $this->acquireLock("$file.lock", LOCK_EX); + $code = "tempDirectory) { + throw new \LogicException('Set path to temporary directory using setTempDirectory().'); + } + + return $this->tempDirectory . '/' . md5(serialize($cacheKey)) . '.php'; + } +} From bfb3bd5f650373d30eb683c93e0592480661a821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 16 Jul 2026 11:15:42 +0200 Subject: [PATCH 02/15] feat: Use a custom autoloader instead of composer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The idea is to cache to disk a static classmap with all classes from core and applications. Signed-off-by: Côme Chilliet --- console.php | 1 + lib/OC.php | 12 +- lib/private/App/AppManager.php | 2 + .../AppFramework/Bootstrap/Coordinator.php | 5 +- lib/private/Autoloader.php | 468 ++++++++++++++++++ lib/private/legacy/OC_App.php | 15 +- 6 files changed, 496 insertions(+), 7 deletions(-) create mode 100644 lib/private/Autoloader.php diff --git a/console.php b/console.php index b5a000e8873a0..98b1db0dc1abf 100644 --- a/console.php +++ b/console.php @@ -113,6 +113,7 @@ function exceptionHandler($exception) { $exitCode = 255; } + var_dump(\OC::$autoloader->getStats()); exit($exitCode); } catch (Exception $ex) { exceptionHandler($ex); diff --git a/lib/OC.php b/lib/OC.php index b2b4f65fee004..413a3dbfa160c 100644 --- a/lib/OC.php +++ b/lib/OC.php @@ -89,6 +89,7 @@ class OC { * @psalm-suppress ImpureStaticProperty */ public static \Composer\Autoload\ClassLoader $composerAutoloader; + public static \OC\Autoloader $autoloader; /** * @psalm-suppress ImpureStaticProperty @@ -675,9 +676,16 @@ public static function boot(): void { self::$CLI = (php_sapi_name() == 'cli'); + require_once __DIR__ . '/private/PhpDumpCache.php'; + require_once __DIR__ . '/private/Autoloader.php'; + $phpDumpCache = new \OC\PhpDumpCache(OC::$SERVERROOT . '/temp'); + self::$autoloader = new \OC\Autoloader($phpDumpCache); + self::$autoloader->addDirectory(OC::$SERVERROOT . '/lib'); + self::$autoloader->addDirectory(OC::$SERVERROOT . '/core'); + self::$autoloader->register(); // Add default composer PSR-4 autoloader, ensure apcu to be disabled - self::$composerAutoloader = require_once OC::$SERVERROOT . '/lib/composer/autoload.php'; - self::$composerAutoloader->setApcuPrefix(null); + // self::$composerAutoloader = require_once OC::$SERVERROOT . '/lib/composer/autoload.php'; + // self::$composerAutoloader->setApcuPrefix(null); // setup 3rdparty autoloader $vendorAutoLoad = OC::$SERVERROOT . '/3rdparty/autoload.php'; diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php index 4b802f72c34a5..5d836757e7004 100644 --- a/lib/private/App/AppManager.php +++ b/lib/private/App/AppManager.php @@ -283,6 +283,7 @@ public function loadApps(array $types = []): bool { } } } + \OC::$autoloader->triggerReload(); // prevent app loading from printing output ob_start(); @@ -1108,6 +1109,7 @@ public function upgradeApp(string $appId): bool { $this->checkAppDependencies($appId, $ignoreMax); \OC_App::registerAutoloading($appId, $appPath, true); + \OC::$autoloader->triggerReload(); $this->executeRepairSteps($appId, $appInfo['repair-steps']['pre-migration']); $ms = new MigrationService($appId, Server::get(\OC\DB\Connection::class)); diff --git a/lib/private/AppFramework/Bootstrap/Coordinator.php b/lib/private/AppFramework/Bootstrap/Coordinator.php index 9912e2c901337..2bf159068382f 100644 --- a/lib/private/AppFramework/Bootstrap/Coordinator.php +++ b/lib/private/AppFramework/Bootstrap/Coordinator.php @@ -68,7 +68,6 @@ private function registerApps(array $appIds): void { } $apps = []; foreach ($appIds as $appId) { - $this->eventLogger->start("bootstrap:register_app:$appId", "Register $appId"); $this->eventLogger->start("bootstrap:register_app:$appId:autoloader", "Setup autoloader for $appId"); /* * First, we have to enable the app's autoloader @@ -81,6 +80,10 @@ private function registerApps(array $appIds): void { continue; } $this->eventLogger->end("bootstrap:register_app:$appId:autoloader"); + } + \OC::$autoloader->triggerReload(); + foreach ($appIds as $appId) { + $this->eventLogger->start("bootstrap:register_app:$appId", "Register $appId"); /* * Next we check if there is an application class, and it implements diff --git a/lib/private/Autoloader.php b/lib/private/Autoloader.php new file mode 100644 index 0000000000000..ef88709380aa9 --- /dev/null +++ b/lib/private/Autoloader.php @@ -0,0 +1,468 @@ + namespace => path */ + private array $psr4Paths = []; + + /** @var string[] */ + private array $excludeDirs = []; + + /** @var array class => [file, time] */ + private array $classes = []; + private bool $cacheLoaded = false; + private bool $refreshed = false; + + /** @var array class => counter */ + private array $missingClasses = []; + + /** @var array file => mtime */ + private array $emptyFiles = []; + private bool $needSave = false; + + private int $loadsFromCache = 0; + private int $diskScans = 0; + + public function __construct( + private PhpDumpCache $dumpCache, + ) { + if (!extension_loaded('tokenizer')) { + throw new \LogicException('PHP extension Tokenizer is not loaded.'); + } + } + + public function __destruct() { + if ($this->needSave) { + $this->saveCache(); + } + } + + /** + * Register autoloader. + */ + public function register(bool $prepend = false): static { + spl_autoload_register([$this, 'tryLoad'], prepend: $prepend); + return $this; + } + + /** + * Handles autoloading of classes, interfaces or traits. + */ + public function tryLoad(string $type): void { + try { + $this->loadCache(); + + $missing = $this->missingClasses[$type] ?? 0; + if ($missing >= self::RetryLimit) { + return; + } + + [$file, $mtime] = $this->classes[$type] ?? null; + + if ($this->autoRebuild) { + if (!$this->refreshed) { + if (!$file || !is_file($file)) { + $this->refreshClasses(); + [$file] = $this->classes[$type] ?? null; + $this->needSave = true; + + } elseif (filemtime($file) !== $mtime) { + $this->updateFile($file); + [$file] = $this->classes[$type] ?? null; + $this->needSave = true; + } + } + + if (!$file || !is_file($file)) { + $this->missingClasses[$type] = ++$missing; + $this->needSave = $this->needSave || $file || ($missing <= self::RetryLimit); + unset($this->classes[$type]); + $file = null; + } + } + + if ($file) { + (static function ($file) { + require $file; + })($file); + } + } catch (\Throwable $t) { + throw $t; + } + } + + /** + * Add path or paths to list. + */ + public function addDirectory(string ...$paths): static { + $this->scanPaths = array_merge($this->scanPaths, $paths); + $this->refreshed = false; + $this->cacheLoaded = false; + return $this; + } + + /** + * Add path or paths to list. + */ + public function addPsr4(string $namespace, string $path): static { + $this->psr4Paths[$namespace] = $path; + return $this; + } + + public function triggerReload(): void { + $this->refreshed = false; + $this->cacheLoaded = false; + } + + public function reportParseErrors(bool $on = true): static { + $this->reportParseErrors = $on; + return $this; + } + + /** + * Excludes path or paths from list. + */ + public function excludeDirectory(string ...$paths): static { + $this->excludeDirs = array_merge($this->excludeDirs, $paths); + return $this; + } + + /** + * @return array class => filename + */ + public function getIndexedClasses(): array { + $this->loadCache(); + $res = []; + foreach ($this->classes as $class => [$file]) { + $res[$class] = $file; + } + + return $res; + } + + /** + * Rebuilds class list cache. + */ + public function rebuild(): void { + $this->cacheLoaded = true; + $this->classes = $this->missingClasses = $this->emptyFiles = []; + $this->refreshClasses(); + $this->saveCache(); + } + + /** + * Refreshes class list cache. + */ + public function refresh(): void { + $this->loadCache(); + if (!$this->refreshed) { + $this->refreshClasses(); + $this->saveCache(); + } + } + + /** + * Refreshes $this->classes & $this->emptyFiles. + */ + private function refreshClasses(): void { + $this->refreshed = true; // prevents calling refreshClasses() or updateFile() in tryLoad() + $files = $this->emptyFiles; + $classes = []; + foreach ($this->classes as $class => [$file, $mtime]) { + $files[$file] = $mtime; + $classes[$file][] = $class; + } + + $this->classes = $this->emptyFiles = []; + + foreach ($this->scanPaths as $path) { + $iterator = is_file($path) + ? [$path] + : $this->createFileIterator($path); + + foreach ($iterator as $file) { + $mtime = filemtime($file); + $foundClasses = isset($files[$file]) && $files[$file] === $mtime + ? ($classes[$file] ?? []) + : $this->scanPhp($file); + + if (!$foundClasses) { + $this->emptyFiles[$file] = $mtime; + } + + $files[$file] = $mtime; + $classes[$file] = []; // prevents the error when adding the same file twice + + foreach ($foundClasses as $class) { + if (isset($this->classes[$class])) { + continue; //FIXME + throw new \RuntimeException(sprintf( + 'Ambiguous class %s resolution; defined in %s and in %s.', + $class, + $this->classes[$class][0], + $file, + )); + } + + $this->classes[$class] = [$file, $mtime]; + unset($this->missingClasses[$class]); + } + } + } + + foreach ($this->psr4Paths as $namespace => $path) { + $iterator = $this->createFileIterator($path); + $pathLen = strlen($path); + foreach ($iterator as $file) { + $class = $namespace . '\\' . str_replace('/', '\\', substr($file, $pathLen, -4)); + if (isset($this->classes[$class])) { + continue; //FIXME + throw new \RuntimeException(sprintf( + 'Ambiguous class %s resolution; defined in %s and in %s.', + $class, + $this->classes[$class][0], + $file, + )); + } + + //FIXME needed? + $mtime = filemtime($file); + + $this->classes[$class] = [$file, $mtime]; + unset($this->missingClasses[$class]); + } + } + + $this->diskScans++; + } + + /** + * Creates an iterator scanning directory for PHP files and subdirectories. + * @throws \RuntimeException if path is not found + */ + private function createFileIterator(string $dir): \Generator { + if (!is_dir($dir)) { + throw new \RuntimeException(sprintf("Directory '%s' not found.", $dir)); + } + + $dir = realpath($dir) ?: $dir; // realpath does not work in phar + $disallow = []; + foreach (array_merge($this->ignoreDirs, $this->excludeDirs) as $item) { + if ($item = realpath($item)) { + $disallow[$item] = true; + } + } + + yield from $this->traverseDir($dir, $disallow); + } + + private function traverseDir(string $dir, array $disallow): \Generator { + try { + $files = new \FilesystemIterator($dir, \FilesystemIterator::FOLLOW_SYMLINKS | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::UNIX_PATHS); + } catch (\RuntimeException) { + return; + } + + foreach ($files as $file) { + $realPath = realpath($file); + $file = $realPath ?: $file; + if ($realPath && isset($disallow[$realPath])) { + continue; + } elseif (is_dir($file) && !self::matches(basename($file), $this->ignoreDirs)) { + yield from $this->traverseDir($file, $disallow); + } elseif (is_file($file) && self::matches(basename($file), $this->acceptFiles)) { + yield $file; + } + } + } + + private static function matches(string $file, array $masks): bool { + foreach ($masks as $mask) { + if (fnmatch($mask, $file)) { + return true; + } + } + return false; + } + + private function updateFile(string $file): void { + foreach ($this->classes as $class => [$prevFile]) { + if ($file === $prevFile) { + unset($this->classes[$class]); + } + } + + $foundClasses = is_file($file) ? $this->scanPhp($file) : []; + + foreach ($foundClasses as $class) { + [$prevFile, $prevMtime] = $this->classes[$class] ?? null; + + if (isset($prevFile) && @filemtime($prevFile) !== $prevMtime) { // @ file may not exist + $this->updateFile($prevFile); + [$prevFile] = $this->classes[$class] ?? null; + } + + if (isset($prevFile)) { + throw new \RuntimeException(sprintf( + 'Ambiguous class %s resolution; defined in %s and in %s.', + $class, + $prevFile, + $file, + )); + } + + $this->classes[$class] = [$file, filemtime($file)]; + } + } + + /** + * Searches classes, interfaces and traits in PHP file. + * @return string[] + */ + private function scanPhp(string $file): array { + $code = file_get_contents($file); + $expected = false; + $namespace = $name = ''; + $level = $minLevel = 0; + $classes = []; + + try { + $tokens = \PhpToken::tokenize($code, TOKEN_PARSE); + } catch (\ParseError $e) { + if ($this->reportParseErrors) { + $rp = new \ReflectionProperty($e, 'file'); + $rp->setAccessible(true); + $rp->setValue($e, $file); + throw $e; + } + + $tokens = []; + } + + foreach ($tokens as $token) { + switch ($token->id) { + case T_COMMENT: + case T_DOC_COMMENT: + case T_WHITESPACE: + continue 2; + + case T_STRING: + case T_NAME_QUALIFIED: + if ($expected) { + $name .= $token->text; + } + + continue 2; + + case T_NAMESPACE: + case T_CLASS: + case T_INTERFACE: + case T_TRAIT: + case PHP_VERSION_ID < 80100 + ? T_CLASS + : T_ENUM: + $expected = $token->id; + $name = ''; + continue 2; + } + + if ($expected) { + if ($expected === T_NAMESPACE) { + $namespace = $name ? $name . '\\' : ''; + $minLevel = $token->text === '{' ? 1 : 0; + + } elseif ($name && $level === $minLevel) { + $classes[] = $namespace . $name; + } + + $expected = null; + } + + if ($token->text === '{') { + $level++; + } elseif ($token->text === '}') { + $level--; + } + } + + return $classes; + } + + /********************* caching ****************d*g**/ + + /** + * Sets auto-refresh mode. + */ + public function setAutoRefresh(bool $on = true): static { + $this->autoRebuild = $on; + return $this; + } + + /** + * Loads class list from cache. + */ + private function loadCache(): void { + if ($this->cacheLoaded) { + return; + } + + $this->cacheLoaded = true; + + $data = $this->dumpCache->loadCache($this->generateCacheKey()); + if (is_array($data)) { + [$this->classes, $this->missingClasses, $this->emptyFiles] = $data; + $this->loadsFromCache++; + return; + } + + $this->classes = $this->missingClasses = $this->emptyFiles = []; + $this->refreshClasses(); + $this->saveCache(); + } + + /** + * Writes class list to cache. + * @param resource $lock + */ + private function saveCache(): void { + $this->dumpCache->saveCache($this->generateCacheKey(), [$this->classes, $this->missingClasses, $this->emptyFiles]); + } + + protected function generateCacheKey(): array { + return [$this->psr4Paths,$this->ignoreDirs, $this->acceptFiles, $this->scanPaths, $this->excludeDirs]; + } + + public function getStats(): array { + return [ + 'Loads from cache' => $this->loadsFromCache, + 'Disk scans' => $this->diskScans, + ]; + } +} diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php index 39c067e57304f..e881950be54e6 100644 --- a/lib/private/legacy/OC_App.php +++ b/lib/private/legacy/OC_App.php @@ -121,11 +121,18 @@ public static function registerAutoloading(string $app, string $path, bool $forc $appNamespace = Server::get(IAppManager::class)->getAppNamespace($app); \OC::$server->registerNamespace($app, $appNamespace); - if (file_exists($path . '/composer/autoload.php')) { - require_once $path . '/composer/autoload.php'; - } else { - \OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true); + // if (file_exists($path . '/composer/autoload.php')) { + // echo "$app $path\n"; + // require_once $path . '/composer/autoload.php'; + // } else { + if (is_dir($path . '/lib')) { + // autoloader crashes on non-existing dir + // \OC::$autoloader->addDirectory($path . '/lib/'); + \OC::$autoloader->addPsr4($appNamespace, $path . '/lib/'); + // debug_print_backtrace(); } + // \OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true); + // } // Register Test namespace only when testing if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) { From 5b89f3abf63b4cddb49568ae19ae01be7304e5d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 16 Jul 2026 11:57:28 +0200 Subject: [PATCH 03/15] perf: Move registerAutoloading to AppManager and optimize MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/private/App/AppManager.php | 88 +++++++++++++++---- .../AppFramework/Bootstrap/Coordinator.php | 20 +---- lib/private/Console/Application.php | 1 - lib/private/Installer.php | 2 +- lib/private/legacy/OC_App.php | 29 +----- 5 files changed, 74 insertions(+), 66 deletions(-) diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php index 5d836757e7004..cdddd4bccbae1 100644 --- a/lib/private/App/AppManager.php +++ b/lib/private/App/AppManager.php @@ -83,6 +83,8 @@ class AppManager implements IAppManager { /** @var array */ private array $loadedApps = []; + /** @var array */ + private array $registeredApps = []; /** @var string[] */ private $namespaceCache = []; @@ -267,23 +269,12 @@ public function loadApps(array $types = []): bool { // Load the enabled apps here $apps = $this->getEnabledApps(); - - // Add each apps' folder as allowed class path - foreach ($apps as $app) { + $appsToRegister = array_filter( + $apps, // If the app is already loaded then autoloading it makes no sense - if (!$this->isAppLoaded($app) && ($types === [] || $this->isType($app, $types))) { - try { - $path = $this->getAppPath($app); - \OC_App::registerAutoloading($app, $path); - } catch (AppPathNotFoundException $e) { - $this->logger->info('Error during app loading: ' . $e->getMessage(), [ - 'exception' => $e, - 'app' => $app, - ]); - } - } - } - \OC::$autoloader->triggerReload(); + fn (string $app) => (!$this->isAppLoaded($app) && ($types === [] || $this->isType($app, $types))), + ); + $this->registerAppsAutoloading($appsToRegister); // prevent app loading from printing output ob_start(); @@ -488,7 +479,7 @@ public function loadApp(string $app): void { $eventLogger->start("bootstrap:load_app:$app", "Load app: $app"); // in case someone calls loadApp() directly - \OC_App::registerAutoloading($app, $appPath); + $this->registerAppsAutoloading([$app]); if (is_file($appPath . '/appinfo/app.php')) { $this->logger->error('/appinfo/app.php is not supported anymore, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.', [ @@ -579,6 +570,66 @@ public function loadApp(string $app): void { $eventLogger->end("bootstrap:load_app:$app"); } + /** + * @internal + */ + public function registerAppsAutoloading(array $apps): void { + $reload = false; + foreach ($apps as $app) { + if (!isset($this->registeredApps[$app])) { + try { + $path = $this->getAppPath($app); + $this->registerAutoloading($app, $path); + $reload = true; + } catch (AppPathNotFoundException $e) { + $this->logger->info('Error during app loading: ' . $e->getMessage(), [ + 'exception' => $e, + 'app' => $app, + ]); + } + } + } + if ($reload) { + \OC::$autoloader->triggerReload(); + } + } + + /** + * @internal + */ + public function registerAutoloading(string $app, string $path, bool $force = false): void { + if (!$force && isset($this->registeredApps[$app])) { + return; + } + + $this->registeredApps[$app] = true; + + // Register on PSR-4 composer autoloader + $appNamespace = $this->getAppNamespace($app); + \OC::$server->registerNamespace($app, $appNamespace); + + // if (file_exists($path . '/composer/autoload.php')) { + // echo "$app $path\n"; + // require_once $path . '/composer/autoload.php'; + // } else { + if (is_dir($path . '/lib')) { + // autoloader crashes on non-existing dir + // \OC::$autoloader->addDirectory($path . '/lib/'); + \OC::$autoloader->addPsr4($appNamespace, $path . '/lib/'); + // debug_print_backtrace(); + } + // \OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true); + // } + + // Register Test namespace only when testing + if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) { + \OC::$autoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/'); + } + if ($force) { + \OC::$autoloader->triggerReload(); + } + } + /** * Check if an app is loaded * @param string $app app id @@ -1108,8 +1159,7 @@ public function upgradeApp(string $appId): bool { $ignoreMax = in_array($appId, $ignoreMaxApps, true); $this->checkAppDependencies($appId, $ignoreMax); - \OC_App::registerAutoloading($appId, $appPath, true); - \OC::$autoloader->triggerReload(); + $this->registerAutoloading($appId, $appPath, true); $this->executeRepairSteps($appId, $appInfo['repair-steps']['pre-migration']); $ms = new MigrationService($appId, Server::get(\OC\DB\Connection::class)); diff --git a/lib/private/AppFramework/Bootstrap/Coordinator.php b/lib/private/AppFramework/Bootstrap/Coordinator.php index 2bf159068382f..7d8772404c383 100644 --- a/lib/private/AppFramework/Bootstrap/Coordinator.php +++ b/lib/private/AppFramework/Bootstrap/Coordinator.php @@ -10,8 +10,6 @@ namespace OC\AppFramework\Bootstrap; use OC\Support\CrashReport\Registry; -use OC_App; -use OCP\App\AppPathNotFoundException; use OCP\App\IAppManager; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootstrap; @@ -66,22 +64,10 @@ private function registerApps(array $appIds): void { if ($this->registrationContext === null) { $this->registrationContext = new RegistrationContext($this->logger); } + $this->eventLogger->start('bootstrap:register_app:autoloader', 'Setup autoloader for apps'); + $this->appManager->registerAppsAutoloading($appIds); + $this->eventLogger->end('bootstrap:register_app:autoloader'); $apps = []; - foreach ($appIds as $appId) { - $this->eventLogger->start("bootstrap:register_app:$appId:autoloader", "Setup autoloader for $appId"); - /* - * First, we have to enable the app's autoloader - */ - try { - $path = $this->appManager->getAppPath($appId); - OC_App::registerAutoloading($appId, $path); - } catch (AppPathNotFoundException) { - // Ignore - continue; - } - $this->eventLogger->end("bootstrap:register_app:$appId:autoloader"); - } - \OC::$autoloader->triggerReload(); foreach ($appIds as $appId) { $this->eventLogger->start("bootstrap:register_app:$appId", "Register $appId"); diff --git a/lib/private/Console/Application.php b/lib/private/Console/Application.php index 7f903693d1cf7..49d9fe04550ce 100644 --- a/lib/private/Console/Application.php +++ b/lib/private/Console/Application.php @@ -126,7 +126,6 @@ public function loadCommands( } } // load from register_command.php - \OC_App::registerAutoloading($app, $appPath); $file = $appPath . '/appinfo/register_command.php'; if (file_exists($file)) { try { diff --git a/lib/private/Installer.php b/lib/private/Installer.php index b1e444f6b984a..1852a103be7a3 100644 --- a/lib/private/Installer.php +++ b/lib/private/Installer.php @@ -534,7 +534,7 @@ public function installShippedApps(bool $softErrors = false, ?IOutput $output = } private function installAppLastSteps(string $appPath, array $info, ?IOutput $output = null, string $enabled = 'no'): string { - \OC_App::registerAutoloading($info['id'], $appPath); + $this->appManager->registerAutoloading($info['id'], $appPath, true); $previousVersion = $this->config->getAppValue($info['id'], 'installed_version', ''); $ms = new MigrationService($info['id'], Server::get(Connection::class)); diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php index e881950be54e6..2ed37f66d7ba8 100644 --- a/lib/private/legacy/OC_App.php +++ b/lib/private/legacy/OC_App.php @@ -110,34 +110,7 @@ public static function loadApp(string $app): void { * @internal */ public static function registerAutoloading(string $app, string $path, bool $force = false): void { - $key = $app . '-' . $path; - if (!$force && isset(self::$alreadyRegistered[$key])) { - return; - } - - self::$alreadyRegistered[$key] = true; - - // Register on PSR-4 composer autoloader - $appNamespace = Server::get(IAppManager::class)->getAppNamespace($app); - \OC::$server->registerNamespace($app, $appNamespace); - - // if (file_exists($path . '/composer/autoload.php')) { - // echo "$app $path\n"; - // require_once $path . '/composer/autoload.php'; - // } else { - if (is_dir($path . '/lib')) { - // autoloader crashes on non-existing dir - // \OC::$autoloader->addDirectory($path . '/lib/'); - \OC::$autoloader->addPsr4($appNamespace, $path . '/lib/'); - // debug_print_backtrace(); - } - // \OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true); - // } - - // Register Test namespace only when testing - if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) { - \OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true); - } + Server::get(IAppManager::class)->registerAutoloading($app, $path, $force); } /** From bcbead02fe38ed05ae76a9da2bbfed8eadfbe503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 16 Jul 2026 12:00:03 +0200 Subject: [PATCH 04/15] fix: Re-enable composer autoloader from apps for backward compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/private/App/AppManager.php | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php index cdddd4bccbae1..5aea592fcdc31 100644 --- a/lib/private/App/AppManager.php +++ b/lib/private/App/AppManager.php @@ -608,18 +608,12 @@ public function registerAutoloading(string $app, string $path, bool $force = fal $appNamespace = $this->getAppNamespace($app); \OC::$server->registerNamespace($app, $appNamespace); - // if (file_exists($path . '/composer/autoload.php')) { - // echo "$app $path\n"; - // require_once $path . '/composer/autoload.php'; - // } else { - if (is_dir($path . '/lib')) { + if (file_exists($path . '/composer/autoload.php')) { + require_once $path . '/composer/autoload.php'; + } elseif (is_dir($path . '/lib')) { // autoloader crashes on non-existing dir - // \OC::$autoloader->addDirectory($path . '/lib/'); \OC::$autoloader->addPsr4($appNamespace, $path . '/lib/'); - // debug_print_backtrace(); } - // \OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true); - // } // Register Test namespace only when testing if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) { From b96c44ef8fed43389bb428283bd4905274acce55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 16 Jul 2026 12:01:44 +0200 Subject: [PATCH 05/15] perf: Delete composer autoloader for shipped apps to rely on the new one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- apps/admin_audit/composer/autoload.php | 22 ------------------- apps/appstore/composer/autoload.php | 22 ------------------- .../composer/autoload.php | 22 ------------------- apps/comments/composer/autoload.php | 22 ------------------- .../contactsinteraction/composer/autoload.php | 22 ------------------- apps/dashboard/composer/autoload.php | 22 ------------------- apps/dav/composer/autoload.php | 22 ------------------- apps/encryption/composer/autoload.php | 22 ------------------- .../composer/autoload.php | 22 ------------------- apps/federation/composer/autoload.php | 22 ------------------- apps/files/composer/autoload.php | 22 ------------------- apps/files_external/composer/autoload.php | 22 ------------------- apps/files_reminders/composer/autoload.php | 22 ------------------- apps/files_sharing/composer/autoload.php | 22 ------------------- apps/files_trashbin/composer/autoload.php | 22 ------------------- apps/files_versions/composer/autoload.php | 22 ------------------- .../composer/autoload.php | 22 ------------------- apps/oauth2/composer/autoload.php | 22 ------------------- apps/profile/composer/autoload.php | 22 ------------------- apps/provisioning_api/composer/autoload.php | 22 ------------------- apps/settings/composer/autoload.php | 22 ------------------- apps/sharebymail/composer/autoload.php | 22 ------------------- apps/systemtags/composer/autoload.php | 22 ------------------- apps/testing/composer/autoload.php | 22 ------------------- apps/theming/composer/autoload.php | 22 ------------------- .../composer/autoload.php | 22 ------------------- apps/updatenotification/composer/autoload.php | 22 ------------------- apps/user_ldap/composer/autoload.php | 22 ------------------- apps/user_status/composer/autoload.php | 22 ------------------- apps/weather_status/composer/autoload.php | 22 ------------------- apps/webhook_listeners/composer/autoload.php | 22 ------------------- apps/workflowengine/composer/autoload.php | 22 ------------------- 32 files changed, 704 deletions(-) delete mode 100644 apps/admin_audit/composer/autoload.php delete mode 100644 apps/appstore/composer/autoload.php delete mode 100644 apps/cloud_federation_api/composer/autoload.php delete mode 100644 apps/comments/composer/autoload.php delete mode 100644 apps/contactsinteraction/composer/autoload.php delete mode 100644 apps/dashboard/composer/autoload.php delete mode 100644 apps/dav/composer/autoload.php delete mode 100644 apps/encryption/composer/autoload.php delete mode 100644 apps/federatedfilesharing/composer/autoload.php delete mode 100644 apps/federation/composer/autoload.php delete mode 100644 apps/files/composer/autoload.php delete mode 100644 apps/files_external/composer/autoload.php delete mode 100644 apps/files_reminders/composer/autoload.php delete mode 100644 apps/files_sharing/composer/autoload.php delete mode 100644 apps/files_trashbin/composer/autoload.php delete mode 100644 apps/files_versions/composer/autoload.php delete mode 100644 apps/lookup_server_connector/composer/autoload.php delete mode 100644 apps/oauth2/composer/autoload.php delete mode 100644 apps/profile/composer/autoload.php delete mode 100644 apps/provisioning_api/composer/autoload.php delete mode 100644 apps/settings/composer/autoload.php delete mode 100644 apps/sharebymail/composer/autoload.php delete mode 100644 apps/systemtags/composer/autoload.php delete mode 100644 apps/testing/composer/autoload.php delete mode 100644 apps/theming/composer/autoload.php delete mode 100644 apps/twofactor_backupcodes/composer/autoload.php delete mode 100644 apps/updatenotification/composer/autoload.php delete mode 100644 apps/user_ldap/composer/autoload.php delete mode 100644 apps/user_status/composer/autoload.php delete mode 100644 apps/weather_status/composer/autoload.php delete mode 100644 apps/webhook_listeners/composer/autoload.php delete mode 100644 apps/workflowengine/composer/autoload.php diff --git a/apps/admin_audit/composer/autoload.php b/apps/admin_audit/composer/autoload.php deleted file mode 100644 index 7480b576ba0e7..0000000000000 --- a/apps/admin_audit/composer/autoload.php +++ /dev/null @@ -1,22 +0,0 @@ - Date: Thu, 16 Jul 2026 14:32:42 +0200 Subject: [PATCH 06/15] chore: Use Psr4 for OC/OCP, make cache directory configurable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cachedirectory still needs to be added to config.sample.php Signed-off-by: Côme Chilliet --- lib/OC.php | 41 ++++++++++++++++++++---------------- lib/private/Autoloader.php | 18 ++++++++++------ lib/private/PhpDumpCache.php | 3 ++- 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/lib/OC.php b/lib/OC.php index 413a3dbfa160c..953322c353c98 100644 --- a/lib/OC.php +++ b/lib/OC.php @@ -671,6 +671,23 @@ public static function boot(): void { // calculate the root directories OC::$SERVERROOT = str_replace('\\', '/', substr(__DIR__, 0, -4)); + // No autoloader yet, manually load Config class + require_once __DIR__ . '/private/Config.php'; + + // load configs + if (defined('PHPUNIT_CONFIG_DIR')) { + self::$configDir = OC::$SERVERROOT . '/' . PHPUNIT_CONFIG_DIR . '/'; + } elseif (defined('PHPUNIT_RUN') && PHPUNIT_RUN && is_dir(OC::$SERVERROOT . '/tests/config/')) { + self::$configDir = OC::$SERVERROOT . '/tests/config/'; + } elseif ($dir = getenv('NEXTCLOUD_CONFIG_DIR')) { + self::$configDir = rtrim($dir, '/') . '/'; + } else { + self::$configDir = OC::$SERVERROOT . '/config/'; + } + self::$config = new \OC\Config(self::$configDir); + + $cacheDirectory = self::$config->getValue('cachedirectory', OC::$SERVERROOT . '/cache'); + // register autoloader self::$loaderStart = microtime(true); @@ -678,14 +695,14 @@ public static function boot(): void { require_once __DIR__ . '/private/PhpDumpCache.php'; require_once __DIR__ . '/private/Autoloader.php'; - $phpDumpCache = new \OC\PhpDumpCache(OC::$SERVERROOT . '/temp'); + $phpDumpCache = new \OC\PhpDumpCache($cacheDirectory); self::$autoloader = new \OC\Autoloader($phpDumpCache); - self::$autoloader->addDirectory(OC::$SERVERROOT . '/lib'); - self::$autoloader->addDirectory(OC::$SERVERROOT . '/core'); + self::$autoloader->addPsr4('OC', OC::$SERVERROOT . '/lib/private'); + self::$autoloader->addPsr4('OCP', OC::$SERVERROOT . '/lib/public'); + self::$autoloader->addPsr4('NCU', OC::$SERVERROOT . '/lib/unstable'); + self::$autoloader->addPsr4('OC\\Core', OC::$SERVERROOT . '/core'); + self::$autoloader->addPsr4('', OC::$SERVERROOT . '/lib/private/legacy'); self::$autoloader->register(); - // Add default composer PSR-4 autoloader, ensure apcu to be disabled - // self::$composerAutoloader = require_once OC::$SERVERROOT . '/lib/composer/autoload.php'; - // self::$composerAutoloader->setApcuPrefix(null); // setup 3rdparty autoloader $vendorAutoLoad = OC::$SERVERROOT . '/3rdparty/autoload.php'; @@ -696,18 +713,6 @@ public static function boot(): void { self::$loaderEnd = microtime(true); - // load configs - if (defined('PHPUNIT_CONFIG_DIR')) { - self::$configDir = OC::$SERVERROOT . '/' . PHPUNIT_CONFIG_DIR . '/'; - } elseif (defined('PHPUNIT_RUN') && PHPUNIT_RUN && is_dir(OC::$SERVERROOT . '/tests/config/')) { - self::$configDir = OC::$SERVERROOT . '/tests/config/'; - } elseif ($dir = getenv('NEXTCLOUD_CONFIG_DIR')) { - self::$configDir = rtrim($dir, '/') . '/'; - } else { - self::$configDir = OC::$SERVERROOT . '/config/'; - } - self::$config = new \OC\Config(self::$configDir); - // Enable lazy loading if activated \OC\AppFramework\Utility\SimpleContainer::$useLazyObjects = (bool)self::$config->getValue('enable_lazy_objects', true); diff --git a/lib/private/Autoloader.php b/lib/private/Autoloader.php index ef88709380aa9..44104807cfefc 100644 --- a/lib/private/Autoloader.php +++ b/lib/private/Autoloader.php @@ -129,10 +129,12 @@ public function addDirectory(string ...$paths): static { } /** - * Add path or paths to list. + * Add path for given namespace */ public function addPsr4(string $namespace, string $path): static { - $this->psr4Paths[$namespace] = $path; + $path = trim($path, '/'); + $namespace = trim($namespace, '\\'); + $this->psr4Paths[$namespace] = '/' . $path; return $this; } @@ -222,7 +224,6 @@ private function refreshClasses(): void { foreach ($foundClasses as $class) { if (isset($this->classes[$class])) { - continue; //FIXME throw new \RuntimeException(sprintf( 'Ambiguous class %s resolution; defined in %s and in %s.', $class, @@ -239,11 +240,16 @@ private function refreshClasses(): void { foreach ($this->psr4Paths as $namespace => $path) { $iterator = $this->createFileIterator($path); - $pathLen = strlen($path); + // Length of path + separator + $pathLen = strlen($path) + 1; + if ($namespace === '') { + $prefix = ''; + } else { + $prefix = $namespace . '\\'; + } foreach ($iterator as $file) { - $class = $namespace . '\\' . str_replace('/', '\\', substr($file, $pathLen, -4)); + $class = $prefix . str_replace('/', '\\', substr($file, $pathLen, -4)); if (isset($this->classes[$class])) { - continue; //FIXME throw new \RuntimeException(sprintf( 'Ambiguous class %s resolution; defined in %s and in %s.', $class, diff --git a/lib/private/PhpDumpCache.php b/lib/private/PhpDumpCache.php index 4bc4af2b3fbe8..dfa1215559935 100644 --- a/lib/private/PhpDumpCache.php +++ b/lib/private/PhpDumpCache.php @@ -14,8 +14,9 @@ class PhpDumpCache { public function __construct( - private ?string $tempDirectory = null, + private string $tempDirectory, ) { + $this->setTempDirectory($tempDirectory); } /** From 91043e6166cb08720d186370641df035e0bcf8cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 16 Jul 2026 14:42:33 +0200 Subject: [PATCH 07/15] chore: Fix autoloading of test classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/private/App/AppManager.php | 2 +- tests/autoload.php | 3 ++- tests/bootstrap.php | 2 -- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php index 5aea592fcdc31..e5a193b699c33 100644 --- a/lib/private/App/AppManager.php +++ b/lib/private/App/AppManager.php @@ -616,7 +616,7 @@ public function registerAutoloading(string $app, string $path, bool $force = fal } // Register Test namespace only when testing - if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) { + if ((defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) && is_dir($path . '/tests/')) { \OC::$autoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/'); } if ($force) { diff --git a/tests/autoload.php b/tests/autoload.php index 05fc38529242f..c25319e45f381 100644 --- a/tests/autoload.php +++ b/tests/autoload.php @@ -13,4 +13,5 @@ * This is a file that applications can require to be able to autoload the class Test\TestCase from Nextcloud tests */ -\OC::$composerAutoloader->addPsr4('Test\\', OC::$SERVERROOT . '/tests/lib/', true); +\OC::$autoloader->addPsr4('Test\\', OC::$SERVERROOT . '/tests/lib/'); +\OC::$autoloader->triggerReload(); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 1fb54344d4978..d5e75e89e99e7 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -21,8 +21,6 @@ require_once __DIR__ . '/../lib/base.php'; require_once __DIR__ . '/autoload.php'; -\OC::$composerAutoloader->addPsr4('Tests\\', OC::$SERVERROOT . '/tests/', true); - $dontLoadApps = getenv('TEST_DONT_LOAD_APPS'); if (!$dontLoadApps) { // load all apps From 19a3416bdefd68dd038567d0c5e9b053eb707185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 16 Jul 2026 15:11:03 +0200 Subject: [PATCH 08/15] fix: Hardcode require of functions file that autoloader cannot load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/OC.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/OC.php b/lib/OC.php index 953322c353c98..3bfbf11a5ba2a 100644 --- a/lib/OC.php +++ b/lib/OC.php @@ -40,6 +40,9 @@ require_once __DIR__ . '/public/Constants.php'; +// There is no autoloading for functions so we need to hardcode it +require_once __DIR__ . '/public/Log/functions.php'; + /** * Class that is a namespace for all global OC variables * @internal From b07461f7ec1d1689eec130b25f386aa43a5d3a6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 16 Jul 2026 16:05:14 +0200 Subject: [PATCH 09/15] fix: Harmonize namepsaces in tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There was a weird inconsistent mix of Test\, Tests\lib, Test\Core, Tests\Core and Core\. Now there is only Test\ for tests/lib/ and Tests\Core for tests/Core. Signed-off-by: Côme Chilliet --- tests/Core/Command/Config/System/CastHelperTest.php | 2 +- tests/Core/Command/Group/AddTest.php | 2 +- tests/Core/Command/Group/AddUserTest.php | 2 +- tests/Core/Command/Group/DeleteTest.php | 2 +- tests/Core/Command/Group/InfoTest.php | 2 +- tests/Core/Command/Group/ListCommandTest.php | 2 +- tests/Core/Command/Group/RemoveUserTest.php | 2 +- tests/Core/Command/Preview/CleanupTest.php | 2 +- tests/Core/Command/SystemTag/AddTest.php | 2 +- tests/Core/Command/SystemTag/DeleteTest.php | 2 +- tests/Core/Command/SystemTag/EditTest.php | 2 +- tests/Core/Command/SystemTag/ListCommandTest.php | 2 +- tests/Core/Command/TwoFactorAuth/CleanupTest.php | 2 +- tests/Core/Command/TwoFactorAuth/DisableTest.php | 2 +- tests/Core/Command/TwoFactorAuth/EnableTest.php | 2 +- tests/Core/Command/TwoFactorAuth/StateTest.php | 2 +- tests/Core/Command/User/AddTest.php | 2 +- tests/Core/Command/User/ProfileTest.php | 2 +- tests/Core/Controller/ClientFlowLoginV2ControllerTest.php | 2 +- tests/Core/Controller/ContactsMenuControllerTest.php | 2 +- tests/Core/Controller/GuestAvatarControllerTest.php | 2 +- tests/Core/Controller/OCSControllerTest.php | 3 ++- tests/Core/Controller/TwoFactorChallengeControllerTest.php | 2 +- tests/Core/Controller/UserControllerTest.php | 2 +- tests/Core/Middleware/TwoFactorMiddlewareTest.php | 2 +- tests/bootstrap.php | 2 ++ .../lib/Authentication/TwoFactorAuth/EnforcementStateTest.php | 2 +- .../Authentication/TwoFactorAuth/MandatoryTwoFactorTest.php | 2 +- tests/lib/Config/LexiconTest.php | 2 +- tests/lib/Config/TestConfigLexicon_I.php | 2 +- tests/lib/Config/TestLexicon_E.php | 2 +- tests/lib/Config/TestLexicon_N.php | 2 +- tests/lib/Config/TestLexicon_UserIndexed.php | 2 +- tests/lib/Config/TestLexicon_UserIndexedRemove.php | 2 +- tests/lib/Config/TestLexicon_W.php | 2 +- tests/lib/Config/UserConfigMigrationFallbackTest.php | 2 +- tests/lib/Config/UserConfigTest.php | 2 +- tests/lib/Contacts/ContactsMenu/ActionFactoryTest.php | 2 +- tests/lib/Contacts/ContactsMenu/ActionProviderStoreTest.php | 2 +- tests/lib/Contacts/ContactsMenu/Actions/LinkActionTest.php | 2 +- tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php | 2 +- tests/lib/Contacts/ContactsMenu/EntryTest.php | 2 +- tests/lib/Contacts/ContactsMenu/ManagerTest.php | 2 +- .../lib/Contacts/ContactsMenu/Providers/EMailproviderTest.php | 2 +- tests/lib/Http/WellKnown/GenericResponseTest.php | 2 +- 45 files changed, 47 insertions(+), 44 deletions(-) diff --git a/tests/Core/Command/Config/System/CastHelperTest.php b/tests/Core/Command/Config/System/CastHelperTest.php index c47c77ee9f827..d65382ba82b5d 100644 --- a/tests/Core/Command/Config/System/CastHelperTest.php +++ b/tests/Core/Command/Config/System/CastHelperTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -namespace Core\Command\Config\System; +namespace Tests\Core\Command\Config\System; use OC\Core\Command\Config\System\CastHelper; use Test\TestCase; diff --git a/tests/Core/Command/Group/AddTest.php b/tests/Core/Command/Group/AddTest.php index fc1406c77a6ba..52186411c5254 100644 --- a/tests/Core/Command/Group/AddTest.php +++ b/tests/Core/Command/Group/AddTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Test\Core\Command\Group; +namespace Tests\Core\Command\Group; use OC\Core\Command\Group\Add; use OCP\IGroup; diff --git a/tests/Core/Command/Group/AddUserTest.php b/tests/Core/Command/Group/AddUserTest.php index 8d31f7cdbc454..920e2994e3113 100644 --- a/tests/Core/Command/Group/AddUserTest.php +++ b/tests/Core/Command/Group/AddUserTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Test\Core\Command\Group; +namespace Tests\Core\Command\Group; use OC\Core\Command\Group\AddUser; use OCP\IGroup; diff --git a/tests/Core/Command/Group/DeleteTest.php b/tests/Core/Command/Group/DeleteTest.php index 92315c4daf475..1778ec817b3ed 100644 --- a/tests/Core/Command/Group/DeleteTest.php +++ b/tests/Core/Command/Group/DeleteTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Test\Core\Command\Group; +namespace Tests\Core\Command\Group; use OC\Core\Command\Group\Delete; use OCP\IGroup; diff --git a/tests/Core/Command/Group/InfoTest.php b/tests/Core/Command/Group/InfoTest.php index 8d9cb1c329181..70df2bd2b4160 100644 --- a/tests/Core/Command/Group/InfoTest.php +++ b/tests/Core/Command/Group/InfoTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Test\Core\Command\Group; +namespace Tests\Core\Command\Group; use OC\Core\Command\Group\Info; use OCP\IGroup; diff --git a/tests/Core/Command/Group/ListCommandTest.php b/tests/Core/Command/Group/ListCommandTest.php index 2885a6e44b051..99db782ad32c8 100644 --- a/tests/Core/Command/Group/ListCommandTest.php +++ b/tests/Core/Command/Group/ListCommandTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Test\Core\Command\Group; +namespace Tests\Core\Command\Group; use OC\Core\Command\Group\ListCommand; use OCP\IGroup; diff --git a/tests/Core/Command/Group/RemoveUserTest.php b/tests/Core/Command/Group/RemoveUserTest.php index 0c50152fe1a74..90ba3b3937a16 100644 --- a/tests/Core/Command/Group/RemoveUserTest.php +++ b/tests/Core/Command/Group/RemoveUserTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Test\Core\Command\Group; +namespace Tests\Core\Command\Group; use OC\Core\Command\Group\RemoveUser; use OCP\IGroup; diff --git a/tests/Core/Command/Preview/CleanupTest.php b/tests/Core/Command/Preview/CleanupTest.php index 6625e792695ca..f00b206ad4c9d 100644 --- a/tests/Core/Command/Preview/CleanupTest.php +++ b/tests/Core/Command/Preview/CleanupTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Core\Command\Preview; +namespace Tests\Core\Command\Preview; use OC\Core\Command\Preview\Cleanup; use OC\Preview\PreviewService; diff --git a/tests/Core/Command/SystemTag/AddTest.php b/tests/Core/Command/SystemTag/AddTest.php index f36bf6c9f14ba..0e4d21eca8bf9 100644 --- a/tests/Core/Command/SystemTag/AddTest.php +++ b/tests/Core/Command/SystemTag/AddTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Test\Core\Command\SystemTag; +namespace Tests\Core\Command\SystemTag; use OC\Core\Command\SystemTag\Add; use OCP\SystemTag\ISystemTag; diff --git a/tests/Core/Command/SystemTag/DeleteTest.php b/tests/Core/Command/SystemTag/DeleteTest.php index ee53c8b6d3bf7..5ab3a20ab1988 100644 --- a/tests/Core/Command/SystemTag/DeleteTest.php +++ b/tests/Core/Command/SystemTag/DeleteTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Test\Core\Command\SystemTag; +namespace Tests\Core\Command\SystemTag; use OC\Core\Command\SystemTag\Delete; use OCP\SystemTag\ISystemTagManager; diff --git a/tests/Core/Command/SystemTag/EditTest.php b/tests/Core/Command/SystemTag/EditTest.php index 6e87ff5f3e43b..d554c0c155938 100644 --- a/tests/Core/Command/SystemTag/EditTest.php +++ b/tests/Core/Command/SystemTag/EditTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Test\Core\Command\SystemTag; +namespace Tests\Core\Command\SystemTag; use OC\Core\Command\SystemTag\Edit; use OCP\SystemTag\ISystemTag; diff --git a/tests/Core/Command/SystemTag/ListCommandTest.php b/tests/Core/Command/SystemTag/ListCommandTest.php index 93f8ca458ad07..623e49133e6c6 100644 --- a/tests/Core/Command/SystemTag/ListCommandTest.php +++ b/tests/Core/Command/SystemTag/ListCommandTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Test\Core\Command\SystemTag; +namespace Tests\Core\Command\SystemTag; use OC\Core\Command\SystemTag\ListCommand; use OCP\SystemTag\ISystemTag; diff --git a/tests/Core/Command/TwoFactorAuth/CleanupTest.php b/tests/Core/Command/TwoFactorAuth/CleanupTest.php index 33d9a86f2c664..72436e90d391b 100644 --- a/tests/Core/Command/TwoFactorAuth/CleanupTest.php +++ b/tests/Core/Command/TwoFactorAuth/CleanupTest.php @@ -7,7 +7,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Core\Command\TwoFactorAuth; +namespace Tests\Core\Command\TwoFactorAuth; use OC\Core\Command\TwoFactorAuth\Cleanup; use OCP\Authentication\TwoFactorAuth\IRegistry; diff --git a/tests/Core/Command/TwoFactorAuth/DisableTest.php b/tests/Core/Command/TwoFactorAuth/DisableTest.php index ab5c3dd365bd9..4663d3785f7c6 100644 --- a/tests/Core/Command/TwoFactorAuth/DisableTest.php +++ b/tests/Core/Command/TwoFactorAuth/DisableTest.php @@ -7,7 +7,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Test\Core\Command\TwoFactorAuth; +namespace Tests\Core\Command\TwoFactorAuth; use OC\Authentication\TwoFactorAuth\ProviderManager; use OC\Core\Command\TwoFactorAuth\Disable; diff --git a/tests/Core/Command/TwoFactorAuth/EnableTest.php b/tests/Core/Command/TwoFactorAuth/EnableTest.php index 320782c039fc6..90859ffe1323b 100644 --- a/tests/Core/Command/TwoFactorAuth/EnableTest.php +++ b/tests/Core/Command/TwoFactorAuth/EnableTest.php @@ -7,7 +7,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Test\Core\Command\TwoFactorAuth; +namespace Tests\Core\Command\TwoFactorAuth; use OC\Authentication\TwoFactorAuth\ProviderManager; use OC\Core\Command\TwoFactorAuth\Enable; diff --git a/tests/Core/Command/TwoFactorAuth/StateTest.php b/tests/Core/Command/TwoFactorAuth/StateTest.php index da18a405478fa..cb1dff02ec0cc 100644 --- a/tests/Core/Command/TwoFactorAuth/StateTest.php +++ b/tests/Core/Command/TwoFactorAuth/StateTest.php @@ -7,7 +7,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Core\Command\TwoFactorAuth; +namespace Tests\Core\Command\TwoFactorAuth; use OC\Core\Command\TwoFactorAuth\State; use OCP\Authentication\TwoFactorAuth\IRegistry; diff --git a/tests/Core/Command/User/AddTest.php b/tests/Core/Command/User/AddTest.php index 43b7aaea55601..7f76b8931e0cc 100644 --- a/tests/Core/Command/User/AddTest.php +++ b/tests/Core/Command/User/AddTest.php @@ -7,7 +7,7 @@ declare(strict_types=1); -namespace Core\Command\User; +namespace Tests\Core\Command\User; use OC\Core\Command\User\Add; use OCA\Settings\Mailer\NewUserMailHelper; diff --git a/tests/Core/Command/User/ProfileTest.php b/tests/Core/Command/User/ProfileTest.php index 5ebbd9182a3a2..d967fc0b1281e 100644 --- a/tests/Core/Command/User/ProfileTest.php +++ b/tests/Core/Command/User/ProfileTest.php @@ -7,7 +7,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Core\Command\User; +namespace Tests\Core\Command\User; use OC\Core\Command\User\Profile; use OCP\Accounts\IAccount; diff --git a/tests/Core/Controller/ClientFlowLoginV2ControllerTest.php b/tests/Core/Controller/ClientFlowLoginV2ControllerTest.php index d20dde5fe29a8..6a2a27b7cee13 100644 --- a/tests/Core/Controller/ClientFlowLoginV2ControllerTest.php +++ b/tests/Core/Controller/ClientFlowLoginV2ControllerTest.php @@ -6,7 +6,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Test\Core\Controller; +namespace Tests\Core\Controller; use OC\Core\Controller\ClientFlowLoginV2Controller; use OC\Core\Data\LoginFlowV2Credentials; diff --git a/tests/Core/Controller/ContactsMenuControllerTest.php b/tests/Core/Controller/ContactsMenuControllerTest.php index c06086bce24c4..0f8bc3814f977 100644 --- a/tests/Core/Controller/ContactsMenuControllerTest.php +++ b/tests/Core/Controller/ContactsMenuControllerTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Tests\Controller; +namespace Tests\Core\Controller; use OC\Contacts\ContactsMenu\Manager; use OC\Core\Controller\ContactsMenuController; diff --git a/tests/Core/Controller/GuestAvatarControllerTest.php b/tests/Core/Controller/GuestAvatarControllerTest.php index 0626f0198fdaf..6c83ffbe40981 100644 --- a/tests/Core/Controller/GuestAvatarControllerTest.php +++ b/tests/Core/Controller/GuestAvatarControllerTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Core\Controller; +namespace Tests\Core\Controller; use OC\Core\Controller\GuestAvatarController; use OCP\AppFramework\Http\FileDisplayResponse; diff --git a/tests/Core/Controller/OCSControllerTest.php b/tests/Core/Controller/OCSControllerTest.php index fac18e344920c..cd87fa458df21 100644 --- a/tests/Core/Controller/OCSControllerTest.php +++ b/tests/Core/Controller/OCSControllerTest.php @@ -6,9 +6,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OC\Core\Controller; +namespace Tests\Core\Controller; use OC\CapabilitiesManager; +use OC\Core\Controller\OCSController; use OC\Security\IdentityProof\Key; use OC\Security\IdentityProof\Manager; use OCP\AppFramework\Http\DataResponse; diff --git a/tests/Core/Controller/TwoFactorChallengeControllerTest.php b/tests/Core/Controller/TwoFactorChallengeControllerTest.php index 2d47f7d651954..a7492be300f22 100644 --- a/tests/Core/Controller/TwoFactorChallengeControllerTest.php +++ b/tests/Core/Controller/TwoFactorChallengeControllerTest.php @@ -6,7 +6,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -namespace Test\Core\Controller; +namespace Tests\Core\Controller; use OC\Authentication\TwoFactorAuth\Manager; use OC\Authentication\TwoFactorAuth\ProviderSet; diff --git a/tests/Core/Controller/UserControllerTest.php b/tests/Core/Controller/UserControllerTest.php index 4f0380f6b96d8..90aca5c7ab78d 100644 --- a/tests/Core/Controller/UserControllerTest.php +++ b/tests/Core/Controller/UserControllerTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Test\Core\Controller; +namespace Tests\Core\Controller; use OC\Core\Controller\UserController; use OCP\AppFramework\Http\JSONResponse; diff --git a/tests/Core/Middleware/TwoFactorMiddlewareTest.php b/tests/Core/Middleware/TwoFactorMiddlewareTest.php index 46b4afaff0a44..6fb53ea174447 100644 --- a/tests/Core/Middleware/TwoFactorMiddlewareTest.php +++ b/tests/Core/Middleware/TwoFactorMiddlewareTest.php @@ -6,7 +6,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -namespace Test\Core\Middleware; +namespace Tests\Core\Middleware; use OC\AppFramework\Http\Attributes\TwoFactorSetUpDoneRequired; use OC\AppFramework\Http\Request; diff --git a/tests/bootstrap.php b/tests/bootstrap.php index d5e75e89e99e7..8db9c6fca81cc 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -21,6 +21,8 @@ require_once __DIR__ . '/../lib/base.php'; require_once __DIR__ . '/autoload.php'; +\OC::$autoloader->addPsr4('Tests\\Core', OC::$SERVERROOT . '/tests/Core'); + $dontLoadApps = getenv('TEST_DONT_LOAD_APPS'); if (!$dontLoadApps) { // load all apps diff --git a/tests/lib/Authentication/TwoFactorAuth/EnforcementStateTest.php b/tests/lib/Authentication/TwoFactorAuth/EnforcementStateTest.php index f1d38c10801c1..0b8da3bfd1eeb 100644 --- a/tests/lib/Authentication/TwoFactorAuth/EnforcementStateTest.php +++ b/tests/lib/Authentication/TwoFactorAuth/EnforcementStateTest.php @@ -12,7 +12,7 @@ * Time: 13:01 */ -namespace Tests\Authentication\TwoFactorAuth; +namespace Test\Authentication\TwoFactorAuth; use OC\Authentication\TwoFactorAuth\EnforcementState; use Test\TestCase; diff --git a/tests/lib/Authentication/TwoFactorAuth/MandatoryTwoFactorTest.php b/tests/lib/Authentication/TwoFactorAuth/MandatoryTwoFactorTest.php index cfbb701414c93..5b24a4a25b1b9 100644 --- a/tests/lib/Authentication/TwoFactorAuth/MandatoryTwoFactorTest.php +++ b/tests/lib/Authentication/TwoFactorAuth/MandatoryTwoFactorTest.php @@ -7,7 +7,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Tests\Authentication\TwoFactorAuth; +namespace Test\Authentication\TwoFactorAuth; use OC\Authentication\TwoFactorAuth\EnforcementState; use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor; diff --git a/tests/lib/Config/LexiconTest.php b/tests/lib/Config/LexiconTest.php index c3ead6240b5f4..83ec9ca207482 100644 --- a/tests/lib/Config/LexiconTest.php +++ b/tests/lib/Config/LexiconTest.php @@ -6,7 +6,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -namespace Tests\lib\Config; +namespace Test\Config; use OC\AppConfig; use OC\AppFramework\Bootstrap\Coordinator; diff --git a/tests/lib/Config/TestConfigLexicon_I.php b/tests/lib/Config/TestConfigLexicon_I.php index a7fb9e6d568ff..390157418250d 100644 --- a/tests/lib/Config/TestConfigLexicon_I.php +++ b/tests/lib/Config/TestConfigLexicon_I.php @@ -6,7 +6,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -namespace Tests\lib\Config; +namespace Test\Config; use OCP\Config\IUserConfig; use OCP\Config\Lexicon\Entry; diff --git a/tests/lib/Config/TestLexicon_E.php b/tests/lib/Config/TestLexicon_E.php index b3992e72d8e73..62deb8c742e3a 100644 --- a/tests/lib/Config/TestLexicon_E.php +++ b/tests/lib/Config/TestLexicon_E.php @@ -6,7 +6,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -namespace Tests\lib\Config; +namespace Test\Config; use OCP\Config\IUserConfig; use OCP\Config\Lexicon\Entry; diff --git a/tests/lib/Config/TestLexicon_N.php b/tests/lib/Config/TestLexicon_N.php index aee68db5032a7..1ce4eac3550e9 100644 --- a/tests/lib/Config/TestLexicon_N.php +++ b/tests/lib/Config/TestLexicon_N.php @@ -6,7 +6,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -namespace Tests\lib\Config; +namespace Test\Config; use OCP\Config\IUserConfig; use OCP\Config\Lexicon\Entry; diff --git a/tests/lib/Config/TestLexicon_UserIndexed.php b/tests/lib/Config/TestLexicon_UserIndexed.php index e876b834585f8..7b4ea3c0e2999 100644 --- a/tests/lib/Config/TestLexicon_UserIndexed.php +++ b/tests/lib/Config/TestLexicon_UserIndexed.php @@ -6,7 +6,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -namespace Tests\lib\Config; +namespace Test\Config; use OCP\Config\IUserConfig; use OCP\Config\Lexicon\Entry; diff --git a/tests/lib/Config/TestLexicon_UserIndexedRemove.php b/tests/lib/Config/TestLexicon_UserIndexedRemove.php index 5cb326d364625..b4a163ed164b9 100644 --- a/tests/lib/Config/TestLexicon_UserIndexedRemove.php +++ b/tests/lib/Config/TestLexicon_UserIndexedRemove.php @@ -6,7 +6,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -namespace Tests\lib\Config; +namespace Test\Config; use OCP\Config\Lexicon\Entry; use OCP\Config\Lexicon\ILexicon; diff --git a/tests/lib/Config/TestLexicon_W.php b/tests/lib/Config/TestLexicon_W.php index f945bd4bba510..c420cc85ad488 100644 --- a/tests/lib/Config/TestLexicon_W.php +++ b/tests/lib/Config/TestLexicon_W.php @@ -6,7 +6,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -namespace Tests\lib\Config; +namespace Test\Config; use OCP\Config\IUserConfig; use OCP\Config\Lexicon\Entry; diff --git a/tests/lib/Config/UserConfigMigrationFallbackTest.php b/tests/lib/Config/UserConfigMigrationFallbackTest.php index 3d7232c429a32..22a091015061e 100644 --- a/tests/lib/Config/UserConfigMigrationFallbackTest.php +++ b/tests/lib/Config/UserConfigMigrationFallbackTest.php @@ -6,7 +6,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Test\lib\Config; +namespace Test\Config; use Doctrine\DBAL\Exception\InvalidFieldNameException; use OC\Config\ConfigManager; diff --git a/tests/lib/Config/UserConfigTest.php b/tests/lib/Config/UserConfigTest.php index 7d0feb84f14c0..aecd513b7d4ce 100644 --- a/tests/lib/Config/UserConfigTest.php +++ b/tests/lib/Config/UserConfigTest.php @@ -6,7 +6,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -namespace Test\lib\Config; +namespace Test\Config; use OC\Config\ConfigManager; use OC\Config\PresetManager; diff --git a/tests/lib/Contacts/ContactsMenu/ActionFactoryTest.php b/tests/lib/Contacts/ContactsMenu/ActionFactoryTest.php index 8b939928d8609..0a0754bea1000 100644 --- a/tests/lib/Contacts/ContactsMenu/ActionFactoryTest.php +++ b/tests/lib/Contacts/ContactsMenu/ActionFactoryTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Tests\Contacts\ContactsMenu; +namespace Test\Contacts\ContactsMenu; use OC\Contacts\ContactsMenu\ActionFactory; use OCP\Contacts\ContactsMenu\IAction; diff --git a/tests/lib/Contacts/ContactsMenu/ActionProviderStoreTest.php b/tests/lib/Contacts/ContactsMenu/ActionProviderStoreTest.php index 08afc6e930786..dfa03a003a82b 100644 --- a/tests/lib/Contacts/ContactsMenu/ActionProviderStoreTest.php +++ b/tests/lib/Contacts/ContactsMenu/ActionProviderStoreTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Tests\Contacts\ContactsMenu; +namespace Test\Contacts\ContactsMenu; use OC\App\AppManager; use OC\Contacts\ContactsMenu\ActionProviderStore; diff --git a/tests/lib/Contacts/ContactsMenu/Actions/LinkActionTest.php b/tests/lib/Contacts/ContactsMenu/Actions/LinkActionTest.php index b2a9ecb362fb6..f0d11a44a11f9 100644 --- a/tests/lib/Contacts/ContactsMenu/Actions/LinkActionTest.php +++ b/tests/lib/Contacts/ContactsMenu/Actions/LinkActionTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Tests\Contacts\ContactsMenu\Actions; +namespace Test\Contacts\ContactsMenu\Actions; use OC\Contacts\ContactsMenu\Actions\LinkAction; use Test\TestCase; diff --git a/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php b/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php index f8faa2b17f966..645167c28d293 100644 --- a/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php +++ b/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php @@ -7,7 +7,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Tests\Contacts\ContactsMenu; +namespace Test\Contacts\ContactsMenu; use OC\Contacts\ContactsMenu\ContactsStore; use OC\KnownUser\KnownUserService; diff --git a/tests/lib/Contacts/ContactsMenu/EntryTest.php b/tests/lib/Contacts/ContactsMenu/EntryTest.php index c0f07f7bf3b00..8f38469d38416 100644 --- a/tests/lib/Contacts/ContactsMenu/EntryTest.php +++ b/tests/lib/Contacts/ContactsMenu/EntryTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Tests\Contacts\ContactsMenu; +namespace Test\Contacts\ContactsMenu; use OC\Contacts\ContactsMenu\Actions\LinkAction; use OC\Contacts\ContactsMenu\Entry; diff --git a/tests/lib/Contacts/ContactsMenu/ManagerTest.php b/tests/lib/Contacts/ContactsMenu/ManagerTest.php index dfa37dd926ec5..0d66cea061860 100644 --- a/tests/lib/Contacts/ContactsMenu/ManagerTest.php +++ b/tests/lib/Contacts/ContactsMenu/ManagerTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Tests\Contacts\ContactsMenu; +namespace Test\Contacts\ContactsMenu; use OC\Contacts\ContactsMenu\ActionProviderStore; use OC\Contacts\ContactsMenu\ContactsStore; diff --git a/tests/lib/Contacts/ContactsMenu/Providers/EMailproviderTest.php b/tests/lib/Contacts/ContactsMenu/Providers/EMailproviderTest.php index 36ce0b96540f3..59cd08e7fa706 100644 --- a/tests/lib/Contacts/ContactsMenu/Providers/EMailproviderTest.php +++ b/tests/lib/Contacts/ContactsMenu/Providers/EMailproviderTest.php @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Tests\Contacts\ContactsMenu\Providers; +namespace Test\Contacts\ContactsMenu\Providers; use OC\Contacts\ContactsMenu\Providers\EMailProvider; use OCP\Contacts\ContactsMenu\IActionFactory; diff --git a/tests/lib/Http/WellKnown/GenericResponseTest.php b/tests/lib/Http/WellKnown/GenericResponseTest.php index f35f43221b8ce..2e74569a43469 100644 --- a/tests/lib/Http/WellKnown/GenericResponseTest.php +++ b/tests/lib/Http/WellKnown/GenericResponseTest.php @@ -7,7 +7,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace Tests\Http\WellKnown; +namespace Test\Http\WellKnown; use OCP\AppFramework\Http\JSONResponse; use OCP\Http\WellKnown\GenericResponse; From 435f0934c45b2cae610407b751adae2a6a8a54a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 13 Aug 2026 14:35:04 +0200 Subject: [PATCH 10/15] chore: Explicitely use OC AppManager when calling internal method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/private/AppFramework/Bootstrap/Coordinator.php | 4 ++-- lib/private/legacy/OC_App.php | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/private/AppFramework/Bootstrap/Coordinator.php b/lib/private/AppFramework/Bootstrap/Coordinator.php index 7d8772404c383..cf7c71500e0ef 100644 --- a/lib/private/AppFramework/Bootstrap/Coordinator.php +++ b/lib/private/AppFramework/Bootstrap/Coordinator.php @@ -9,8 +9,8 @@ namespace OC\AppFramework\Bootstrap; +use OC\App\AppManager; use OC\Support\CrashReport\Registry; -use OCP\App\IAppManager; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\QueryException; @@ -38,7 +38,7 @@ public function __construct( private IManager $dashboardManager, private IEventDispatcher $eventDispatcher, private IEventLogger $eventLogger, - private IAppManager $appManager, + private AppManager $appManager, private LoggerInterface $logger, ) { } diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php index 2ed37f66d7ba8..a6728d3977c3b 100644 --- a/lib/private/legacy/OC_App.php +++ b/lib/private/legacy/OC_App.php @@ -7,6 +7,7 @@ * SPDX-FileCopyrightText: 2016 ownCloud, Inc. * SPDX-License-Identifier: AGPL-3.0-only */ + use OC\App\AppManager; use OC\AppFramework\Bootstrap\Coordinator; use OC\Installer; @@ -110,7 +111,7 @@ public static function loadApp(string $app): void { * @internal */ public static function registerAutoloading(string $app, string $path, bool $force = false): void { - Server::get(IAppManager::class)->registerAutoloading($app, $path, $force); + Server::get(AppManager::class)->registerAutoloading($app, $path, $force); } /** From b688559cdeb6f240f5c8557f302c6943f8a99844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 13 Aug 2026 14:39:09 +0200 Subject: [PATCH 11/15] fix(autoloader): Remove now-unused non-psr4 directory scan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/private/Autoloader.php | 65 +++++--------------------------------- 1 file changed, 8 insertions(+), 57 deletions(-) diff --git a/lib/private/Autoloader.php b/lib/private/Autoloader.php index 44104807cfefc..87eef53a2c8f6 100644 --- a/lib/private/Autoloader.php +++ b/lib/private/Autoloader.php @@ -26,9 +26,6 @@ class Autoloader { private bool $autoRebuild = false; private bool $reportParseErrors = true; - /** @var string[] */ - private array $scanPaths = []; - /** @var array namespace => path */ private array $psr4Paths = []; @@ -43,8 +40,6 @@ class Autoloader { /** @var array class => counter */ private array $missingClasses = []; - /** @var array file => mtime */ - private array $emptyFiles = []; private bool $needSave = false; private int $loadsFromCache = 0; @@ -118,16 +113,6 @@ public function tryLoad(string $type): void { } } - /** - * Add path or paths to list. - */ - public function addDirectory(string ...$paths): static { - $this->scanPaths = array_merge($this->scanPaths, $paths); - $this->refreshed = false; - $this->cacheLoaded = false; - return $this; - } - /** * Add path for given namespace */ @@ -174,7 +159,7 @@ public function getIndexedClasses(): array { */ public function rebuild(): void { $this->cacheLoaded = true; - $this->classes = $this->missingClasses = $this->emptyFiles = []; + $this->classes = $this->missingClasses = []; $this->refreshClasses(); $this->saveCache(); } @@ -191,52 +176,18 @@ public function refresh(): void { } /** - * Refreshes $this->classes & $this->emptyFiles. + * Refreshes $this->classes. */ private function refreshClasses(): void { $this->refreshed = true; // prevents calling refreshClasses() or updateFile() in tryLoad() - $files = $this->emptyFiles; + $files = []; $classes = []; foreach ($this->classes as $class => [$file, $mtime]) { $files[$file] = $mtime; $classes[$file][] = $class; } - $this->classes = $this->emptyFiles = []; - - foreach ($this->scanPaths as $path) { - $iterator = is_file($path) - ? [$path] - : $this->createFileIterator($path); - - foreach ($iterator as $file) { - $mtime = filemtime($file); - $foundClasses = isset($files[$file]) && $files[$file] === $mtime - ? ($classes[$file] ?? []) - : $this->scanPhp($file); - - if (!$foundClasses) { - $this->emptyFiles[$file] = $mtime; - } - - $files[$file] = $mtime; - $classes[$file] = []; // prevents the error when adding the same file twice - - foreach ($foundClasses as $class) { - if (isset($this->classes[$class])) { - throw new \RuntimeException(sprintf( - 'Ambiguous class %s resolution; defined in %s and in %s.', - $class, - $this->classes[$class][0], - $file, - )); - } - - $this->classes[$class] = [$file, $mtime]; - unset($this->missingClasses[$class]); - } - } - } + $this->classes = []; foreach ($this->psr4Paths as $namespace => $path) { $iterator = $this->createFileIterator($path); @@ -443,12 +394,12 @@ private function loadCache(): void { $data = $this->dumpCache->loadCache($this->generateCacheKey()); if (is_array($data)) { - [$this->classes, $this->missingClasses, $this->emptyFiles] = $data; + [$this->classes, $this->missingClasses] = $data; $this->loadsFromCache++; return; } - $this->classes = $this->missingClasses = $this->emptyFiles = []; + $this->classes = $this->missingClasses = []; $this->refreshClasses(); $this->saveCache(); } @@ -458,11 +409,11 @@ private function loadCache(): void { * @param resource $lock */ private function saveCache(): void { - $this->dumpCache->saveCache($this->generateCacheKey(), [$this->classes, $this->missingClasses, $this->emptyFiles]); + $this->dumpCache->saveCache($this->generateCacheKey(), [$this->classes, $this->missingClasses]); } protected function generateCacheKey(): array { - return [$this->psr4Paths,$this->ignoreDirs, $this->acceptFiles, $this->scanPaths, $this->excludeDirs]; + return [$this->psr4Paths,$this->ignoreDirs, $this->acceptFiles, $this->excludeDirs]; } public function getStats(): array { From 916e3caf2e84c91adecb6775c3695c6986daa1e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 13 Aug 2026 14:58:52 +0200 Subject: [PATCH 12/15] chore(autoloader): Avoid having to trim on adding an autoloading path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/private/App/AppManager.php | 2 +- lib/private/Autoloader.php | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php index e5a193b699c33..4f00becc0d520 100644 --- a/lib/private/App/AppManager.php +++ b/lib/private/App/AppManager.php @@ -612,7 +612,7 @@ public function registerAutoloading(string $app, string $path, bool $force = fal require_once $path . '/composer/autoload.php'; } elseif (is_dir($path . '/lib')) { // autoloader crashes on non-existing dir - \OC::$autoloader->addPsr4($appNamespace, $path . '/lib/'); + \OC::$autoloader->addPsr4($appNamespace, $path . '/lib'); } // Register Test namespace only when testing diff --git a/lib/private/Autoloader.php b/lib/private/Autoloader.php index 87eef53a2c8f6..0990e505fb220 100644 --- a/lib/private/Autoloader.php +++ b/lib/private/Autoloader.php @@ -115,11 +115,12 @@ public function tryLoad(string $type): void { /** * Add path for given namespace + * + * @param string $namespace The namespace to register, with no \ at either end. + * @param string $path The path to register, with a beginning / and no ending /. */ public function addPsr4(string $namespace, string $path): static { - $path = trim($path, '/'); - $namespace = trim($namespace, '\\'); - $this->psr4Paths[$namespace] = '/' . $path; + $this->psr4Paths[$namespace] = $path; return $this; } From 6239c3f687ba62703af45873d45d9973e380f62c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 13 Aug 2026 16:20:00 +0200 Subject: [PATCH 13/15] chore: Remove unused file scanning and autorebuild from Autoloader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/private/Autoloader.php | 138 +------------------------------------ 1 file changed, 2 insertions(+), 136 deletions(-) diff --git a/lib/private/Autoloader.php b/lib/private/Autoloader.php index 0990e505fb220..7a6aee88a31a2 100644 --- a/lib/private/Autoloader.php +++ b/lib/private/Autoloader.php @@ -23,7 +23,6 @@ class Autoloader { /** @var string[] */ public array $acceptFiles = ['*.php']; - private bool $autoRebuild = false; private bool $reportParseErrors = true; /** @var array namespace => path */ @@ -81,28 +80,6 @@ public function tryLoad(string $type): void { [$file, $mtime] = $this->classes[$type] ?? null; - if ($this->autoRebuild) { - if (!$this->refreshed) { - if (!$file || !is_file($file)) { - $this->refreshClasses(); - [$file] = $this->classes[$type] ?? null; - $this->needSave = true; - - } elseif (filemtime($file) !== $mtime) { - $this->updateFile($file); - [$file] = $this->classes[$type] ?? null; - $this->needSave = true; - } - } - - if (!$file || !is_file($file)) { - $this->missingClasses[$type] = ++$missing; - $this->needSave = $this->needSave || $file || ($missing <= self::RetryLimit); - unset($this->classes[$type]); - $file = null; - } - } - if ($file) { (static function ($file) { require $file; @@ -180,7 +157,7 @@ public function refresh(): void { * Refreshes $this->classes. */ private function refreshClasses(): void { - $this->refreshed = true; // prevents calling refreshClasses() or updateFile() in tryLoad() + $this->refreshed = true; // prevents calling refreshClasses() in tryLoad() $files = []; $classes = []; foreach ($this->classes as $class => [$file, $mtime]) { @@ -270,118 +247,7 @@ private static function matches(string $file, array $masks): bool { return false; } - private function updateFile(string $file): void { - foreach ($this->classes as $class => [$prevFile]) { - if ($file === $prevFile) { - unset($this->classes[$class]); - } - } - - $foundClasses = is_file($file) ? $this->scanPhp($file) : []; - - foreach ($foundClasses as $class) { - [$prevFile, $prevMtime] = $this->classes[$class] ?? null; - - if (isset($prevFile) && @filemtime($prevFile) !== $prevMtime) { // @ file may not exist - $this->updateFile($prevFile); - [$prevFile] = $this->classes[$class] ?? null; - } - - if (isset($prevFile)) { - throw new \RuntimeException(sprintf( - 'Ambiguous class %s resolution; defined in %s and in %s.', - $class, - $prevFile, - $file, - )); - } - - $this->classes[$class] = [$file, filemtime($file)]; - } - } - - /** - * Searches classes, interfaces and traits in PHP file. - * @return string[] - */ - private function scanPhp(string $file): array { - $code = file_get_contents($file); - $expected = false; - $namespace = $name = ''; - $level = $minLevel = 0; - $classes = []; - - try { - $tokens = \PhpToken::tokenize($code, TOKEN_PARSE); - } catch (\ParseError $e) { - if ($this->reportParseErrors) { - $rp = new \ReflectionProperty($e, 'file'); - $rp->setAccessible(true); - $rp->setValue($e, $file); - throw $e; - } - - $tokens = []; - } - - foreach ($tokens as $token) { - switch ($token->id) { - case T_COMMENT: - case T_DOC_COMMENT: - case T_WHITESPACE: - continue 2; - - case T_STRING: - case T_NAME_QUALIFIED: - if ($expected) { - $name .= $token->text; - } - - continue 2; - - case T_NAMESPACE: - case T_CLASS: - case T_INTERFACE: - case T_TRAIT: - case PHP_VERSION_ID < 80100 - ? T_CLASS - : T_ENUM: - $expected = $token->id; - $name = ''; - continue 2; - } - - if ($expected) { - if ($expected === T_NAMESPACE) { - $namespace = $name ? $name . '\\' : ''; - $minLevel = $token->text === '{' ? 1 : 0; - - } elseif ($name && $level === $minLevel) { - $classes[] = $namespace . $name; - } - - $expected = null; - } - - if ($token->text === '{') { - $level++; - } elseif ($token->text === '}') { - $level--; - } - } - - return $classes; - } - - /********************* caching ****************d*g**/ - - /** - * Sets auto-refresh mode. - */ - public function setAutoRefresh(bool $on = true): static { - $this->autoRebuild = $on; - return $this; - } + /********************* caching *******************/ /** * Loads class list from cache. From 80f5d9928773bded196e886b2a453f5b4bf174a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Mon, 17 Aug 2026 11:37:25 +0200 Subject: [PATCH 14/15] feat: Cache application states and autoloading directly from AppManager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avoids parsing info.xml from each app and recomputing namespaces. For now Autoloader still has its own calls to PhpDumpCache which is a bit dirty, we might want to clean that up later. Signed-off-by: Côme Chilliet --- lib/private/App/AppManager.php | 57 ++++++++++++++++--- .../AppFramework/Bootstrap/Coordinator.php | 6 +- lib/private/Autoloader.php | 20 +++++++ lib/private/Server.php | 3 + 4 files changed, 76 insertions(+), 10 deletions(-) diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php index 4f00becc0d520..db1d9cbdbd61d 100644 --- a/lib/private/App/AppManager.php +++ b/lib/private/App/AppManager.php @@ -88,6 +88,7 @@ class AppManager implements IAppManager { /** @var string[] */ private $namespaceCache = []; + private $appinfoCache = []; private ?AppConfig $appConfig = null; private ?IURLGenerator $urlGenerator = null; @@ -107,6 +108,8 @@ public function __construct( private ServerVersion $serverVersion, private ConfigManager $configManager, private DependencyAnalyzer $dependencyAnalyzer, + private \OC\PhpDumpCache $phpDumpCache, + private IEventLogger $eventLogger, ) { } @@ -272,9 +275,11 @@ public function loadApps(array $types = []): bool { $appsToRegister = array_filter( $apps, // If the app is already loaded then autoloading it makes no sense - fn (string $app) => (!$this->isAppLoaded($app) && ($types === [] || $this->isType($app, $types))), + fn (string $app) => (!isset($this->registeredApps[$app]) && ($types === [] || $this->isType($app, $types))), ); - $this->registerAppsAutoloading($appsToRegister); + if (count($appsToRegister) > 0) { + $this->registerAppsAutoloading($appsToRegister); + } // prevent app loading from printing output ob_start(); @@ -475,11 +480,12 @@ public function loadApp(string $app): void { ]); return; } - $eventLogger = Server::get(IEventLogger::class); - $eventLogger->start("bootstrap:load_app:$app", "Load app: $app"); + $this->eventLogger->start("bootstrap:load_app:$app", "Load app: $app"); // in case someone calls loadApp() directly - $this->registerAppsAutoloading([$app]); + if (!isset($this->registeredApps[$app])) { + $this->registerAppsAutoloading([$app]); + } if (is_file($appPath . '/appinfo/app.php')) { $this->logger->error('/appinfo/app.php is not supported anymore, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.', [ @@ -490,7 +496,7 @@ public function loadApp(string $app): void { $coordinator = Server::get(Coordinator::class); $coordinator->bootApp($app); - $eventLogger->start("bootstrap:load_app:$app:info", "Load info.xml for $app and register any services defined in it"); + $this->eventLogger->start("bootstrap:load_app:$app:info", "Load info.xml for $app and register any services defined in it"); $info = $this->getAppInfo($app); if (!empty($info['activity'])) { $activityManager = Server::get(IActivityManager::class); @@ -565,15 +571,46 @@ public function loadApp(string $app): void { } } } - $eventLogger->end("bootstrap:load_app:$app:info"); + $this->eventLogger->end("bootstrap:load_app:$app:info"); - $eventLogger->end("bootstrap:load_app:$app"); + $this->eventLogger->end("bootstrap:load_app:$app"); } /** * @internal */ public function registerAppsAutoloading(array $apps): void { + $this->eventLogger->start('bootstrap:register_apps_autoloading', ''); + $loadedApps = array_unique(array_merge($apps, array_keys($this->registeredApps))); + sort($loadedApps); + $cacheKey = [self::class, ...$loadedApps]; + $this->eventLogger->start('bootstrap:register_apps_autoloading:load_cache', ''); + $cachedInfo = $this->phpDumpCache->loadCache($cacheKey); + if ($cachedInfo !== null) { + // echo "loading cache, key is ".json_encode($cacheKey).' '.md5(serialize($cacheKey))."\n"; + $this->eventLogger->end('bootstrap:register_apps_autoloading:load_cache'); + [$this->namespaceCache, $this->appInfos, $autoloaderProperties] = $cachedInfo; + \OC::$autoloader->loadFromArray($autoloaderProperties); + + $this->eventLogger->start('bootstrap:register_apps_autoloading:apply_cache', ''); + foreach ($apps as $app) { + if (isset($this->registeredApps[$app])) { + continue; + } + $this->registeredApps[$app] = true; + + $appNamespace = $this->getAppNamespace($app); + \OC::$server->registerNamespace($app, $appNamespace); + $path = $this->getAppPath($app); + if (file_exists($path . '/composer/autoload.php')) { + // FIXME needed? + require_once $path . '/composer/autoload.php'; + } + } + $this->eventLogger->end('bootstrap:register_apps_autoloading:apply_cache'); + $this->eventLogger->end('bootstrap:register_apps_autoloading'); + return; + } $reload = false; foreach ($apps as $app) { if (!isset($this->registeredApps[$app])) { @@ -590,8 +627,10 @@ public function registerAppsAutoloading(array $apps): void { } } if ($reload) { - \OC::$autoloader->triggerReload(); + \OC::$autoloader->rebuild(); } + $this->phpDumpCache->saveCache($cacheKey, [$this->namespaceCache, $this->appInfos, \OC::$autoloader->serializeToArray()]); + $this->eventLogger->end('bootstrap:register_apps_autoloading'); } /** diff --git a/lib/private/AppFramework/Bootstrap/Coordinator.php b/lib/private/AppFramework/Bootstrap/Coordinator.php index cf7c71500e0ef..e0bd85355edaa 100644 --- a/lib/private/AppFramework/Bootstrap/Coordinator.php +++ b/lib/private/AppFramework/Bootstrap/Coordinator.php @@ -65,7 +65,11 @@ private function registerApps(array $appIds): void { $this->registrationContext = new RegistrationContext($this->logger); } $this->eventLogger->start('bootstrap:register_app:autoloader', 'Setup autoloader for apps'); - $this->appManager->registerAppsAutoloading($appIds); + if ($appIds === []) { + $this->appManager->registerAppsAutoloading($this->appManager->getAlwaysEnabledApps()); + } else { + $this->appManager->registerAppsAutoloading($appIds); + } $this->eventLogger->end('bootstrap:register_app:autoloader'); $apps = []; foreach ($appIds as $appId) { diff --git a/lib/private/Autoloader.php b/lib/private/Autoloader.php index 7a6aee88a31a2..5bbb3632ffd69 100644 --- a/lib/private/Autoloader.php +++ b/lib/private/Autoloader.php @@ -289,4 +289,24 @@ public function getStats(): array { 'Disk scans' => $this->diskScans, ]; } + + public function serializeToArray(): array { + return [ + 'psr4Paths' => $this->psr4Paths, + 'excludeDirs' => $this->excludeDirs, + 'classes' => $this->classes, + 'missingClasses' => $this->missingClasses, + ]; + } + + public function loadFromArray(array $properties): void { + $this->psr4Paths = $properties['psr4Paths']; + $this->excludeDirs = $properties['excludeDirs']; + $this->classes = $properties['classes']; + $this->missingClasses = $properties['missingClasses']; + + /* Avoid any refresh */ + $this->cacheLoaded = true; + $this->refreshed = true; + } } diff --git a/lib/private/Server.php b/lib/private/Server.php index 7edd96f041440..0a1b8741703a2 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -322,6 +322,9 @@ public function __construct( $this->registerService(ContainerInterface::class, function (ContainerInterface $c) { return $c; }); + $this->registerService(\OC\PhpDumpCache::class, function (ContainerInterface $c) { + return new \OC\PhpDumpCache($c->get(SystemConfig::class)->getValue('cachedirectory', \OC::$SERVERROOT . '/cache')); + }); $this->registerDeprecatedAlias(IServerContainer::class, ContainerInterface::class); $this->registerAlias(\OCP\Calendar\IManager::class, \OC\Calendar\Manager::class); From d1d3cd809a153e084bf5e29b7bf9bcbad67e0002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Mon, 17 Aug 2026 11:39:12 +0200 Subject: [PATCH 15/15] feat: Use PhpDumpCache in Console/Application to avoid loading all commmands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This saves 100ms on occ runs by avoiding to load all commands to only run one of them. The list of commands is cached and loading is skipped when command is known. Signed-off-by: Côme Chilliet --- lib/private/Console/Application.php | 30 ++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/private/Console/Application.php b/lib/private/Console/Application.php index 49d9fe04550ce..5cce7871b954e 100644 --- a/lib/private/Console/Application.php +++ b/lib/private/Console/Application.php @@ -11,6 +11,7 @@ use ArgumentCountError; use OC\MemoryInfo; use OC\NeedsUpdateException; +use OC\PhpDumpCache; use OC\SystemConfig; use OCP\App\AppPathNotFoundException; use OCP\App\IAppManager; @@ -43,6 +44,7 @@ public function __construct( private MemoryInfo $memoryInfo, private IAppManager $appManager, private Defaults $defaults, + private PhpDumpCache $dumpCache, ) { $this->application = new SymfonyApplication($defaults->getName(), $serverVersion->getVersionString()); } @@ -83,11 +85,11 @@ public function loadCommands( } try { - require_once __DIR__ . '/../../../core/register_command.php'; if ($this->config->getSystemValueBool('installed', false)) { if (Util::needUpgrade()) { throw new NeedsUpdateException(); } elseif ($this->config->getSystemValueBool('maintenance')) { + require_once __DIR__ . '/../../../core/register_command.php'; if ($this->appManager->isEnabledForAnyone('app_api')) { // AppAPI must stay usable during maintenance mode; // loading commands from register_command.php is intentionally skipped. @@ -106,6 +108,16 @@ public function loadCommands( } $this->writeMaintenanceModeInfo($input, $output); } else { + $cachedCommandList = $this->dumpCache->loadCache([self::class]); + if (is_array($cachedCommandList)) { + $firstArg = $input->getArgument('command'); + if ($firstArg !== null && isset($cachedCommandList[$firstArg])) { + $this->appManager->loadApps(); + $this->application->add(Server::get($cachedCommandList[$firstArg])); + return; + } + } + require_once __DIR__ . '/../../../core/register_command.php'; $this->appManager->loadApps(); foreach ($this->appManager->getEnabledApps() as $app) { try { @@ -150,6 +162,9 @@ public function loadCommands( } } + /* To cover branches from above if that skipped register_command */ + require_once __DIR__ . '/../../../core/register_command.php'; + if ($input->getFirstArgument() !== 'check') { $errors = \OC_Util::checkServer(Server::get(SystemConfig::class)); if (!empty($errors)) { @@ -161,6 +176,19 @@ public function loadCommands( throw new \Exception('Environment not properly prepared.'); } } + $commands = $this->application->all(); + $cache = []; + foreach ($commands as $command) { + $name = $command->getName(); + if ($name !== null) { + $cache[$name] = $command::class; + } + + foreach ($command->getAliases() as $alias) { + $cache[$alias] = $command::class; + } + } + $this->dumpCache->saveCache([self::class], $cache); } /**