diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index cf90535bf..371f3ed76 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -101,15 +101,15 @@ class AutoloadGenerator $this->runScripts = (bool) $runScripts; } - public function dump(Config $config, InstalledRepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir, $scanPsr0Packages = false, $suffix = '') + public function dump(Config $config, InstalledRepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir, $scanPsrPackages = false, $suffix = '') { if ($this->classMapAuthoritative) { - // Force scanPsr0Packages when classmap is authoritative - $scanPsr0Packages = true; + // Force scanPsrPackages when classmap is authoritative + $scanPsrPackages = true; } if ($this->runScripts) { $this->eventDispatcher->dispatchScript(ScriptEvents::PRE_AUTOLOAD_DUMP, $this->devMode, array(), array( - 'optimize' => (bool) $scanPsr0Packages, + 'optimize' => (bool) $scanPsrPackages, )); } @@ -234,14 +234,18 @@ EOF; $blacklist = '{(' . implode('|', $autoloads['exclude-from-classmap']) . ')}'; } - // flatten array $classMap = array(); $ambiguousClasses = array(); - if ($scanPsr0Packages) { + $scannedFiles = array(); + foreach ($autoloads['classmap'] as $dir) { + $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, null, null, $classMap, $ambiguousClasses, $scannedFiles); + } + + if ($scanPsrPackages) { $namespacesToScan = array(); // Scan the PSR-0/4 directories for class files, and add them to the class map - foreach (array('psr-0', 'psr-4') as $psrType) { + foreach (array('psr-4', 'psr-0') as $psrType) { foreach ($autoloads[$psrType] as $namespace => $paths) { $namespacesToScan[$namespace][] = array('paths' => $paths, 'type' => $psrType); } @@ -257,16 +261,12 @@ EOF; continue; } - $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespace, $group['type'], $classMap, $ambiguousClasses); + $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespace, $group['type'], $classMap, $ambiguousClasses, $scannedFiles); } } } } - foreach ($autoloads['classmap'] as $dir) { - $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, null, null, $classMap, $ambiguousClasses); - } - foreach ($ambiguousClasses as $className => $ambigiousPaths) { $cleanPath = str_replace(array('$vendorDir . \'', '$baseDir . \'', "',\n"), array($vendorPath, $basePath, ''), $classMap[$className]); @@ -319,7 +319,7 @@ EOF; if ($this->runScripts) { $this->eventDispatcher->dispatchScript(ScriptEvents::POST_AUTOLOAD_DUMP, $this->devMode, array(), array( - 'optimize' => (bool) $scanPsr0Packages, + 'optimize' => (bool) $scanPsrPackages, )); } @@ -336,9 +336,9 @@ EOF; return 0; } - private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespaceFilter, $autoloadType, array $classMap, array &$ambiguousClasses) + private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespaceFilter, $autoloadType, array $classMap, array &$ambiguousClasses, array &$scannedFiles) { - foreach ($this->generateClassMap($dir, $blacklist, $namespaceFilter, $autoloadType) as $class => $path) { + foreach ($this->generateClassMap($dir, $blacklist, $namespaceFilter, $autoloadType, true, $scannedFiles) as $class => $path) { $pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n"; if (!isset($classMap[$class])) { $classMap[$class] = $pathCode; @@ -350,9 +350,9 @@ EOF; return $classMap; } - private function generateClassMap($dir, $blacklist = null, $namespaceFilter = null, $autoloadType = null, $showAmbiguousWarning = true) + private function generateClassMap($dir, $blacklist, $namespaceFilter, $autoloadType, $showAmbiguousWarning, array &$scannedFiles) { - return ClassMapGenerator::createMap($dir, $blacklist, $showAmbiguousWarning ? $this->io : null, $namespaceFilter, $autoloadType); + return ClassMapGenerator::createMap($dir, $blacklist, $showAmbiguousWarning ? $this->io : null, $namespaceFilter, $autoloadType, $scannedFiles); } public function buildPackageMap(InstallationManager $installationManager, PackageInterface $mainPackage, array $packages) @@ -461,9 +461,10 @@ EOF; $blacklist = '{(' . implode('|', $autoloads['exclude-from-classmap']) . ')}'; } + $scannedFiles = array(); foreach ($autoloads['classmap'] as $dir) { try { - $loader->addClassMap($this->generateClassMap($dir, $blacklist, null, null, false)); + $loader->addClassMap($this->generateClassMap($dir, $blacklist, null, null, false, $scannedFiles)); } catch (\RuntimeException $e) { $this->io->writeError(''.$e->getMessage().''); } diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php index 09bbd447f..c0b011f3f 100644 --- a/src/Composer/Autoload/ClassMapGenerator.php +++ b/src/Composer/Autoload/ClassMapGenerator.php @@ -59,7 +59,7 @@ class ClassMapGenerator * @throws \RuntimeException When the path is neither an existing file nor directory * @return array A class map array */ - public static function createMap($path, $blacklist = null, IOInterface $io = null, $namespace = null, $autoloadType = null) + public static function createMap($path, $blacklist = null, IOInterface $io = null, $namespace = null, $autoloadType = null, &$scannedFiles = array()) { if (is_string($path)) { $basePath = $path; @@ -94,8 +94,16 @@ class ClassMapGenerator $filePath = preg_replace('{[\\\\/]{2,}}', '/', $filePath); } + $realPath = realpath($filePath); + + // if a list of scanned files is given, avoid scanning twice the same file to save cycles and avoid generating warnings + // in case a PSR-0/4 declaration follows another more specific one, or a classmap declaration, which covered this file already + if (isset($scannedFiles[$realPath])) { + continue; + } + // check the realpath of the file against the blacklist as the path might be a symlink and the blacklist is realpath'd so symlink are resolved - if ($blacklist && preg_match($blacklist, strtr(realpath($filePath), '\\', '/'))) { + if ($blacklist && preg_match($blacklist, strtr($realPath, '\\', '/'))) { continue; } // check non-realpath of file for directories symlink in project dir @@ -105,7 +113,15 @@ class ClassMapGenerator $classes = self::findClasses($filePath); if (null !== $autoloadType) { - $classes = self::filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath, $io); + list($classes, $validClasses) = self::filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath, $io); + + // if no valid class was found in the file then we do not mark it as scanned as it might still be matched by another rule later + if ($validClasses) { + $scannedFiles[$realPath] = true; + } + } else { + // classmap autoload rules always collect all classes so for these we definitely do not want to scan again + $scannedFiles[$realPath] = true; } foreach ($classes as $class) { @@ -192,7 +208,7 @@ class ClassMapGenerator // TODO enable in Composer 2.0 & unskip test in AutoloadGeneratorTest::testPSRToClassMapIgnoresNonPSRClasses //return $validClasses; - return $classes; + return array($classes, $validClasses); } /** diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php index f1e91f15c..9627b2a88 100644 --- a/src/Composer/Command/DumpAutoloadCommand.php +++ b/src/Composer/Command/DumpAutoloadCommand.php @@ -82,7 +82,7 @@ EOT } elseif ($optimize) { $this->getIO()->write('Generated optimized autoload files containing '. $numberOfClasses .' classes'); } else { - $this->getIO()->write('Generated autoload files containing '. $numberOfClasses .' classes'); + $this->getIO()->write('Generated autoload files'); } return 0;