Composer是用PHP开拓的用来管理项目依赖的工具,当你在项目中声明了依赖关系后,composer可以自动帮你下载和安装这些依赖库,并实现自动加载代码。
定义一个composer.json:
{\"大众name\公众: \"大众gitlib/composer\公众,\"大众require\公众:{\"大众predis/predis\公众:\"大众1.1.1\公众}}
输入命令 composer install,composer会帮我们自动下载predis库,依赖库会默认放在项目的vendor目录下。

├── composer.json├── composer.lock├── index.php└── vendor ├── autoload.php ├── composer │ ├── ClassLoader.php │ ├── LICENSE │ ├── autoload_classmap.php │ ├── autoload_namespaces.php │ ├── autoload_psr4.php │ ├── autoload_real.php │ ├── autoload_static.php │ └── installed.json └── predis └── predis
composer不仅仅帮我们处理依赖,还帮我们实现了自动加载。在vendor目录下有一个autoload.php, 只要在我们的项目中引入这个文件就可以自动加载依赖库。
<?phprequire 'vendor/autoload.php';$client = new Predis\Client();$client->set('foo', 'bar');$value = $client->get('foo');echo $value;
可以看到Predis库完备不须要我们手动去加载,只须要require 'vendor/autoload.php',composer的自动加载机制会帮我们找到对应的文件并加载。
对付依赖库,composer帮我们处理好了自动加载, 那对付其他的类库,如何实现自动加载呢?
composer支持四种自动加载的办法:Files/Classmap/PSR-0/ PSR-4, 个中PSR-4是当前推举的加载办法。
FilesFiles 是最大略的加载办法,这种办法不管加载的文件是否用到始终都会加载,而不是按需加载, 修正项目根眼前的composer.json, 加入 “autoload” 项:
{\"大众name\公众: \"大众gitlib/composer\"大众,\公众require\公众:{\"大众predis/predis\公众:\公众1.1.1\"大众},\"大众autoload\"大众:{\公众files\"大众:[\"大众Controller/User.php\"大众]}}
files键对应的值是一个数组,数组元素是文件的路径,路径是相对付运用的根目录。加上上述内容后,运行命令:
composer dump-autoload让composer重修自动加载的信息,composer会把配置值写入与 Files加载办法对应的 verndor\composer\autoload_files.php配置文件中:
<?php// autoload_files.php @generated by Composer$vendorDir = dirname(dirname(__FILE__));$baseDir = dirname($vendorDir);return array( '7efd69bb86214589340b40039fd363f7' => $baseDir . '/Controller/User.php',);
现在就可以在代码中里调用User类了。
<?phprequire 'vendor/autoload.php';$client = new Predis\Client();$user = new \Controller\User();$user->login();Classmap
classmap引用的所有组合,都会在 install/update 过程中天生,并存储到vendor/composer/autoload_classmap.php 文件中。这个 map 是经由扫描指定目录(同样支持直接精确到文件)中所有的 .php 和 .inc 文件里内置的类而得到的。
{\"大众name\公众: \"大众gitlib/composer\"大众,\公众require\公众:{\公众predis/predis\"大众:\公众1.1.1\"大众},\"大众autoload\公众:{\"大众classmap\"大众:[\公众Controller\公众]}}
Composer会扫描Controller目录下的所有.php和.inc文件,存储到vendor/composer/autoload_classmap.php文件中:
<?php// autoload_classmap.php @generated by Composer$vendorDir = dirname(dirname(__FILE__));$baseDir = dirname($vendorDir);return array( 'Controller\\User' => $baseDir . '/Controller/User.php',);PSR-0
PSR-0自动加载规范是已经废弃的标准, 不再做解释。
PSR-4PSR-4是Composer推举利用的一种办法(关于PSR规范可参考:PHP标准规范PSR),由于它更易利用并能带来更简洁的目录构造。对付上面的Controller目录我们先改名src:
├── composer.json├── composer.json.bk├── composer.lock├── index.php├── src│ └── User.php└── vendor ├── autoload.php ├── composer └── predis
在composer.json中我们将Controller命名空间和src关联起来:
{\"大众name\"大众: \"大众gitlib/composer\"大众,\"大众require\公众:{\"大众predis/predis\"大众:\公众1.1.1\公众},\"大众autoload\公众:{\"大众psr-4\"大众: {\"大众Controller\\\"大众:\公众src/\"大众}}}PSR-4 的命名空间前缀也必须以 \\ 结尾,以避免类似前缀间的冲突。
psr-4中的key和value定义了namespace以及其对应的目录映射。按照PSR-4的规则,当试图自动加载”Controller\User”类的利用,会去探求”src/User.php”这个文件,此时Controller并不会涌如今文件路径中。
自动加载事理下面我们通过源码剖析composer是如何实现自动加载功能。
入口<?phprequire 'vendor/autoload.php';
我们通过require ‘vendor/autoload.php实现自动加载,vendor/autoloaad.php文件引用composer/autoload_real.php。
<?php// autoload.php @generated by Composerrequire_once __DIR__ . '/composer/autoload_real.php';return ComposerAutoloaderInitb84761f57e62a6a534584b91ca213591::getLoader();autoload_real
autoload_real.php是自动加载勾引类,程序紧张调用了勾引类的静态方法getLoader()。
<?php// autoload_real.php @generated by Composerclass ComposerAutoloaderInitb84761f57e62a6a534584b91ca213591{ private static $loader; public static function loadClassLoader($class) { if ('Composer\Autoload\ClassLoader' === $class) { require __DIR__ . '/ClassLoader.php'; } } public static function getLoader() { // 返回Composer\Autoload\ClassLoader单例 if (null !== self::$loader) { return self::$loader; } // 调用spl_autoload_register加载\Composer\Autoload\ClassLoader spl_autoload_register(array('ComposerAutoloaderInitb84761f57e62a6a534584b91ca213591', 'loadClassLoader'), true, true); // 实例化\Composer\Autoload\ClassLoader类 self::$loader = $loader = new \Composer\Autoload\ClassLoader(); spl_autoload_unregister(array('ComposerAutoloaderInitb84761f57e62a6a534584b91ca213591', 'loadClassLoader')); // 静态初始化只支持 PHP5.6 以上版本并且不支持 HHVM 虚拟机 $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { // 利用 autoload_static 进行静态初始化 require_once __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInitb84761f57e62a6a534584b91ca213591::getInitializer($loader)); } else { // 如果PHP版本低于 5.6 或者利用 HHVM 虚拟机环境,那么就要利用核心类的接口进行初始化 // PSR0 标准 $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { $loader->set($namespace, $path); } // PSR4 标准 $map = require __DIR__ . '/autoload_psr4.php'; foreach ($map as $namespace => $path) { $loader->setPsr4($namespace, $path); } // classmap $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); } } $loader->register(true); // files if ($useStaticLoader) { $includeFiles = Composer\Autoload\ComposerStaticInitb84761f57e62a6a534584b91ca213591::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } // files定义的文件,直接require就行了 foreach ($includeFiles as $fileIdentifier => $file) { composerRequireb84761f57e62a6a534584b91ca213591($fileIdentifier, $file); } return $loader; }}autoload_static
<?php// autoload_static.php @generated by Composernamespace Composer\Autoload;class ComposerStaticInitb84761f57e62a6a534584b91ca213591{ public static $files = array ( '7efd69bb86214589340b40039fd363f7' => __DIR__ . '/../..' . '/Controller/User.php', ); public static $prefixLengthsPsr4 = array ( 'P' => array ( 'Predis\\' => 7, ), 'C' => array ( 'Controller\\' => 11, ), ); public static $prefixDirsPsr4 = array ( 'Predis\\' => array ( 0 => __DIR__ . '/..' . '/predis/predis/src', ), 'Controller\\' => array ( 0 => __DIR__ . '/../..' . '/src', ), ); public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInitb84761f57e62a6a534584b91ca213591::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInitb84761f57e62a6a534584b91ca213591::$prefixDirsPsr4; }, null, ClassLoader::class); }}
静态初始化类的核心便是 getInitializer() 函数,它将自己类中的顶级命名空间映射给了 ClassLoader 类。
PSR4 标准顶级命名空间映射用了两个数组,第一个是用命名空间第一个字母作为前缀索引,然后是 顶级命名空间,但是终极并不是文件路径,而是 顶级命名空间的长度。为什么呢?
由于 PSR4 标准是用顶级命名空间目录更换顶级命名空间,以是得到顶级命名空间的长度很主要。
ClassLoaderpublic function register($prepend = false){ spl_autoload_register(array($this, 'loadClass'), true, $prepend);}public function loadClass($class){ if ($file = $this->findFile($class)) { includeFile($file); return true; }}/ Finds the path to the file where the class is defined. @param string $class The name of the class @return string|false The path if found, false otherwise/public function findFile($class){ // class map lookup if (isset($this->classMap[$class])) { return $this->classMap[$class]; } 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'); // Search for Hack files if we are running on HHVM if (false === $file && defined('HHVM_VERSION')) { $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; } return $file;}function includeFile($file){ include $file;}
ClassLoader 的 register() 函数将 loadClass() 函数注册到 PHP 的 SPL 函数堆栈中,每当 PHP 碰着不认识的命名空间时就会调用函数堆栈的每个函数,直到加载命名空间成功。以是 loadClass() 函数便是自动加载的关键了。