Add classmap-authoritative config setting
Add a "classmap-authoritative" configuration setting that can be used to disable searching the various prefix and fallback directories for classes that have not been registered with the Composer\Autoload\ClassLoader class map. This setting can be used to optimize performance by avoiding a potentially large number of `file_exists` calls when Composer is being used in a program with additional autoloader facilities. Use of the setting implies "optimize-autoloader" to ensure that the most complete class map possible is generated. Closes #3603pull/3610/head
parent
e172cd81a1
commit
ad1f8e6c5a
|
@ -789,6 +789,9 @@ The following options are supported:
|
||||||
the generated Composer autoloader. When null a random one will be generated.
|
the generated Composer autoloader. When null a random one will be generated.
|
||||||
* **optimize-autoloader** Defaults to `false`. Always optimize when dumping
|
* **optimize-autoloader** Defaults to `false`. Always optimize when dumping
|
||||||
the autoloader.
|
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-domains:** Defaults to `["github.com"]`. A list of domains to use in
|
||||||
github mode. This is used for GitHub Enterprise setups.
|
github mode. This is used for GitHub Enterprise setups.
|
||||||
* **github-expose-hostname:** Defaults to `true`. If set to false, the OAuth
|
* **github-expose-hostname:** Defaults to `true`. If set to false, the OAuth
|
||||||
|
|
|
@ -197,6 +197,10 @@
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "If false, the composer autoloader will not be prepended to existing autoloaders, defaults to true."
|
"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": {
|
"github-domains": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].",
|
"description": "A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].",
|
||||||
|
|
|
@ -63,6 +63,7 @@ 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);
|
||||||
|
|
||||||
|
@ -226,7 +227,7 @@ EOF;
|
||||||
file_put_contents($targetDir.'/autoload_files.php', $includeFilesFile);
|
file_put_contents($targetDir.'/autoload_files.php', $includeFilesFile);
|
||||||
}
|
}
|
||||||
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) $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
|
// use stream_copy_to_stream instead of copy
|
||||||
// to work around https://bugs.php.net/bug.php?id=64634
|
// to work around https://bugs.php.net/bug.php?id=64634
|
||||||
|
@ -443,7 +444,7 @@ return ComposerAutoloaderInit$suffix::getLoader();
|
||||||
AUTOLOAD;
|
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
|
// TODO the class ComposerAutoloaderInit should be revert to a closure
|
||||||
// when APC has been fixed:
|
// when APC has been fixed:
|
||||||
|
@ -520,6 +521,13 @@ PSR4;
|
||||||
CLASSMAP;
|
CLASSMAP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($classMapAuthoritative) {
|
||||||
|
$file .= <<<'CLASSMAPAUTHORITATIVE'
|
||||||
|
$loader->setClassMapAuthoritative(true);
|
||||||
|
|
||||||
|
CLASSMAPAUTHORITATIVE;
|
||||||
|
}
|
||||||
|
|
||||||
if ($useGlobalIncludePath) {
|
if ($useGlobalIncludePath) {
|
||||||
$file .= <<<'INCLUDEPATH'
|
$file .= <<<'INCLUDEPATH'
|
||||||
$loader->setUseIncludePath(true);
|
$loader->setUseIncludePath(true);
|
||||||
|
|
|
@ -54,6 +54,8 @@ class ClassLoader
|
||||||
private $useIncludePath = false;
|
private $useIncludePath = false;
|
||||||
private $classMap = array();
|
private $classMap = array();
|
||||||
|
|
||||||
|
private $classMapAuthoratative = false;
|
||||||
|
|
||||||
public function getPrefixes()
|
public function getPrefixes()
|
||||||
{
|
{
|
||||||
if (!empty($this->prefixesPsr0)) {
|
if (!empty($this->prefixesPsr0)) {
|
||||||
|
@ -248,6 +250,27 @@ class ClassLoader
|
||||||
return $this->useIncludePath;
|
return $this->useIncludePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns off searching the prefix and fallback directories for classes
|
||||||
|
* that have not been registered with the class map.
|
||||||
|
*
|
||||||
|
* @param bool $classMapAuthoratative
|
||||||
|
*/
|
||||||
|
public function setClassMapAuthoritative($classMapAuthoratative)
|
||||||
|
{
|
||||||
|
$this->classMapAuthoratative = $classMapAuthoratative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should class lookup fail if not found in the current class map?
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getClassMapAuthoratative()
|
||||||
|
{
|
||||||
|
return $this->classMapAuthoratative;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers this instance as an autoloader.
|
* Registers this instance as an autoloader.
|
||||||
*
|
*
|
||||||
|
@ -298,6 +321,8 @@ class ClassLoader
|
||||||
// class map lookup
|
// class map lookup
|
||||||
if (isset($this->classMap[$class])) {
|
if (isset($this->classMap[$class])) {
|
||||||
return $this->classMap[$class];
|
return $this->classMap[$class];
|
||||||
|
} elseif ($this->classMapAuthoratative) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$file = $this->findFileWithExtension($class, '.php');
|
$file = $this->findFileWithExtension($class, '.php');
|
||||||
|
|
|
@ -323,6 +323,7 @@ EOT
|
||||||
),
|
),
|
||||||
'autoloader-suffix' => array('is_string', function ($val) { return $val === 'null' ? null : $val; }),
|
'autoloader-suffix' => array('is_string', function ($val) { return $val === 'null' ? null : $val; }),
|
||||||
'optimize-autoloader' => array($booleanValidator, $booleanNormalizer),
|
'optimize-autoloader' => array($booleanValidator, $booleanNormalizer),
|
||||||
|
'classmap-authoritative' => array($booleanValidator, $booleanNormalizer),
|
||||||
'prepend-autoloader' => array($booleanValidator, $booleanNormalizer),
|
'prepend-autoloader' => array($booleanValidator, $booleanNormalizer),
|
||||||
'github-expose-hostname' => array($booleanValidator, $booleanNormalizer),
|
'github-expose-hostname' => array($booleanValidator, $booleanNormalizer),
|
||||||
);
|
);
|
||||||
|
|
|
@ -52,7 +52,7 @@ EOT
|
||||||
$package = $composer->getPackage();
|
$package = $composer->getPackage();
|
||||||
$config = $composer->getConfig();
|
$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) {
|
if ($optimize) {
|
||||||
$output->writeln('<info>Generating optimized autoload files</info>');
|
$output->writeln('<info>Generating optimized autoload files</info>');
|
||||||
|
|
|
@ -106,7 +106,7 @@ EOT
|
||||||
$preferDist = $input->getOption('prefer-dist');
|
$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
|
$install
|
||||||
->setDryRun($input->getOption('dry-run'))
|
->setDryRun($input->getOption('dry-run'))
|
||||||
|
|
|
@ -110,7 +110,7 @@ EOT
|
||||||
$preferDist = $input->getOption('prefer-dist');
|
$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
|
$install
|
||||||
->setDryRun($input->getOption('dry-run'))
|
->setDryRun($input->getOption('dry-run'))
|
||||||
|
|
|
@ -39,6 +39,7 @@ class Config
|
||||||
'discard-changes' => false,
|
'discard-changes' => false,
|
||||||
'autoloader-suffix' => null,
|
'autoloader-suffix' => null,
|
||||||
'optimize-autoloader' => false,
|
'optimize-autoloader' => false,
|
||||||
|
'classmap-authoritative' => false,
|
||||||
'prepend-autoloader' => true,
|
'prepend-autoloader' => true,
|
||||||
'github-domains' => array('github.com'),
|
'github-domains' => array('github.com'),
|
||||||
'github-expose-hostname' => true,
|
'github-expose-hostname' => true,
|
||||||
|
|
|
@ -67,6 +67,11 @@ class AutoloadGeneratorTest extends TestCase
|
||||||
*/
|
*/
|
||||||
private $eventDispatcher;
|
private $eventDispatcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $configValueMap;
|
||||||
|
|
||||||
protected function setUp()
|
protected function setUp()
|
||||||
{
|
{
|
||||||
$this->fs = new Filesystem;
|
$this->fs = new Filesystem;
|
||||||
|
@ -79,18 +84,23 @@ class AutoloadGeneratorTest extends TestCase
|
||||||
|
|
||||||
$this->config = $this->getMock('Composer\Config');
|
$this->config = $this->getMock('Composer\Config');
|
||||||
|
|
||||||
$this->config->expects($this->at(0))
|
$this->configValueMap = array(
|
||||||
->method('get')
|
'vendor-dir' => function () use ($that) {
|
||||||
->with($this->equalTo('vendor-dir'))
|
|
||||||
->will($this->returnCallback(function () use ($that) {
|
|
||||||
return $that->vendorDir;
|
return $that->vendorDir;
|
||||||
}));
|
},
|
||||||
|
);
|
||||||
|
|
||||||
$this->config->expects($this->at(1))
|
$this->config->expects($this->atLeastOnce())
|
||||||
->method('get')
|
->method('get')
|
||||||
->with($this->equalTo('vendor-dir'))
|
->will($this->returnCallback(function ($arg) use ($that) {
|
||||||
->will($this->returnCallback(function () use ($that) {
|
$ret = null;
|
||||||
return $that->vendorDir;
|
if (isset($that->configValueMap[$arg])) {
|
||||||
|
$ret = $that->configValueMap[$arg];
|
||||||
|
if (is_callable($ret)) {
|
||||||
|
$ret = $ret();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $ret;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
$this->origDir = getcwd();
|
$this->origDir = getcwd();
|
||||||
|
@ -483,6 +493,49 @@ class AutoloadGeneratorTest extends TestCase
|
||||||
include $this->vendorDir.'/composer/autoload_classmap.php'
|
include $this->vendorDir.'/composer/autoload_classmap.php'
|
||||||
);
|
);
|
||||||
$this->assertAutoloadFiles('classmap5', $this->vendorDir.'/composer', 'classmap');
|
$this->assertAutoloadFiles('classmap5', $this->vendorDir.'/composer', 'classmap');
|
||||||
|
$this->assertNotRegExp('/\$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->assertRegExp('/\$loader->setClassMapAuthoritative\(true\);/', file_get_contents($this->vendorDir.'/composer/autoload_real.php'));
|
||||||
|
// FIXME: how can we actually test the ClassLoader implementation?
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFilesAutoloadGeneration()
|
public function testFilesAutoloadGeneration()
|
||||||
|
@ -829,10 +882,7 @@ EOF;
|
||||||
->method('getCanonicalPackages')
|
->method('getCanonicalPackages')
|
||||||
->will($this->returnValue(array()));
|
->will($this->returnValue(array()));
|
||||||
|
|
||||||
$this->config->expects($this->at(2))
|
$this->configValueMap['use-include-path'] = true;
|
||||||
->method('get')
|
|
||||||
->with($this->equalTo('use-include-path'))
|
|
||||||
->will($this->returnValue(true));
|
|
||||||
|
|
||||||
$this->fs->ensureDirectoryExists($this->vendorDir.'/a');
|
$this->fs->ensureDirectoryExists($this->vendorDir.'/a');
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue