Browse Source

Merge remote-tracking branch 'and800/fix-autoload-opts'

Jordi Boggiano 5 năm trước cách đây
mục cha
commit
3a0d05c148

+ 7 - 8
src/Composer/Autoload/AutoloadGenerator.php

@@ -256,15 +256,14 @@ EOF;
                             continue;
                         }
 
-                        $namespaceFilter = $namespace === '' ? null : $namespace;
-                        $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespaceFilter, $classMap);
+                        $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespace, $group['type'], $classMap);
                     }
                 }
             }
         }
 
         foreach ($autoloads['classmap'] as $dir) {
-            $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, null, $classMap);
+            $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, null, null, $classMap);
         }
 
         ksort($classMap);
@@ -317,9 +316,9 @@ EOF;
         return count($classMap);
     }
 
-    private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist = null, $namespaceFilter = null, array $classMap = array())
+    private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist = null, $namespaceFilter = null, $autoloadType = null, array $classMap = array())
     {
-        foreach ($this->generateClassMap($dir, $blacklist, $namespaceFilter) as $class => $path) {
+        foreach ($this->generateClassMap($dir, $blacklist, $namespaceFilter, $autoloadType) as $class => $path) {
             $pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n";
             if (!isset($classMap[$class])) {
                 $classMap[$class] = $pathCode;
@@ -334,9 +333,9 @@ EOF;
         return $classMap;
     }
 
-    private function generateClassMap($dir, $blacklist = null, $namespaceFilter = null, $showAmbiguousWarning = true)
+    private function generateClassMap($dir, $blacklist = null, $namespaceFilter = null, $autoloadType = null, $showAmbiguousWarning = true)
     {
-        return ClassMapGenerator::createMap($dir, $blacklist, $showAmbiguousWarning ? $this->io : null, $namespaceFilter);
+        return ClassMapGenerator::createMap($dir, $blacklist, $showAmbiguousWarning ? $this->io : null, $namespaceFilter, $autoloadType);
     }
 
     public function buildPackageMap(InstallationManager $installationManager, PackageInterface $mainPackage, array $packages)
@@ -447,7 +446,7 @@ EOF;
 
             foreach ($autoloads['classmap'] as $dir) {
                 try {
-                    $loader->addClassMap($this->generateClassMap($dir, $blacklist, null, false));
+                    $loader->addClassMap($this->generateClassMap($dir, $blacklist, null, null, false));
                 } catch (\RuntimeException $e) {
                     $this->io->writeError('<warning>'.$e->getMessage().'</warning>');
                 }

+ 72 - 6
src/Composer/Autoload/ClassMapGenerator.php

@@ -50,17 +50,19 @@ class ClassMapGenerator
     /**
      * Iterate over all files in the given directory searching for classes
      *
-     * @param \Iterator|string $path      The path to search in or an iterator
-     * @param string           $blacklist Regex that matches against the file path that exclude from the classmap.
-     * @param IOInterface      $io        IO object
-     * @param string           $namespace Optional namespace prefix to filter by
+     * @param \Iterator|string $path         The path to search in or an iterator
+     * @param string           $blacklist    Regex that matches against the file path that exclude from the classmap.
+     * @param IOInterface      $io           IO object
+     * @param string           $namespace    Optional namespace prefix to filter by
+     * @param string           $autoloadType psr-0|psr-4 Optional autoload standard to use mapping rules
      *
      * @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)
+    public static function createMap($path, $blacklist = null, IOInterface $io = null, $namespace = null, $autoloadType = null)
     {
         if (is_string($path)) {
+            $basePath = $path;
             if (is_file($path)) {
                 $path = array(new \SplFileInfo($path));
             } elseif (is_dir($path)) {
@@ -71,6 +73,8 @@ class ClassMapGenerator
                     '" which does not appear to be a file nor a folder'
                 );
             }
+        } elseif (null !== $autoloadType) {
+            throw new \RuntimeException('Path must be a string when specifying an autoload type');
         }
 
         $map = array();
@@ -100,10 +104,13 @@ class ClassMapGenerator
             }
 
             $classes = self::findClasses($filePath);
+            if (null !== $autoloadType) {
+                $classes = self::filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath, $io);
+            }
 
             foreach ($classes as $class) {
                 // skip classes not within the given namespace prefix
-                if (null !== $namespace && 0 !== strpos($class, $namespace)) {
+                if (null === $autoloadType && null !== $namespace && 0 !== strpos($class, $namespace)) {
                     continue;
                 }
 
@@ -121,6 +128,65 @@ class ClassMapGenerator
         return $map;
     }
 
+    /**
+     * Remove classes which could not have been loaded by namespace autoloaders
+     *
+     * @param array       $classes       found classes in given file
+     * @param string      $filePath      current file
+     * @param string      $baseNamespace prefix of given autoload mapping
+     * @param string      $namespaceType psr-0|psr-4
+     * @param string      $basePath      root directory of given autoload mapping
+     * @param IOInterface $io            IO object
+     * @return array      valid classes
+     */
+    private static function filterByNamespace($classes, $filePath, $baseNamespace, $namespaceType, $basePath, $io)
+    {
+        $validClasses = array();
+        $rejectedClasses = array();
+        foreach ($classes as $class) {
+            // silently skip if ns doesn't have common root
+            if ('' !== $baseNamespace && 0 !== strpos($class, $baseNamespace)) {
+                continue;
+            }
+            // transform class name to file path and validate
+            if ('psr-0' === $namespaceType) {
+                $namespaceLength = strrpos($class, '\\');
+                if (false !== $namespaceLength) {
+                    $namespace = substr($class, 0, $namespaceLength + 1);
+                    $className = substr($class, $namespaceLength + 1);
+                    $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $namespace)
+                        . str_replace('_', DIRECTORY_SEPARATOR, $className);
+                }
+                else {
+                    $subPath = str_replace('_', DIRECTORY_SEPARATOR, $class);
+                }
+            } elseif ('psr-4' === $namespaceType) {
+                $subNamespace = ('' !== $baseNamespace) ? substr($class, strlen($baseNamespace)) : $class;
+                $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $subNamespace);
+            } else {
+                throw new \RuntimeException("namespaceType must be psr-0 or psr-4, $namespaceType given");
+            }
+            $realSubPath = substr($filePath, strlen($basePath) + 1);
+            $realSubPath = substr($realSubPath, 0, strrpos($realSubPath, '.'));
+            if ($subPath === $realSubPath) {
+                $validClasses[] = $class;
+            } else {
+                $rejectedClasses[] = $class;
+            }
+        }
+        // warn only if no valid classes, else silently skip invalid
+        if (!empty($validClasses)) {
+            return $validClasses;
+        }
+        if ($io) {
+            foreach ($rejectedClasses as $class) {
+                $io->writeError("<warning>Warning: class $class located in $filePath "
+                    . "doesn't comply with $namespaceType autoloading standard. Skipping.</warning>");
+            }
+        }
+        return array();
+    }
+
     /**
      * Extract the classes in the given file
      *

+ 39 - 0
tests/Composer/Test/Autoload/AutoloadGeneratorTest.php

@@ -548,6 +548,45 @@ class AutoloadGeneratorTest extends TestCase
         );
     }
 
+    public function testPSRToClassMapIgnoresNonPSRClasses()
+    {
+        $package = new Package('a', '1.0', '1.0');
+
+        $package->setAutoload(array(
+            'psr-0' => array('psr0_' => 'psr0/'),
+            'psr-4' => array('psr4\\' => 'psr4/'),
+        ));
+
+        $this->repository->expects($this->once())
+            ->method('getCanonicalPackages')
+            ->will($this->returnValue(array()));
+
+        $this->fs->ensureDirectoryExists($this->workingDir.'/psr0/psr0');
+        $this->fs->ensureDirectoryExists($this->workingDir.'/psr4');
+        file_put_contents($this->workingDir.'/psr0/psr0/match.php', '<?php class psr0_match {}');
+        file_put_contents($this->workingDir.'/psr0/psr0/badfile.php', '<?php class psr0_badclass {}');
+        file_put_contents($this->workingDir.'/psr4/match.php', '<?php namespace psr4; class match {}');
+        file_put_contents($this->workingDir.'/psr4/badfile.php', '<?php namespace psr4; class badclass {}');
+
+        $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1');
+        $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated.");
+        $expectedClassmap = <<<EOF
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+\$vendorDir = dirname(dirname(__FILE__));
+\$baseDir = dirname(\$vendorDir);
+
+return array(
+    'psr0_match' => \$baseDir . '/psr0/psr0/match.php',
+    'psr4\\\\match' => \$baseDir . '/psr4/match.php',
+);
+
+EOF;
+        $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_classmap.php', $expectedClassmap);
+    }
+
     public function testVendorsClassMapAutoloading()
     {
         $package = new Package('a', '1.0', '1.0');