举例来说,假设我们的项目想要使用 monolog 这个日志工具,就需要在composer.json里告诉composer我们需要它:
{ "require": {"monolog/monolog": "1.*" }}之后执行:
php composer.phar install
好,现在安装完了,该怎么使用呢?Composer自动生成了一个autoload文件,你只需要引用它
require "/path/to/vendor/autoload.php";
然后就可以非常方便的去使用第三方的类库了,是不是感觉很棒啊!对于我们需要的monolog,就可以这样用了:
use MonologLogger;use MonologHandlerStreamHandler;// create a log channel$log = new Logger("name");$log->pushHandler(new StreamHandler("/path/to/log/log_name.log", Logger::WARNING));// add records to the log$log->addWarning("Foo");$log->addError("Bar");
在这个过程中,Composer做了什么呢?它生成了一个autoloader,再根据各个包自己的autoload配置,从而帮我们进行自动加载的工作。(如果对autoload这部分内容不太了解,可以看我之前的 一篇文章
)接下来让我们看看Composer是怎么做的吧。
对于第三方包的自动加载,Composer提供了四种方式的支持,分别是 PSR-0和PSR-4的自动加载(我的一篇文章也有介绍过它们),生成class-map,和直接包含files的方式。
PSR-4是composer推荐使用的一种方式,因为它更易使用并能带来更简洁的目录结构。在composer.json里是这样进行配置的:
{"autoload": {"psr-4": {"Foo\": "src/",}}}key和value就定义出了namespace以及到相应path的映射。按照PSR-4的规则,当试图自动加载 "Foo\Bar\Baz" 这个class时,会去寻找 "src/Bar/Baz.php" 这个文件,如果它存在则进行加载。注意, "Foo\"
并没有出现在文件路径中,这是与PSR-0不同的一点,如果PSR-0有此配置,那么会去寻找
"src/Foo/Bar/Baz.php"
这个文件。
另外注意PSR-4和PSR-0的配置里,"Foo\"结尾的命名空间分隔符必须加上并且进行转义,以防出现"Foo"匹配到了"FooBar"这样的意外发生。
在composer安装或更新完之后,psr-4的配置换被转换成namespace为key,dir path为value的Map的形式,并写入生成的 vendor/composer/autoload_psr4.php 文件之中。
{"autoload": {"psr-0": {"Foo\": "src/",}}}最终这个配置也以Map的形式写入生成的
vendor/composer/autoload_namespaces.php
文件之中。
Class-map方式,则是通过配置指定的目录或文件,然后在Composer安装或更新时,它会扫描指定目录下以.php或.inc结尾的文件中的class,生成class到指定file path的映射,并加入新生成的 vendor/composer/autoload_classmap.php 文件中,。
{"autoload": {"classmap": ["src/", "lib/", "Something.php"]}}例如src/下有一个BaseController类,那么在autoload_classmap.php文件中,就会生成这样的配置:
"BaseController" => $baseDir . "/src/BaseController.php"
Files方式,就是手动指定供直接加载的文件。比如说我们有一系列全局的helper functions,可以放到一个helper文件里然后直接进行加载
{"autoload": {"files": ["src/MyLibrary/functions.php"]}}它会生成一个array,包含这些配置中指定的files,再写入新生成的
vendor/composer/autoload_files.php
文件中,以供autoloader直接进行加载。
下面来看看composer autoload的代码吧
<?php// autoload_real.php @generated by Composerclass ComposerAutoloaderInit73612b48e6c3d0de8d56e03dece61d11{private static $loader;public static function loadClassLoader($class){ if ("ComposerAutoloadClassLoader" === $class) { require __DIR__ . "/ClassLoader.php"; }}public static function getLoader(){ if (null !== self::$loader) { return self::$loader; } spl_autoload_register(array("ComposerAutoloaderInit73612b48e6c3d0de8d56e03dece61d11", "loadClassLoader"), true, true); self::$loader = $loader = new ComposerAutoloadClassLoader(); spl_autoload_unregister(array("ComposerAutoloaderInit73612b48e6c3d0de8d56e03dece61d11", "loadClassLoader")); $vendorDir = dirname(__DIR__); //verdor第三方类库提供者目录 $baseDir = dirname($vendorDir); //整个应用的目录 $includePaths = require __DIR__ . "/include_paths.php"; array_push($includePaths, get_include_path()); set_include_path(join(PATH_SEPARATOR, $includePaths)); $map = require __DIR__ . "/autoload_namespaces.php"; foreach ($map as $namespace => $path) { $loader->set($namespace, $path); } $map = require __DIR__ . "/autoload_psr4.php"; foreach ($map as $namespace => $path) { $loader->setPsr4($namespace, $path); } $classMap = require __DIR__ . "/autoload_classmap.php"; if ($classMap) { $loader->addClassMap($classMap); } $loader->register(true); $includeFiles = require __DIR__ . "/autoload_files.php"; foreach ($includeFiles as $file) { composerRequire73612b48e6c3d0de8d56e03dece61d11($file); } return $loader;}}function composerRequire73612b48e6c3d0de8d56e03dece61d11($file){require $file;}首先初始化ClassLoader类,然后依次用上面提到的4种加载方式来注册/直接加载,ClassLoader的一些核心代码如下:
/*** @param array $classMap Class to filename map*/ public function addClassMap(array $classMap) {if ($this->classMap) { $this->classMap = array_merge($this->classMap, $classMap);} else { $this->classMap = $classMap;} } /*** Registers a set of PSR-0 directories for a given prefix,* replacing any others previously set for this prefix.** @param string$prefix The prefix* @param array|string $paths The PSR-0 base directories*/ public function set($prefix, $paths) {if (!$prefix) { $this->fallbackDirsPsr0 = (array) $paths;} else { $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;} } /*** Registers a set of PSR-4 directories for a given namespace,* replacing any others previously set for this namespace.** @param string$prefix The prefix/namespace, with trailing "\"* @param array|string $paths The PSR-4 base directories** @throws InvalidArgumentException*/ public function setPsr4($prefix, $paths) {if (!$prefix) { $this->fallbackDirsPsr4 = (array) $paths;} else { $length = strlen($prefix); if ("\" !== $prefix[$length - 1]) {throw new InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = (array) $paths;} } /*** Registers this instance as an autoloader.** @param bool $prepend Whether to prepend the autoloader or not*/ public function register($prepend = false) {spl_autoload_register(array($this, "loadClass"), true, $prepend); } /*** Loads the given class or interface.** @param string $class The name of the class* @return bool|null True if loaded, null otherwise*/ 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) {//这是PHP5.3.0 - 5.3.2的一个bug 详见https://bugs.php.net/50731if ("\" == $class[0]) { $class = substr($class, 1);}// class map 方式的查找if (isset($this->classMap[$class])) { return $this->classMap[$class];}//psr-0/4方式的查找$file = $this->findFileWithExtension($class, ".php");// Search for Hack files if we are running on HHVMif ($file === null && defined("HHVM_VERSION")) { $file = $this->findFileWithExtension($class, ".hh");}if ($file === null) { // Remember that this class does not exist. return $this->classMap[$class] = false;}return $file; } private function findFileWithExtension($class, $ext) {// PSR-4 lookup$logicalPathPsr4 = strtr($class, "\", DIRECTORY_SEPARATOR) . $ext;$first = $class[0];if (isset($this->prefixLengthsPsr4[$first])) { foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {if (0 === strpos($class, $prefix)) { foreach ($this->prefixDirsPsr4[$prefix] as $dir) {if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { return $file;} }} }}// PSR-4 fallback dirsforeach ($this->fallbackDirsPsr4 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {return $file; }}// PSR-0 lookupif (false !== $pos = strrpos($class, "\")) { // namespaced class name $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1). strtr(substr($logicalPathPsr4, $pos + 1), "_", DIRECTORY_SEPARATOR);} else { // PEAR-like class name $logicalPathPsr0 = strtr($class, "_", DIRECTORY_SEPARATOR) . $ext;}if (isset($this->prefixesPsr0[$first])) { foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {if (0 === strpos($class, $prefix)) { foreach ($dirs as $dir) {if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file;} }} }}// PSR-0 fallback dirsforeach ($this->fallbackDirsPsr0 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {return $file; }}// PSR-0 include paths.if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file;} }/** * Scope isolated include. * * Prevents access to $this/self from included files. */function includeFile($file){ include $file;}