Skip to content

Commit 8863a59

Browse files
committed
perf: fast-path for default Composer namespaces loading in Autoloader
When Config\Modules::$composerPackages has no filters defined (the default behavior in almost all applications), all PSR-4 prefixes from Composer belong to installed packages. Skip the expensive InstalledVersions::getAllRawData() scanning, versions array_merge, and triple-nested str_starts_with matching loop when neither 'only' nor 'exclude' is configured. When package filtering is configured, the original full validation logic is preserved with zero BC breaks.
1 parent 27a1e6a commit 8863a59

2 files changed

Lines changed: 45 additions & 7 deletions

File tree

‎system/Autoloader/Autoloader.php‎

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -404,13 +404,37 @@ private function loadComposerNamespaces(ClassLoader $composer, array $composerPa
404404
}
405405
}
406406

407+
// Check config for $composerPackages.
408+
$only = $composerPackages['only'] ?? [];
409+
$exclude = $composerPackages['exclude'] ?? [];
410+
411+
if ($only !== [] && $exclude !== []) {
412+
throw new ConfigException('Cannot use "only" and "exclude" at the same time in "Config\Modules::$composerPackages".');
413+
}
414+
415+
// When no package filtering is configured (the default), all PSR-4 prefixes
416+
// from Composer can be added directly without expensive InstalledVersions scanning.
417+
if ($only === [] && $exclude === []) {
418+
$newPaths = [];
419+
420+
foreach ($namespacePaths as $namespace => $srcPaths) {
421+
// Composer stores namespaces with trailing slash. We don't.
422+
$newPaths[rtrim($namespace, '\\ ')] = $srcPaths;
423+
}
424+
425+
$this->addNamespace($newPaths);
426+
427+
return;
428+
}
429+
407430
if (! method_exists(InstalledVersions::class, 'getAllRawData')) { // @phpstan-ignore function.alreadyNarrowedType
408431
throw new RuntimeException(
409432
'Your Composer version is too old.'
410433
. ' Please update Composer (run `composer self-update`) to v2.0.14 or later'
411434
. ' and remove your vendor/ directory, and run `composer update`.',
412435
);
413436
}
437+
414438
// This method requires Composer 2.0.14 or later.
415439
$allData = InstalledVersions::getAllRawData();
416440
$packageList = [];
@@ -419,15 +443,9 @@ private function loadComposerNamespaces(ClassLoader $composer, array $composerPa
419443
$packageList = array_merge($packageList, $list['versions']);
420444
}
421445

422-
// Check config for $composerPackages.
423-
$only = $composerPackages['only'] ?? [];
424-
$exclude = $composerPackages['exclude'] ?? [];
425-
if ($only !== [] && $exclude !== []) {
426-
throw new ConfigException('Cannot use "only" and "exclude" at the same time in "Config\Modules::$composerPackages".');
427-
}
428-
429446
// Get install paths of packages to add namespace for auto-discovery.
430447
$installPaths = [];
448+
431449
if ($only !== []) {
432450
foreach ($packageList as $packageName => $data) {
433451
if (in_array($packageName, $only, true) && isset($data['install_path'])) {

‎tests/system/Autoloader/AutoloaderTest.php‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,26 @@ public function testFindsComposerRoutesWithComposerPathNotFound(): void
417417
$this->assertArrayNotHasKey('Laminas\\Escaper', $namespaces);
418418
}
419419

420+
public function testComposerPackagesDefaultLoadsAllNamespacesDirectly(): void
421+
{
422+
$config = new Autoload();
423+
$config->psr4 = [];
424+
$modules = new Modules();
425+
$modules->discoverInComposer = true;
426+
$modules->composerPackages = [];
427+
428+
$loader = new Autoloader();
429+
$loader->initialize($config, $modules);
430+
431+
$namespaces = $loader->getNamespace();
432+
433+
$this->assertArrayHasKey('Laminas\Escaper', $namespaces);
434+
$this->assertArrayHasKey('Psr\Log', $namespaces);
435+
$this->assertArrayNotHasKey('CodeIgniter', $namespaces);
436+
$this->assertArrayNotHasKey(APP_NAMESPACE, $namespaces);
437+
$this->assertArrayNotHasKey('Config', $namespaces);
438+
}
439+
420440
public function testAutoloaderLoadsNonClassFiles(): void
421441
{
422442
$config = new Autoload();

0 commit comments

Comments
 (0)