From 6d4e60b9914e5514c49f4b19ee2b664c1d477605 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 28 Jul 2016 10:23:39 +0200 Subject: [PATCH] Add --apcu-autoloader option to enable APCu caching of found/not-found classes --- doc/03-cli.md | 5 +++ doc/06-config.md | 5 +++ res/composer-schema.json | 4 +++ src/Composer/Autoload/AutoloadGenerator.php | 23 ++++++++++++++ src/Composer/Autoload/ClassLoader.php | 31 +++++++++++++++++++ src/Composer/Command/ConfigCommand.php | 1 + src/Composer/Command/DumpAutoloadCommand.php | 3 ++ src/Composer/Command/InstallCommand.php | 3 ++ src/Composer/Command/RemoveCommand.php | 3 ++ src/Composer/Command/RequireCommand.php | 3 ++ src/Composer/Command/UpdateCommand.php | 3 ++ src/Composer/Config.php | 1 + src/Composer/Installer.php | 15 +++++++++ .../Test/Autoload/AutoloadGeneratorTest.php | 5 ++- 14 files changed, 104 insertions(+), 1 deletion(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index 340d2f299..fae34d11d 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -105,6 +105,7 @@ resolution. 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`. +* **--apcu-autoloader:** Use APCu to cache found/not-found classes. ## update @@ -150,6 +151,7 @@ php composer.phar update vendor/* 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`. +* **--apcu-autoloader:** Use APCu to cache found/not-found classes. * **--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. @@ -199,6 +201,7 @@ php composer.phar require vendor/package:2.* vendor/package2:dev-master 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`. +* **--apcu-autoloader:** Use APCu to cache found/not-found classes. * **--prefer-stable:** Prefer stable versions of dependencies. * **--prefer-lowest:** Prefer lowest versions of dependencies. Useful for testing minimal versions of requirements, generally used with `--prefer-stable`. @@ -231,6 +234,7 @@ uninstalled. 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`. +* **--apcu-autoloader:** Use APCu to cache found/not-found classes. ## global @@ -651,6 +655,7 @@ performance. 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`. +* **--apcu:** Use APCu to cache found/not-found classes. * **--no-dev:** Disables autoload-dev rules. ## clear-cache diff --git a/doc/06-config.md b/doc/06-config.md index 0a6a91c01..2a86394e8 100644 --- a/doc/06-config.md +++ b/doc/06-config.md @@ -210,6 +210,11 @@ by name in `composer.json` when adding a new package. Defaults to `false`. If `true`, the Composer autoloader will only load classes from the classmap. Implies `optimize-autoloader`. +## apcu-autoloader + +Defaults to `false`. If `true`, the Composer autoloader will check for APCu and +use it to cache found/not-found classes when the extension is enabled. + ## github-domains Defaults to `["github.com"]`. A list of domains to use in github mode. This is diff --git a/res/composer-schema.json b/res/composer-schema.json index 19b9bceb7..69f586c98 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -271,6 +271,10 @@ "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." }, + "apcu-autoloader": { + "type": "boolean", + "description": "If true, the Composer autoloader will check for APCu and use it to cache found/not-found classes when the extension is enabled, 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\"].", diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index e725330a1..6560e9db2 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -48,6 +48,11 @@ class AutoloadGenerator */ private $classMapAuthoritative = false; + /** + * @var bool + */ + private $apcu = false; + /** * @var bool */ @@ -75,6 +80,16 @@ class AutoloadGenerator $this->classMapAuthoritative = (boolean) $classMapAuthoritative; } + /** + * Whether or not generated autoloader considers APCu caching. + * + * @param bool $apcu + */ + public function setApcu($apcu) + { + $this->apcu = (boolean) $apcu; + } + /** * Set whether to run scripts or not * @@ -633,6 +648,14 @@ CLASSMAP; CLASSMAPAUTHORITATIVE; } + if ($this->apcu) { + $apcuPrefix = substr(base64_encode(md5(uniqid('', true), true)), 0, -3); + $file .= <<setApcuPrefix('$apcuPrefix'); + +APCU; + } + if ($useGlobalIncludePath) { $file .= <<<'INCLUDEPATH' $loader->setUseIncludePath(true); diff --git a/src/Composer/Autoload/ClassLoader.php b/src/Composer/Autoload/ClassLoader.php index 126b53882..4626994fd 100644 --- a/src/Composer/Autoload/ClassLoader.php +++ b/src/Composer/Autoload/ClassLoader.php @@ -55,6 +55,7 @@ class ClassLoader private $classMap = array(); private $classMapAuthoritative = false; private $missingClasses = array(); + private $apcuPrefix; public function getPrefixes() { @@ -271,6 +272,26 @@ class ClassLoader return $this->classMapAuthoritative; } + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + /** * Registers this instance as an autoloader. * @@ -320,6 +341,12 @@ class ClassLoader if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } $file = $this->findFileWithExtension($class, '.php'); @@ -328,6 +355,10 @@ class ClassLoader $file = $this->findFileWithExtension($class, '.hh'); } + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + if (false === $file) { // Remember that this class does not exist. $this->missingClasses[$class] = true; diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index aef44e00e..21263426f 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -341,6 +341,7 @@ EOT 'sort-packages' => array($booleanValidator, $booleanNormalizer), 'optimize-autoloader' => array($booleanValidator, $booleanNormalizer), 'classmap-authoritative' => array($booleanValidator, $booleanNormalizer), + 'apcu-autoloader' => array($booleanValidator, $booleanNormalizer), 'prepend-autoloader' => array($booleanValidator, $booleanNormalizer), 'disable-tls' => array($booleanValidator, $booleanNormalizer), 'secure-http' => array($booleanValidator, $booleanNormalizer), diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php index 94e2b4f6c..4c113b0b9 100644 --- a/src/Composer/Command/DumpAutoloadCommand.php +++ b/src/Composer/Command/DumpAutoloadCommand.php @@ -33,6 +33,7 @@ class DumpAutoloadCommand extends BaseCommand new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), 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('apcu', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables autoload-dev rules.'), )) ->setHelp(<<getOption('optimize') || $config->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); + $apcu = $input->getOption('apcu') || $config->get('apcu'); if ($optimize || $authoritative) { $this->getIO()->writeError('Generating optimized autoload files'); @@ -66,6 +68,7 @@ EOT $generator = $composer->getAutoloadGenerator(); $generator->setDevMode(!$input->getOption('no-dev')); $generator->setClassMapAuthoritative($authoritative); + $generator->setApcu($apcu); $generator->setRunScripts(!$input->getOption('no-scripts')); $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize); } diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index 3a007b557..b6ca802eb 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -47,6 +47,7 @@ class InstallCommand extends BaseCommand 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('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), 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.'), )) @@ -94,6 +95,7 @@ EOT $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); + $apcu = $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader'); $install ->setDryRun($input->getOption('dry-run')) @@ -106,6 +108,7 @@ EOT ->setSkipSuggest($input->getOption('no-suggest')) ->setOptimizeAutoloader($optimize) ->setClassMapAuthoritative($authoritative) + ->setApcuAutoloader($apcu) ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')) ; diff --git a/src/Composer/Command/RemoveCommand.php b/src/Composer/Command/RemoveCommand.php index 9813df790..c85a4e593 100644 --- a/src/Composer/Command/RemoveCommand.php +++ b/src/Composer/Command/RemoveCommand.php @@ -46,6 +46,7 @@ class RemoveCommand extends BaseCommand 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`.'), + new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), )) ->setHelp(<<remove command removes a package from the current @@ -120,12 +121,14 @@ EOT $updateDevMode = !$input->getOption('update-no-dev'); $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative'); + $apcu = $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader'); $install ->setVerbose($input->getOption('verbose')) ->setDevMode($updateDevMode) ->setOptimizeAutoloader($optimize) ->setClassMapAuthoritative($authoritative) + ->setApcuAutoloader($apcu) ->setUpdate(true) ->setUpdateWhitelist($packages) ->setWhitelistDependencies(!$input->getOption('no-update-with-dependencies')) diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index a54896c0e..df12b2928 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -54,6 +54,7 @@ class RequireCommand extends InitCommand 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`.'), + new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), )) ->setHelp(<<getOption('update-no-dev'); $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative'); + $apcu = $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader'); // Update packages $this->resetComposer(); @@ -158,6 +160,7 @@ EOT ->setSkipSuggest($input->getOption('no-suggest')) ->setOptimizeAutoloader($optimize) ->setClassMapAuthoritative($authoritative) + ->setApcuAutoloader($apcu) ->setUpdate(true) ->setUpdateWhitelist(array_keys($requirements)) ->setWhitelistDependencies($input->getOption('update-with-dependencies')) diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index ec04e1285..9444f6f77 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -52,6 +52,7 @@ class UpdateCommand extends BaseCommand 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('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), 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.'), @@ -128,6 +129,7 @@ EOT $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); + $apcu = $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader'); $install ->setDryRun($input->getOption('dry-run')) @@ -140,6 +142,7 @@ EOT ->setSkipSuggest($input->getOption('no-suggest')) ->setOptimizeAutoloader($optimize) ->setClassMapAuthoritative($authoritative) + ->setApcuAutoloader($apcu) ->setUpdate(true) ->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $packages) ->setWhitelistDependencies($input->getOption('with-dependencies')) diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 809dcb10e..88e9af73a 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -46,6 +46,7 @@ class Config 'sort-packages' => false, 'optimize-autoloader' => false, 'classmap-authoritative' => false, + 'apcu-autoloader' => false, 'prepend-autoloader' => true, 'github-domains' => array('github.com'), 'bitbucket-expose-hostname' => true, diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 5f257829b..6fb4146c3 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -105,6 +105,7 @@ class Installer protected $preferDist = false; protected $optimizeAutoloader = false; protected $classMapAuthoritative = false; + protected $apcuAutoloader = false; protected $devMode = false; protected $dryRun = false; protected $verbose = false; @@ -287,6 +288,7 @@ class Installer $this->autoloadGenerator->setDevMode($this->devMode); $this->autoloadGenerator->setClassMapAuthoritative($this->classMapAuthoritative); + $this->autoloadGenerator->setApcu($this->apcuAutoloader); $this->autoloadGenerator->setRunScripts($this->runScripts); $this->autoloadGenerator->dump($this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader); } @@ -1492,6 +1494,19 @@ class Installer return $this; } + /** + * Whether or not generated autoloader considers APCu caching. + * + * @param bool $apcuAutoloader + * @return Installer + */ + public function setApcuAutoloader($apcuAutoloader = false) + { + $this->apcuAutoloader = (boolean) $apcuAutoloader; + + return $this; + } + /** * update packages * diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index fafe59798..44f14d483 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -508,9 +508,10 @@ class AutoloadGeneratorTest extends TestCase ); $this->assertAutoloadFiles('classmap5', $this->vendorDir.'/composer', 'classmap'); $this->assertNotContains('$loader->setClassMapAuthoritative(true);', file_get_contents($this->vendorDir.'/composer/autoload_real.php')); + $this->assertNotContains('$loader->setApcuPrefix(', file_get_contents($this->vendorDir.'/composer/autoload_real.php')); } - public function testClassMapAutoloadingAuthoritative() + public function testClassMapAutoloadingAuthoritativeAndApcu() { $package = new Package('a', '1.0', '1.0'); @@ -535,6 +536,7 @@ class AutoloadGeneratorTest extends TestCase file_put_contents($this->vendorDir.'/c/c/foo/ClassMapBaz.php', 'generator->setClassMapAuthoritative(true); + $this->generator->setApcu(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."); @@ -549,6 +551,7 @@ class AutoloadGeneratorTest extends TestCase $this->assertAutoloadFiles('classmap8', $this->vendorDir.'/composer', 'classmap'); $this->assertContains('$loader->setClassMapAuthoritative(true);', file_get_contents($this->vendorDir.'/composer/autoload_real.php')); + $this->assertContains('$loader->setApcuPrefix(', file_get_contents($this->vendorDir.'/composer/autoload_real.php')); } public function testFilesAutoloadGeneration()