Browse Source

Avoid scanning files twice when generating optimized autoloaders, fixes #8683

Jordi Boggiano 5 years ago
parent
commit
86677ad172

+ 19 - 18
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('<warning>'.$e->getMessage().'</warning>');
                 }

+ 20 - 4
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);
     }
 
     /**

+ 1 - 1
src/Composer/Command/DumpAutoloadCommand.php

@@ -82,7 +82,7 @@ EOT
         } elseif ($optimize) {
             $this->getIO()->write('<info>Generated optimized autoload files containing '. $numberOfClasses .' classes</info>');
         } else {
-            $this->getIO()->write('<info>Generated autoload files containing '. $numberOfClasses .' classes</info>');
+            $this->getIO()->write('<info>Generated autoload files</info>');
         }
 
         return 0;