1
0
Fork 0

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
pull/4362/head
Bryan Davis 2015-08-16 13:56:52 -06:00
parent d831c86bcb
commit cc2b9cfca5
9 changed files with 96 additions and 23 deletions

View File

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

View File

@ -119,9 +119,8 @@ Defaults to `false`. If `true`, always optimize when dumping the autoloader.
## classmap-authoritative ## classmap-authoritative
Defaults to `false`. If `true`, the Composer autoloader will not scan the Defaults to `false`. If `true`, the Composer autoloader will only load classes
filesystem for classes that are not found in the class map. Implies from the classmap. Implies `optimize-autoloader`.
'optimize-autoloader'.
## github-domains ## github-domains

View File

@ -38,8 +38,16 @@ class AutoloadGenerator
*/ */
private $io; private $io;
/**
* @var bool
*/
private $devMode = false; private $devMode = false;
/**
* @var bool
*/
private $classMapAuthoritative = false;
public function __construct(EventDispatcher $eventDispatcher, IOInterface $io = null) public function __construct(EventDispatcher $eventDispatcher, IOInterface $io = null)
{ {
$this->eventDispatcher = $eventDispatcher; $this->eventDispatcher = $eventDispatcher;
@ -51,8 +59,23 @@ class AutoloadGenerator
$this->devMode = (boolean) $devMode; $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 = '') 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( $this->eventDispatcher->dispatchScript(ScriptEvents::PRE_AUTOLOAD_DUMP, $this->devMode, array(), array(
'optimize' => (bool) $scanPsr0Packages, 'optimize' => (bool) $scanPsr0Packages,
)); ));
@ -63,7 +86,6 @@ class AutoloadGenerator
$vendorPath = $filesystem->normalizePath(realpath($config->get('vendor-dir'))); $vendorPath = $filesystem->normalizePath(realpath($config->get('vendor-dir')));
$useGlobalIncludePath = (bool) $config->get('use-include-path'); $useGlobalIncludePath = (bool) $config->get('use-include-path');
$prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true'; $prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true';
$classMapAuthoritative = $config->get('classmap-authoritative');
$targetDir = $vendorPath.'/'.$targetDir; $targetDir = $vendorPath.'/'.$targetDir;
$filesystem->ensureDirectoryExists($targetDir); $filesystem->ensureDirectoryExists($targetDir);
@ -265,7 +287,7 @@ EOF;
unlink($includeFilesFilePath); unlink($includeFilesFilePath);
} }
file_put_contents($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix)); 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__.'/ClassLoader.php', $targetDir.'/ClassLoader.php');
$this->safeCopy(__DIR__.'/../../../LICENSE', $targetDir.'/LICENSE'); $this->safeCopy(__DIR__.'/../../../LICENSE', $targetDir.'/LICENSE');
@ -476,7 +498,7 @@ return ComposerAutoloaderInit$suffix::getLoader();
AUTOLOAD; 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 // TODO the class ComposerAutoloaderInit should be revert to a closure
// when APC has been fixed: // when APC has been fixed:
@ -553,7 +575,7 @@ PSR4;
CLASSMAP; CLASSMAP;
} }
if ($classMapAuthoritative) { if ($this->classMapAuthoritative) {
$file .= <<<'CLASSMAPAUTHORITATIVE' $file .= <<<'CLASSMAPAUTHORITATIVE'
$loader->setClassMapAuthoritative(true); $loader->setClassMapAuthoritative(true);

View File

@ -31,6 +31,7 @@ class DumpAutoloadCommand extends Command
->setDescription('Dumps the autoloader') ->setDescription('Dumps the autoloader')
->setDefinition(array( ->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('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.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables autoload-dev rules.'),
)) ))
->setHelp(<<<EOT ->setHelp(<<<EOT
@ -52,9 +53,10 @@ EOT
$package = $composer->getPackage(); $package = $composer->getPackage();
$config = $composer->getConfig(); $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>'); $this->getIO()->writeError('<info>Generating optimized autoload files</info>');
} else { } else {
$this->getIO()->writeError('<info>Generating autoload files</info>'); $this->getIO()->writeError('<info>Generating autoload files</info>');
@ -62,6 +64,7 @@ EOT
$generator = $composer->getAutoloadGenerator(); $generator = $composer->getAutoloadGenerator();
$generator->setDevMode(!$input->getOption('no-dev')); $generator->setDevMode(!$input->getOption('no-dev'));
$generator->setClassMapAuthoritative($authoritative);
$generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize); $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize);
} }
} }

View File

@ -46,6 +46,7 @@ class InstallCommand extends Command
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), 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('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('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('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.'), 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'); $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 $install
->setDryRun($input->getOption('dry-run')) ->setDryRun($input->getOption('dry-run'))
@ -121,6 +123,7 @@ EOT
->setDumpAutoloader(!$input->getOption('no-autoloader')) ->setDumpAutoloader(!$input->getOption('no-autoloader'))
->setRunScripts(!$input->getOption('no-scripts')) ->setRunScripts(!$input->getOption('no-scripts'))
->setOptimizeAutoloader($optimize) ->setOptimizeAutoloader($optimize)
->setClassMapAuthoritative($authoritative)
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')) ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
; ;

View File

@ -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('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('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('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('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-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies.'),
new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest 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'); $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 $install
->setDryRun($input->getOption('dry-run')) ->setDryRun($input->getOption('dry-run'))
@ -125,6 +127,7 @@ EOT
->setDumpAutoloader(!$input->getOption('no-autoloader')) ->setDumpAutoloader(!$input->getOption('no-autoloader'))
->setRunScripts(!$input->getOption('no-scripts')) ->setRunScripts(!$input->getOption('no-scripts'))
->setOptimizeAutoloader($optimize) ->setOptimizeAutoloader($optimize)
->setClassMapAuthoritative($authoritative)
->setUpdate(true) ->setUpdate(true)
->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $input->getArgument('packages')) ->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $input->getArgument('packages'))
->setWhitelistDependencies($input->getOption('with-dependencies')) ->setWhitelistDependencies($input->getOption('with-dependencies'))

View File

@ -103,6 +103,7 @@ class Installer
protected $preferSource = false; protected $preferSource = false;
protected $preferDist = false; protected $preferDist = false;
protected $optimizeAutoloader = false; protected $optimizeAutoloader = false;
protected $classMapAuthoritative = false;
protected $devMode = false; protected $devMode = false;
protected $dryRun = false; protected $dryRun = false;
protected $verbose = false; protected $verbose = false;
@ -335,6 +336,7 @@ class Installer
} }
$this->autoloadGenerator->setDevMode($this->devMode); $this->autoloadGenerator->setDevMode($this->devMode);
$this->autoloadGenerator->setClassMapAuthoritative($this->classMapAuthoritative);
$this->autoloadGenerator->dump($this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader); $this->autoloadGenerator->dump($this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader);
} }
@ -1308,6 +1310,29 @@ class Installer
public function setOptimizeAutoloader($optimizeAutoloader = false) public function setOptimizeAutoloader($optimizeAutoloader = false)
{ {
$this->optimizeAutoloader = (boolean) $optimizeAutoloader; $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; return $this;
} }

View File

@ -512,35 +512,35 @@ class AutoloadGeneratorTest extends TestCase
$packages[] = $a = new Package('a/a', '1.0', '1.0'); $packages[] = $a = new Package('a/a', '1.0', '1.0');
$packages[] = $b = new Package('b/b', '1.0', '1.0'); $packages[] = $b = new Package('b/b', '1.0', '1.0');
$packages[] = $c = new Package('c/c', '1.0', '1.0'); $packages[] = $c = new Package('c/c', '1.0', '1.0');
$a->setAutoload(array('classmap' => array(''))); $a->setAutoload(array('psr-4' => array('' => 'src/')));
$b->setAutoload(array('classmap' => array('test.php'))); $b->setAutoload(array('psr-4' => array('' => './')));
$c->setAutoload(array('classmap' => array('./'))); $c->setAutoload(array('psr-4' => array('' => 'foo/')));
$this->repository->expects($this->once()) $this->repository->expects($this->once())
->method('getCanonicalPackages') ->method('getCanonicalPackages')
->will($this->returnValue($packages)); ->will($this->returnValue($packages));
$this->configValueMap['classmap-authoritative'] = true;
$this->fs->ensureDirectoryExists($this->vendorDir.'/composer'); $this->fs->ensureDirectoryExists($this->vendorDir.'/composer');
$this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/src'); $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/src');
$this->fs->ensureDirectoryExists($this->vendorDir.'/b/b'); $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b');
$this->fs->ensureDirectoryExists($this->vendorDir.'/c/c/foo'); $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.'/a/a/src/ClassMapFoo.php', '<?php class ClassMapFoo {}');
file_put_contents($this->vendorDir.'/b/b/test.php', '<?php class ClassMapBar {}'); file_put_contents($this->vendorDir.'/b/b/ClassMapBar.php', '<?php class ClassMapBar {}');
file_put_contents($this->vendorDir.'/c/c/foo/test.php', '<?php class ClassMapBaz {}'); 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->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->assertTrue(file_exists($this->vendorDir.'/composer/autoload_classmap.php'), "ClassMap file needs to be generated.");
$this->assertEquals( $this->assertEquals(
array( array(
'ClassMapBar' => $this->vendorDir.'/b/b/test.php', 'ClassMapBar' => $this->vendorDir.'/b/b/ClassMapBar.php',
'ClassMapBaz' => $this->vendorDir.'/c/c/foo/test.php', 'ClassMapBaz' => $this->vendorDir.'/c/c/foo/ClassMapBaz.php',
'ClassMapFoo' => $this->vendorDir.'/a/a/src/a.php', 'ClassMapFoo' => $this->vendorDir.'/a/a/src/ClassMapFoo.php',
), ),
include $this->vendorDir.'/composer/autoload_classmap.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')); $this->assertContains('$loader->setClassMapAuthoritative(true);', file_get_contents($this->vendorDir.'/composer/autoload_real.php'));
} }

View File

@ -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',
);