Browse Source

Merge pull request #3610 from bd808/feature/classmap-authoritative

Add classmap-authoritative config setting
Nils Adermann 10 years ago
parent
commit
0543a32d39

+ 3 - 0
doc/04-schema.md

@@ -790,6 +790,9 @@ The following options are supported:
   the generated Composer autoloader. When null a random one will be generated.
 * **optimize-autoloader** Defaults to `false`. Always optimize when dumping
   the autoloader.
+* **classmap-authoritative:** Defaults to `false`. If true, the composer
+  autoloader will not scan the filesystem for classes that are not found in
+  the class map. Implies 'optimize-autoloader'.
 * **github-domains:** Defaults to `["github.com"]`. A list of domains to use in
   github mode. This is used for GitHub Enterprise setups.
 * **github-expose-hostname:** Defaults to `true`. If set to false, the OAuth

+ 4 - 0
res/composer-schema.json

@@ -197,6 +197,10 @@
                     "type": "boolean",
                     "description": "If false, the composer autoloader will not be prepended to existing autoloaders, defaults to true."
                 },
+                "classmap-authoritative": {
+                    "type": "boolean",
+                    "description": "If true, the composer autoloader will not scan the filesystem for classes that are not found in the class map, defaults to false."
+                },
                 "github-domains": {
                     "type": "array",
                     "description": "A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].",

+ 10 - 2
src/Composer/Autoload/AutoloadGenerator.php

@@ -63,6 +63,7 @@ class AutoloadGenerator
         $vendorPath = $filesystem->normalizePath(realpath($config->get('vendor-dir')));
         $useGlobalIncludePath = (bool) $config->get('use-include-path');
         $prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true';
+        $classMapAuthoritative = $config->get('classmap-authoritative');
         $targetDir = $vendorPath.'/'.$targetDir;
         $filesystem->ensureDirectoryExists($targetDir);
 
@@ -226,7 +227,7 @@ EOF;
             file_put_contents($targetDir.'/autoload_files.php', $includeFilesFile);
         }
         file_put_contents($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix));
-        file_put_contents($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFile, $targetDirLoader, (bool) $includeFilesFile, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader));
+        file_put_contents($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFile, $targetDirLoader, (bool) $includeFilesFile, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $classMapAuthoritative));
 
         // use stream_copy_to_stream instead of copy
         // to work around https://bugs.php.net/bug.php?id=64634
@@ -443,7 +444,7 @@ return ComposerAutoloaderInit$suffix::getLoader();
 AUTOLOAD;
     }
 
-    protected function getAutoloadRealFile($useClassMap, $useIncludePath, $targetDirLoader, $useIncludeFiles, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader)
+    protected function getAutoloadRealFile($useClassMap, $useIncludePath, $targetDirLoader, $useIncludeFiles, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $classMapAuthoritative)
     {
         // TODO the class ComposerAutoloaderInit should be revert to a closure
         // when APC has been fixed:
@@ -520,6 +521,13 @@ PSR4;
 CLASSMAP;
         }
 
+        if ($classMapAuthoritative) {
+            $file .= <<<'CLASSMAPAUTHORITATIVE'
+        $loader->setClassMapAuthoritative(true);
+
+CLASSMAPAUTHORITATIVE;
+        }
+
         if ($useGlobalIncludePath) {
             $file .= <<<'INCLUDEPATH'
         $loader->setUseIncludePath(true);

+ 26 - 0
src/Composer/Autoload/ClassLoader.php

@@ -54,6 +54,8 @@ class ClassLoader
     private $useIncludePath = false;
     private $classMap = array();
 
+    private $classMapAuthoritative = false;
+
     public function getPrefixes()
     {
         if (!empty($this->prefixesPsr0)) {
@@ -248,6 +250,27 @@ class ClassLoader
         return $this->useIncludePath;
     }
 
+    /**
+     * Turns off searching the prefix and fallback directories for classes
+     * that have not been registered with the class map.
+     *
+     * @param bool $classMapAuthoritative
+     */
+    public function setClassMapAuthoritative($classMapAuthoritative)
+    {
+        $this->classMapAuthoritative = $classMapAuthoritative;
+    }
+
+    /**
+     * Should class lookup fail if not found in the current class map?
+     *
+     * @return bool
+     */
+    public function isClassMapAuthoritative()
+    {
+        return $this->classMapAuthoritative;
+    }
+
     /**
      * Registers this instance as an autoloader.
      *
@@ -299,6 +322,9 @@ class ClassLoader
         if (isset($this->classMap[$class])) {
             return $this->classMap[$class];
         }
+        if ($this->classMapAuthoritative) {
+            return false;
+        }
 
         $file = $this->findFileWithExtension($class, '.php');
 

+ 1 - 0
src/Composer/Command/ConfigCommand.php

@@ -325,6 +325,7 @@ EOT
             ),
             'autoloader-suffix' => array('is_string', function ($val) { return $val === 'null' ? null : $val; }),
             'optimize-autoloader' => array($booleanValidator, $booleanNormalizer),
+            'classmap-authoritative' => array($booleanValidator, $booleanNormalizer),
             'prepend-autoloader' => array($booleanValidator, $booleanNormalizer),
             'github-expose-hostname' => array($booleanValidator, $booleanNormalizer),
         );

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

@@ -52,7 +52,7 @@ EOT
         $package = $composer->getPackage();
         $config = $composer->getConfig();
 
-        $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader');
+        $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative');
 
         if ($optimize) {
             $output->writeln('<info>Generating optimized autoload files</info>');

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

@@ -106,7 +106,7 @@ EOT
             $preferDist = $input->getOption('prefer-dist');
         }
 
-        $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader');
+        $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative');
 
         $install
             ->setDryRun($input->getOption('dry-run'))

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

@@ -110,7 +110,7 @@ EOT
             $preferDist = $input->getOption('prefer-dist');
         }
 
-        $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader');
+        $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative');
 
         $install
             ->setDryRun($input->getOption('dry-run'))

+ 1 - 0
src/Composer/Config.php

@@ -39,6 +39,7 @@ class Config
         'discard-changes' => false,
         'autoloader-suffix' => null,
         'optimize-autoloader' => false,
+        'classmap-authoritative' => false,
         'prepend-autoloader' => true,
         'github-domains' => array('github.com'),
         'github-expose-hostname' => true,

+ 68 - 13
tests/Composer/Test/Autoload/AutoloadGeneratorTest.php

@@ -67,6 +67,17 @@ class AutoloadGeneratorTest extends TestCase
      */
     private $eventDispatcher;
 
+    /**
+     * Map of setting name => return value configuration for the stub Config
+     * object.
+     *
+     * Note: must be public for compatibility with PHP 5.3 runtimes where
+     * closures cannot access private members of the classes they are created
+     * in.
+     * @var array
+     */
+    public $configValueMap;
+
     protected function setUp()
     {
         $this->fs = new Filesystem;
@@ -79,18 +90,23 @@ class AutoloadGeneratorTest extends TestCase
 
         $this->config = $this->getMock('Composer\Config');
 
-        $this->config->expects($this->at(0))
-            ->method('get')
-            ->with($this->equalTo('vendor-dir'))
-            ->will($this->returnCallback(function () use ($that) {
+        $this->configValueMap = array(
+            'vendor-dir' => function () use ($that) {
                 return $that->vendorDir;
-            }));
+            },
+        );
 
-        $this->config->expects($this->at(1))
+        $this->config->expects($this->atLeastOnce())
             ->method('get')
-            ->with($this->equalTo('vendor-dir'))
-            ->will($this->returnCallback(function () use ($that) {
-                return $that->vendorDir;
+            ->will($this->returnCallback(function ($arg) use ($that) {
+                $ret = null;
+                if (isset($that->configValueMap[$arg])) {
+                    $ret = $that->configValueMap[$arg];
+                    if (is_callable($ret)) {
+                        $ret = $ret();
+                    }
+                }
+                return $ret;
             }));
 
         $this->origDir = getcwd();
@@ -483,6 +499,48 @@ class AutoloadGeneratorTest extends TestCase
             include $this->vendorDir.'/composer/autoload_classmap.php'
         );
         $this->assertAutoloadFiles('classmap5', $this->vendorDir.'/composer', 'classmap');
+        $this->assertNotContains('$loader->setClassMapAuthoritative(true);', file_get_contents($this->vendorDir.'/composer/autoload_real.php'));
+    }
+
+    public function testClassMapAutoloadingAuthoritative()
+    {
+        $package = new Package('a', '1.0', '1.0');
+
+        $packages = array();
+        $packages[] = $a = new Package('a/a', '1.0', '1.0');
+        $packages[] = $b = new Package('b/b', '1.0', '1.0');
+        $packages[] = $c = new Package('c/c', '1.0', '1.0');
+        $a->setAutoload(array('classmap' => array('')));
+        $b->setAutoload(array('classmap' => array('test.php')));
+        $c->setAutoload(array('classmap' => array('./')));
+
+        $this->repository->expects($this->once())
+            ->method('getCanonicalPackages')
+            ->will($this->returnValue($packages));
+
+        $this->configValueMap['classmap-authoritative'] = true;
+
+        $this->fs->ensureDirectoryExists($this->vendorDir.'/composer');
+        $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/src');
+        $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b');
+        $this->fs->ensureDirectoryExists($this->vendorDir.'/c/c/foo');
+        file_put_contents($this->vendorDir.'/a/a/src/a.php', '<?php class ClassMapFoo {}');
+        file_put_contents($this->vendorDir.'/b/b/test.php', '<?php class ClassMapBar {}');
+        file_put_contents($this->vendorDir.'/c/c/foo/test.php', '<?php class ClassMapBaz {}');
+
+        $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_7');
+        $this->assertTrue(file_exists($this->vendorDir.'/composer/autoload_classmap.php'), "ClassMap file needs to be generated.");
+        $this->assertEquals(
+            array(
+                'ClassMapBar' => $this->vendorDir.'/b/b/test.php',
+                'ClassMapBaz' => $this->vendorDir.'/c/c/foo/test.php',
+                'ClassMapFoo' => $this->vendorDir.'/a/a/src/a.php',
+            ),
+            include $this->vendorDir.'/composer/autoload_classmap.php'
+        );
+        $this->assertAutoloadFiles('classmap5', $this->vendorDir.'/composer', 'classmap');
+
+        $this->assertContains('$loader->setClassMapAuthoritative(true);', file_get_contents($this->vendorDir.'/composer/autoload_real.php'));
     }
 
     public function testFilesAutoloadGeneration()
@@ -829,10 +887,7 @@ EOF;
             ->method('getCanonicalPackages')
             ->will($this->returnValue(array()));
 
-        $this->config->expects($this->at(2))
-            ->method('get')
-            ->with($this->equalTo('use-include-path'))
-            ->will($this->returnValue(true));
+        $this->configValueMap['use-include-path'] = true;
 
         $this->fs->ensureDirectoryExists($this->vendorDir.'/a');