Browse Source

Add cli argument for classmap-authoritative

Add a `--classmap-authoritative (-a)` argument to `composer install`,
`composer update` and `composer dumpautoload`. This enables the same
authoritative classmap behavior as the existing `classmap-authoritative`
configuration setting. The option can be used for creating highly
optimized production autoloaders via `composer install --no-dev
--optimize-autoloader --classmap-authoritative` for projects where
multiple autoloaders are present and unnecessary `file_exists` calls
introduce performance issues.

Closes #4361
Bryan Davis 9 years ago
parent
commit
cc2b9cfca5

+ 6 - 0
doc/03-cli.md

@@ -97,6 +97,8 @@ resolution.
 * **--optimize-autoloader (-o):** Convert PSR-0/4 autoloading to classmap to get a faster
   autoloader. This is recommended especially for production, but can take
   a bit of time to run so it is currently not done by default.
+* **--classmap-authoritative (-a):** Autoload classes from the classmap only.
+  Implicitly enables `--optimize-autoloader`.
 
 ## update
 
@@ -140,6 +142,8 @@ php composer.phar update vendor/*
 * **--optimize-autoloader (-o):** Convert PSR-0/4 autoloading to classmap to get a faster
   autoloader. This is recommended especially for production, but can take
   a bit of time to run so it is currently not done by default.
+* **--classmap-authoritative (-a):** Autoload classes from the classmap only.
+  Implicitly enables `--optimize-autoloader`.
 * **--lock:** Only updates the lock file hash to suppress warning about the
   lock file being out of date.
 * **--with-dependencies:** Add also all dependencies of whitelisted packages to the whitelist.
@@ -505,6 +509,8 @@ performance.
 * **--optimize (-o):** Convert PSR-0/4 autoloading to classmap to get a faster
   autoloader. This is recommended especially for production, but can take
   a bit of time to run so it is currently not done by default.
+* **--classmap-authoritative (-a):** Autoload classes from the classmap only.
+  Implicitly enables `--optimize`.
 * **--no-dev:** Disables autoload-dev rules.
 
 ## clear-cache

+ 2 - 3
doc/06-config.md

@@ -119,9 +119,8 @@ Defaults to `false`. If `true`, 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'.
+Defaults to `false`. If `true`, the Composer autoloader will only load classes
+from the classmap. Implies `optimize-autoloader`.
 
 ## github-domains
 

+ 26 - 4
src/Composer/Autoload/AutoloadGenerator.php

@@ -38,8 +38,16 @@ class AutoloadGenerator
      */
     private $io;
 
+    /**
+     * @var bool
+     */
     private $devMode = false;
 
+    /**
+     * @var bool
+     */
+    private $classMapAuthoritative = false;
+
     public function __construct(EventDispatcher $eventDispatcher, IOInterface $io = null)
     {
         $this->eventDispatcher = $eventDispatcher;
@@ -51,8 +59,23 @@ class AutoloadGenerator
         $this->devMode = (boolean) $devMode;
     }
 
+    /**
+     * Whether or not generated autoloader considers the class map
+     * authoritative.
+     *
+     * @param bool $classMapAuthoritative
+     */
+    public function setClassMapAuthoritative($classMapAuthoritative)
+    {
+        $this->classMapAuthoritative = (boolean) $classMapAuthoritative;
+    }
+
     public function dump(Config $config, InstalledRepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir, $scanPsr0Packages = false, $suffix = '')
     {
+        if ($this->classMapAuthoritative) {
+            // Force scanPsr0Packages when classmap is authoritative
+            $scanPsr0Packages = true;
+        }
         $this->eventDispatcher->dispatchScript(ScriptEvents::PRE_AUTOLOAD_DUMP, $this->devMode, array(), array(
             'optimize' => (bool) $scanPsr0Packages,
         ));
@@ -63,7 +86,6 @@ 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);
 
@@ -265,7 +287,7 @@ EOF;
             unlink($includeFilesFilePath);
         }
         file_put_contents($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix));
-        file_put_contents($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFileContents, $targetDirLoader, (bool) $includeFilesFileContents, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $classMapAuthoritative));
+        file_put_contents($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFileContents, $targetDirLoader, (bool) $includeFilesFileContents, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader));
 
         $this->safeCopy(__DIR__.'/ClassLoader.php', $targetDir.'/ClassLoader.php');
         $this->safeCopy(__DIR__.'/../../../LICENSE', $targetDir.'/LICENSE');
@@ -476,7 +498,7 @@ return ComposerAutoloaderInit$suffix::getLoader();
 AUTOLOAD;
     }
 
-    protected function getAutoloadRealFile($useClassMap, $useIncludePath, $targetDirLoader, $useIncludeFiles, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $classMapAuthoritative)
+    protected function getAutoloadRealFile($useClassMap, $useIncludePath, $targetDirLoader, $useIncludeFiles, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader)
     {
         // TODO the class ComposerAutoloaderInit should be revert to a closure
         // when APC has been fixed:
@@ -553,7 +575,7 @@ PSR4;
 CLASSMAP;
         }
 
-        if ($classMapAuthoritative) {
+        if ($this->classMapAuthoritative) {
             $file .= <<<'CLASSMAPAUTHORITATIVE'
         $loader->setClassMapAuthoritative(true);
 

+ 5 - 2
src/Composer/Command/DumpAutoloadCommand.php

@@ -31,6 +31,7 @@ class DumpAutoloadCommand extends Command
             ->setDescription('Dumps the autoloader')
             ->setDefinition(array(
                 new InputOption('optimize', 'o', InputOption::VALUE_NONE, 'Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.'),
+                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize`.'),
                 new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables autoload-dev rules.'),
             ))
             ->setHelp(<<<EOT
@@ -52,9 +53,10 @@ EOT
         $package = $composer->getPackage();
         $config = $composer->getConfig();
 
-        $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative');
+        $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader');
+        $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative');
 
-        if ($optimize) {
+        if ($optimize || $authoritative) {
             $this->getIO()->writeError('<info>Generating optimized autoload files</info>');
         } else {
             $this->getIO()->writeError('<info>Generating autoload files</info>');
@@ -62,6 +64,7 @@ EOT
 
         $generator = $composer->getAutoloadGenerator();
         $generator->setDevMode(!$input->getOption('no-dev'));
+        $generator->setClassMapAuthoritative($authoritative);
         $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize);
     }
 }

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

@@ -46,6 +46,7 @@ class InstallCommand extends Command
                 new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                 new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
                 new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
+                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
                 new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
                 new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Should not be provided, use composer require instead to add a given package to composer.json.'),
             ))
@@ -110,7 +111,8 @@ EOT
             $preferDist = $input->getOption('prefer-dist');
         }
 
-        $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative');
+        $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader');
+        $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative');
 
         $install
             ->setDryRun($input->getOption('dry-run'))
@@ -121,6 +123,7 @@ EOT
             ->setDumpAutoloader(!$input->getOption('no-autoloader'))
             ->setRunScripts(!$input->getOption('no-scripts'))
             ->setOptimizeAutoloader($optimize)
+            ->setClassMapAuthoritative($authoritative)
             ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
         ;
 

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

@@ -47,6 +47,7 @@ class UpdateCommand extends Command
                 new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Add also all dependencies of whitelisted packages to the whitelist.'),
                 new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
                 new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.'),
+                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
                 new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
                 new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies.'),
                 new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies.'),
@@ -114,7 +115,8 @@ EOT
             $preferDist = $input->getOption('prefer-dist');
         }
 
-        $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative');
+        $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader');
+        $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative');
 
         $install
             ->setDryRun($input->getOption('dry-run'))
@@ -125,6 +127,7 @@ EOT
             ->setDumpAutoloader(!$input->getOption('no-autoloader'))
             ->setRunScripts(!$input->getOption('no-scripts'))
             ->setOptimizeAutoloader($optimize)
+            ->setClassMapAuthoritative($authoritative)
             ->setUpdate(true)
             ->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $input->getArgument('packages'))
             ->setWhitelistDependencies($input->getOption('with-dependencies'))

+ 25 - 0
src/Composer/Installer.php

@@ -103,6 +103,7 @@ class Installer
     protected $preferSource = false;
     protected $preferDist = false;
     protected $optimizeAutoloader = false;
+    protected $classMapAuthoritative = false;
     protected $devMode = false;
     protected $dryRun = false;
     protected $verbose = false;
@@ -335,6 +336,7 @@ class Installer
                 }
 
                 $this->autoloadGenerator->setDevMode($this->devMode);
+                $this->autoloadGenerator->setClassMapAuthoritative($this->classMapAuthoritative);
                 $this->autoloadGenerator->dump($this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader);
             }
 
@@ -1308,6 +1310,29 @@ class Installer
     public function setOptimizeAutoloader($optimizeAutoloader = false)
     {
         $this->optimizeAutoloader = (boolean) $optimizeAutoloader;
+        if (!$this->optimizeAutoloader) {
+            // Force classMapAuthoritative off when not optimizing the
+            // autoloader
+            $this->setClassMapAuthoritative(false);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Whether or not generated autoloader considers the class map
+     * authoritative.
+     *
+     * @param  bool      $classMapAuthoritative
+     * @return Installer
+     */
+    public function setClassMapAuthoritative($classMapAuthoritative = false)
+    {
+        $this->classMapAuthoritative = (boolean) $classMapAuthoritative;
+        if ($this->classMapAuthoritative) {
+            // Force optimizeAutoloader when classmap is authoritative
+            $this->setOptimizeAutoloader(true);
+        }
 
         return $this;
     }

+ 12 - 12
tests/Composer/Test/Autoload/AutoloadGeneratorTest.php

@@ -512,35 +512,35 @@ class AutoloadGeneratorTest extends TestCase
         $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('./')));
+        $a->setAutoload(array('psr-4' => array('' => 'src/')));
+        $b->setAutoload(array('psr-4' => array('' => './')));
+        $c->setAutoload(array('psr-4' => array('' => 'foo/')));
 
         $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 {}');
+        file_put_contents($this->vendorDir.'/a/a/src/ClassMapFoo.php', '<?php class ClassMapFoo {}');
+        file_put_contents($this->vendorDir.'/b/b/ClassMapBar.php', '<?php class ClassMapBar {}');
+        file_put_contents($this->vendorDir.'/c/c/foo/ClassMapBaz.php', '<?php class ClassMapBaz {}');
 
+        $this->generator->setClassMapAuthoritative(true);
         $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',
+                'ClassMapBar' => $this->vendorDir.'/b/b/ClassMapBar.php',
+                'ClassMapBaz' => $this->vendorDir.'/c/c/foo/ClassMapBaz.php',
+                'ClassMapFoo' => $this->vendorDir.'/a/a/src/ClassMapFoo.php',
             ),
             include $this->vendorDir.'/composer/autoload_classmap.php'
         );
-        $this->assertAutoloadFiles('classmap5', $this->vendorDir.'/composer', 'classmap');
+        $this->assertAutoloadFiles('classmap8', $this->vendorDir.'/composer', 'classmap');
 
         $this->assertContains('$loader->setClassMapAuthoritative(true);', file_get_contents($this->vendorDir.'/composer/autoload_real.php'));
     }

+ 12 - 0
tests/Composer/Test/Autoload/Fixtures/autoload_classmap8.php

@@ -0,0 +1,12 @@
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    'ClassMapBar' => $vendorDir . '/b/b/ClassMapBar.php',
+    'ClassMapBaz' => $vendorDir . '/c/c/foo/ClassMapBaz.php',
+    'ClassMapFoo' => $vendorDir . '/a/a/src/ClassMapFoo.php',
+);