浏览代码

Add --apcu-autoloader option to enable APCu caching of found/not-found classes

Nicolas Grekas 8 年之前
父节点
当前提交
6d4e60b991

+ 5 - 0
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

+ 5 - 0
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

+ 4 - 0
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\"].",

+ 23 - 0
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 .= <<<APCU
+        \$loader->setApcuPrefix('$apcuPrefix');
+
+APCU;
+        }
+
         if ($useGlobalIncludePath) {
             $file .= <<<'INCLUDEPATH'
         $loader->setUseIncludePath(true);

+ 31 - 0
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;

+ 1 - 0
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),

+ 3 - 0
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(<<<EOT
@@ -56,6 +57,7 @@ EOT
 
         $optimize = $input->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('<info>Generating optimized autoload files</info>');
@@ -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);
     }

+ 3 - 0
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'))
         ;
 

+ 3 - 0
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(<<<EOT
 The <info>remove</info> 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'))

+ 3 - 0
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(<<<EOT
 The require command adds required packages to your composer.json and installs them.
@@ -138,6 +139,7 @@ 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');
 
         // 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'))

+ 3 - 0
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'))

+ 1 - 0
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,

+ 15 - 0
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
      *

+ 4 - 1
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', '<?php class ClassMapBaz {}');
 
         $this->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()