From cc2b9cfca59b392b0ffee721175cc7439a9d42f7 Mon Sep 17 00:00:00 2001 From: Bryan Davis Date: Sun, 16 Aug 2015 13:56:52 -0600 Subject: [PATCH 1/2] 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 --- doc/03-cli.md | 6 ++++ doc/06-config.md | 5 ++-- src/Composer/Autoload/AutoloadGenerator.php | 30 ++++++++++++++++--- src/Composer/Command/DumpAutoloadCommand.php | 7 +++-- src/Composer/Command/InstallCommand.php | 5 +++- src/Composer/Command/UpdateCommand.php | 5 +++- src/Composer/Installer.php | 25 ++++++++++++++++ .../Test/Autoload/AutoloadGeneratorTest.php | 24 +++++++-------- .../Autoload/Fixtures/autoload_classmap8.php | 12 ++++++++ 9 files changed, 96 insertions(+), 23 deletions(-) create mode 100644 tests/Composer/Test/Autoload/Fixtures/autoload_classmap8.php diff --git a/doc/03-cli.md b/doc/03-cli.md index 7864ea904..ee9a62028 100644 --- a/doc/03-cli.md +++ b/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 diff --git a/doc/06-config.md b/doc/06-config.md index 2564fefef..4716b93bb 100644 --- a/doc/06-config.md +++ b/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 diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 7227166a8..4784aae12 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/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); diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php index f560953b8..c452d1fb4 100644 --- a/src/Composer/Command/DumpAutoloadCommand.php +++ b/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(<<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('Generating optimized autoload files'); } else { $this->getIO()->writeError('Generating autoload files'); @@ -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); } } diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index e548b8d69..2494fdd67 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/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')) ; diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 579236143..c6828d10b 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/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')) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 8aecea402..3d06cd5eb 100644 --- a/src/Composer/Installer.php +++ b/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; } diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 007f16363..b5f6e930b 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/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', 'vendorDir.'/b/b/test.php', 'vendorDir.'/c/c/foo/test.php', 'vendorDir.'/a/a/src/ClassMapFoo.php', 'vendorDir.'/b/b/ClassMapBar.php', 'vendorDir.'/c/c/foo/ClassMapBaz.php', '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')); } diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap8.php b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap8.php new file mode 100644 index 000000000..0a40d114c --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap8.php @@ -0,0 +1,12 @@ + $vendorDir . '/b/b/ClassMapBar.php', + 'ClassMapBaz' => $vendorDir . '/c/c/foo/ClassMapBaz.php', + 'ClassMapFoo' => $vendorDir . '/a/a/src/ClassMapFoo.php', +); From 71cb5876111558aafce7a867ff8b9d32328174cc Mon Sep 17 00:00:00 2001 From: Bryan Davis Date: Tue, 18 Aug 2015 09:54:56 -0600 Subject: [PATCH 2/2] Add autoloader cli options to `require` and `remove` Update the `composer require` and `composer remove` commands to support the `--optimize-autoloader` and `--classmap-authoritative` cli options and associated configuration settings. All cli entry points that invoke `Installer::run()` or `AutoloadGenerator::dump()` now have consistent support for these autoloader optimization flags. --- doc/03-cli.md | 10 ++++++++++ src/Composer/Command/RemoveCommand.php | 7 +++++++ src/Composer/Command/RequireCommand.php | 6 ++++++ 3 files changed, 23 insertions(+) diff --git a/doc/03-cli.md b/doc/03-cli.md index ee9a62028..b7a908f3d 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -185,6 +185,11 @@ php composer.phar require vendor/package:2.* vendor/package2:dev-master * **--update-with-dependencies:** Also update dependencies of the newly required packages. * **--sort-packages:** Keep packages sorted in `composer.json`. +* **--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`. ## remove @@ -208,6 +213,11 @@ uninstalled. terminals or scripts which don't handle backspace characters. * **--update-no-dev:** Run the dependency update with the --no-dev option. * **--update-with-dependencies:** Also update dependencies of the removed packages. +* **--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`. ## global diff --git a/src/Composer/Command/RemoveCommand.php b/src/Composer/Command/RemoveCommand.php index 3779b09e9..3f507775f 100644 --- a/src/Composer/Command/RemoveCommand.php +++ b/src/Composer/Command/RemoveCommand.php @@ -42,6 +42,8 @@ class RemoveCommand extends Command new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'), new InputOption('update-with-dependencies', null, InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated with explicit dependencies.'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- 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`.'), )) ->setHelp(<<remove command removes a package from the current @@ -99,9 +101,14 @@ EOT $install = Installer::create($io, $composer); $updateDevMode = !$input->getOption('update-no-dev'); + $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); + $install ->setVerbose($input->getOption('verbose')) ->setDevMode($updateDevMode) + ->setOptimizeAutoloader($optimize) + ->setClassMapAuthoritative($authoritative) ->setUpdate(true) ->setUpdateWhitelist($packages) ->setWhitelistDependencies($input->getOption('update-with-dependencies')) diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 82401ffb2..ed3a9d624 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -48,6 +48,8 @@ class RequireCommand extends InitCommand new InputOption('update-with-dependencies', null, InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated with explicit dependencies.'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'), new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages when adding/updating a new dependency'), + 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`.'), )) ->setHelp(<<getOption('update-no-dev'); + $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); // Update packages $this->resetComposer(); @@ -149,6 +153,8 @@ EOT ->setPreferSource($input->getOption('prefer-source')) ->setPreferDist($input->getOption('prefer-dist')) ->setDevMode($updateDevMode) + ->setOptimizeAutoloader($optimize) + ->setClassMapAuthoritative($authoritative) ->setUpdate(true) ->setUpdateWhitelist(array_keys($requirements)) ->setWhitelistDependencies($input->getOption('update-with-dependencies'))