>在现实世界中使用工具时,如果理解了工具的工作原理,使用起来就会更加有底气。对于一个第一次接触laravel,且是第一次接触 composer 的新手来说,如果理解Composer 是如何工作的,使用起来将会更加自如。 >我的理解是,composer 根据声明的依赖关系,从相关库的 源 下载代码文件,并根据依赖关系在 Composer 目录下生成供类自动加载的 PHP 脚本,使用的时候,项目开始处引入 “/vendor/autoload.php” 文件,就可以直接实例化这些第三方类库中的类了。那么,Composer 是如何实现类的自动加载的呢?接下来,我们从 laravel 的入口文件开始顺藤摸瓜往里跟进,来一睹 Composer 自动加载的奥妙。 1.代码清单 laravel/public/index.php #laravel/public/index.php require __DIR__.'/../bootstrap/autoload.php'; $app = require_once __DIR__.'/../bootstrap/start.php'; $app->run(); 第一行先是引入了 laravel/bootstrap/autoload.php,不做解释,打开该文件。 2.代码清单 laravel/bootstrap/autoload.php define('LARAVEL_START', microtime(true)); require __DIR__.'/../vendor/autoload.php'; if (file_exists($compiled = __DIR__.'/compiled.php')) { require $compiled; } Patchwork\Utf8\Bootup::initMbstring(); 第一行定义了程序开始执行的时间点。紧接着第二行,引入了 laravel/vendor/autoload.php。 第七行,前面说过,引入Composer的autoload.php之后就可以直接使用第三方类库中的类了,这里就是直接使用的 Bootup 类。下面来看看 /vendor/autoload.php 到底做了什么。 3.代码清单 laravel/vendor/autoload.php // autoload.php @generated by Composer require_once __DIR__ . '/composer' . '/autoload_real.php'; return ComposerAutoloaderInit9b2a1b1cf01c9a870ab98748dc5f1256::getLoader(); 到这里,马上就进入自动加在的大门了。 这个文件很简单,第5行的函数名是不是看的一头雾水?别被吓到了,他就是个类名而已。这个类是在第3行引入的文件 laravel/vendor/composer/autoload_real.php 里头声明的,接下来打开该文件看 getLoader(); 4.代码清单laravel/vendor/composer/autoload_real.php $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) { composerRequire9b2a1b1cf01c9a870ab98748dc5f1256($file); } return $loader; } } function composerRequire9b2a1b1cf01c9a870ab98748dc5f1256($file)及 $loader->addClassMap() { require $file; } 第17行,getLoader()中先是判断当前类中的 $loader 值,如果不是 null 就返回,这个可以略过。接着实例化了 ClassLoader 类给 $loader ,laravel/vendor/composer/ClassLoader.php 这里引入了几个文件,这些文件是由composer自动生成的,当依赖关系发生改变时不需要修改这些脚本,运行composer重新生成即可。 `laravel/vendor/composer/autoloade_namespace.php` `laravel/vendor/composer/autoloade_prs4.php` `laravel/vendor/composer/autoloade_classmap.php` `laravel/vendor/composer/autoloade_files.php` 在 设置完一堆的 path 信息后,执行了$loader->set()和 $loader->setPsr4() 及$loader->addClassMap(),然后 进行了$loader->register(true);现在我们一个个来看。 5.代码清单 `laravel/vendor/composer/ClassLoader.php` (自行查看) $loader->set($namespace, $path);Psr0标准 设置命名空间对应的路径,以便于随后自动加载相关类文件。 $loader->setPsr4($namespace, $path);Psr4标准 设置命名空间对应的路径,以便于随后自动加载相关类文件。 $loader->addClassMap($classMap); 设置类文件路径与类名的对应关系,以便于随后自动加载相关类文件。 $loader->register(true); public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); } 这里设置了 欲注册的自动装载函数 $this->loadClass(),关于 spl_autoload_register 和 spl_autoload_unregister 的更多信息随后会有专门的解释。现在打开loadClass()的定义 public function loadClass($class) { if ($file = $this->findFile($class)) { includeFile($file); return true; } } 这里有个 findFile() 函数,如果相关类的声明所在文件的路径找到了,就包含并运行该文件,然后返回 true 。接着打开findFile()的定义 public function findFile($class) { // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 if ('\\' == $class[0]) { $class = substr($class, 1); } // class map lookup if (isset($this->classMap[$class])) { return $this->classMap[$class]; } $file = $this->findFileWithExtension($class, '.php'); // Search for Hack files if we are running on HHVM if ($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; } 先是判断类名是否以'\'开始,如果是的话,清除开头的'\' 接着,检查当前类的名字是否在 类名与声明当前类的文件的路径的关系数组 中,如果存在,直接返回相关键值(类文件路径信息) 如果上一步没有返回路径信息,执行 findFileWithExtension($class, '.php') 继续查找类文件路径信息,findFileWithExtension的定义后面将会列出。 如果仍未找到类的文件路径信息,返回值为 null 且定义了 HHVM_VERSION 信息,则用“.hh”后缀继续查找类文件信息。 HHVM_VERSION 是 HHVM版本信息? HHVM 是 Facebook 开发的高性能 PHP 虚拟机,宣称比官方的快9倍。 如果仍未找到,则返回 false 。Remember that this class does not exist.(这句注释很有喜感?哈哈) 代码清单 findFileWithExtension() 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 dirs foreach ($this->fallbackDirsPsr4 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } // PSR-0 lookup if (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 dirs foreach ($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; } } 该函数唯一的目的就是根据刚才通过$loader->set($namespace, $path)和$loader->setPsr4($namespace, $path)方法设置的信息找出类文件的路径信息。 接下来,我们回到代码清单laravel/vendor/composer/autoload_real.php ,为精简篇幅,这里只贴出片段(续上节的 $loader->register(true))。 $loader->register(true); $includeFiles = require __DIR__ . '/autoload_files.php'; foreach ($includeFiles as $file) { composerRequire9b2a1b1cf01c9a870ab98748dc5f1256($file); } return $loader; } } function composerRequire9b2a1b1cf01c9a870ab98748dc5f1256($file) { require $file; } 在经历了一番长途跋涉后,终于从 laravel/vendor/composer/ClassLoader.php 中出来了。继 $loader->register(true) 之后,又引入了laravel/vendor/composer/autoload_files.php,相比之下,这个文件要简单得多,只是个数组,列 出了几个文件路径。 // autoload_files.php @generated by Composer $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( $vendorDir . '/ircmaxell/password-compat/lib/password.php', $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php', $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Random.php', $vendorDir . '/laravel/framework/src/Illuminate/Support/helpers.php', ); 接着就是用 composerRequire9b2a1b1cf01c9a870ab98748dc5f1256() 函数 包含并运行 这几个文件。这四个文件的具体信息,随后会专门写博文来认识。 最后 , 返回 ClassLoader 类的实例 $loader 。 现在在回到 代码清单 laravel/bootstrap/autoload.php 的第7行 define('LARAVEL_START', microtime(true)); require __DIR__.'/../vendor/autoload.php'; if (file_exists($compiled = __DIR__.'/compiled.php')) { require $compiled; } Patchwork\Utf8\Bootup::initMbstring(); 注意这里第7行,之所以可以直接像 Patchwork\Utf8\Bootup::initMbstring() 这么使用而不需要手动required Bootup类文件,是因为 前面在ClassLoader中的register() 函数用 spl_autoload_register() 对Bootup类进行了注册。下面说一下 spl_autoload_register 。 spl_autoload_register 要使用 spl_autoload_register ,请保证你的PHP版本(PHP 5 >= 5.1.2)。 www.php.net 对 spl_autoload_register 的解释如下:注册__autoload()函数,将函数注册到SPL __autoload函数栈中。如果该栈中的函数尚未激活,则激活它们。刚接触 PHP 的同学肯定觉得这个解释云里雾里的,看完依然不知道什么意思。要想理解这句话,首先要弄明白 __autoload() 是个什么东西。 __autoload() __autoload 的作用是尝试加载未定义的类,可以通过定义这个函数来启用类的自动加载。下面举个例子: function __autoload($class) { echo '尝试加载的类的名字是:'.$class; } $say= @ new say(); 上例会输出:"尝试加载的类的名字是 say "。由于最后一行引用了尚未定义的类 box ,所以 __autoload 函数将被执行。 再看下面这段 class say { public function __construct() { echo 'say 类存在,并说出了hello,所以 __autoload 函数不会执行。'; } } function __autoload($class) { echo '尝试加载的类的名字是:'.$class; } $say= @ new say(); 这将会输出 : say 类存在,并说出了hello,所以 __autoload 函数不会执行。 理解完 __autoload 就好办了,再看上面:“将函数注册到SPL __autoload函数栈中”,意思是我们可以自定义 尝试加载未定义的类时 使用的函数。现在返回代码片段spl_autoload_register(array($this, 'loadClass'), true, $prepend);这下是不是很明白了,当实例化一个类的时候,如果这个类没有定义,就执行 ClassLoader 类中的 loadClass 函数。loadClass 的定义前面我们说过了,就是找到声明相关类的文件,然后包含并运行该文件,随后加载相关类。至此,composer的自动加载机制学习完毕。