基本用法
概念
Composer 不是一个包管理器。是的,它涉及 "packages" 和 "libraries",但它在每个项目的基础上进行管理,在你项目的某个目录中(例如
vendor
)进行安装。默认情况下它不会在全局安装任何东西。因此,这仅仅是一个依赖管理。
Composer 主要解决以下问题
你有一个项目依赖于若干个库。
其中一些库依赖于其他库。
你声明你所依赖的东西。
Composer 会找出哪个版本的包需要安装,并安装它们(将它们下载到你的项目中)。
使用
安装Composer
使用Composer 只需要创建一个 composer.json
文件,其中定义了项目的基本信息和依赖
{
"require": {
"monolog/monolog": "1.2.*"
}
}
定义好依赖关系后,执行 composer install
即可将依赖的库或项目下载到你项目的vendor文件夹下
也可以直接使用 composer require
命令直接安装依赖。
除了 composer.json
文件,composer 还会生成 composer.lock
- 锁文件,锁文件记录着详细的依赖信息,用于多人协同工作时依赖库的同步。
命令行
-
composer init
初始化
composer install
安装composer update
更新composer require
声明依赖composer global
全局执行composer search
搜索包composer show
展示composer depends
依赖性检测
(库)资源包
composer.json 结构
包名 name
描述 description
版本 version
安装类型 type
关键字 keywords
项目主页 homepage
版本发布时间 time
许可协议 license
作者 author
Package links
Composer 的自动加载
PHP自动加载
在一般的PHP程序开发中,我们通常使用 require
和 include
引入文件。
这在一般小规模的开发中没什么问题,但是开发大型项目需要多人合作时就会带来一些隐性的问题。如果一个PHP文件需要引入大量的其他的文件时,需要写大量的 require 或 include 语句,这样可能会遗漏或引入没用的文件,从而影响性能。如果各个文件之间有更加复杂的引入关系,就会很容易造成混乱。
__autoload
函数
PHP5 为了解决这个问题,引入了 类的自动加载机制
autload机制可以在类使用的时候才引入对应的包含类的文件,而不是一开始就加载进来,这种加载方式也称为 Lazy loading(惰性加载)
function __autoload() {
require_once ($classname . ".class.php");
}
__autoload
函数存在的问题
如果一个系统中需要引入大量其他的类库,这些类库可能由不同的开发人员编写的,其类名与实际的磁盘文件的映射规则不尽相同,如果这些类库要全部实现自动加载,则需要在 __autoload
函数中实现所有的映射规则。这样 __autoload
函数的实现会非常复杂和臃肿,甚至可能无法实现。
造成这样结果的主要原因是 __autoload()
是全局函数只能定义一次,不够灵活。如何解决这个问题呢?答案是使用 __autoload
调用堆栈 , 不同的映射关系写到不同的 autoload 函数中,然后统一注册管理,所以PHP5中引入了 Spl Autoload
SPL Autoload
SPL 是 Standard PHP Library(标准PHP库)的缩写。
使用 spl_autoload_register
函数 注册自动加载函数
function my_autoloader($class) {
include "classes/" . $class . ".class.php";
}
spl_autoload_register('my_autoloader');
spl_autoload_register() 就是我们上面所说的 __autoload 调用堆栈,我们可以向这个函数注册多个我们自己的 autoload() 函数,当PHP找不到类名时,PHP就会调用这个堆栈,然后去调用自定义的 autoload() 函数,实现自动加载功能。如果我们不向这个函数输入任何参数,那么就会默认注册 spl_autoload() 函数。
PSR 规范
PSR 是PHP标准组织推出的一套PHP开发标准,其中有7套PSR规范已通过表决并推出使用,分别是:
PSR-0 自动加载标准 (已废弃,一些旧的第三方库还有在使用)
PSR-1 基础编码标准
PSR-2 编码风格导向
PSR-3 日志接口
PSR-4 自动加载增强版
PSR-6 缓存接口规范
PSR-7 HTTP 消息接口规范
PSR4 标准
一个完整的类名需具有以下结构
/<命名空间>/<字命名空间>/<类名>
Composer 自动加载过程
执行 composer require 时发生了什么
- composer 会找到符合 PR4 规范的第三方库的源
- 将其加载到 vendor 目录下
- 初始化顶级域名的映射并写入到指定的文件里
- 写好一个 autoload 函数,并且注册到spl_autoload_register() 里
Composer 实现自动加载的文件
-
autoload_real.php 自动加载功能的引导类
- composer 加载类的初始化(顶级命名空间与文件路径映射初始化)和注册
-
ClassLoader.php composer 加载类
- composer 自动加载功能的核心类
-
autoload_static.php 顶级命名空间初始化类
- 用于给核心类初始化顶级域名空间
-
autoload_classmap.php 自动加载的最简单形式
- 有完整的命名空间和文件目录的映射
-
autoload_files.php 用于加载全局函数的文件
- 存放各个全局函数所在的文件路径名
-
autoload_namespaces.php 符合PSR0标准的自动加载文件
- 存放着顶级命名空间与文件的映射
-
autoload_psr4.php 符合PSR4标准的自动加载文件
- 存放着顶级命名空间与文件的映射
Composer 源码分析
启动
很多框架都会在初始化的时候通过 composer 实现自动加载,以下代码以 laravel 为例
define('LARAVEL_START', microtime(true));
require __DIR__.'/../vendor/autoload.php';
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit56e0da52b922ff4669d17341a618d14e::getLoader();
引入composer 的自动加载文件,调用 getLoader
函数,Compoer 正式开始
autoload_real 引导类
在 autoload.php 文件中我们可以看出,程序主要调用了引导类的静态方法 getLoader() 来实现自动加载的注册。
接下来我们来详细的看一下这个函数
-
单例
一开始先实现了一个简单的单例模式,自动加载类只能有一个
if (null !== self::$loader) { return self::$loader; }
-
构造 ClassLoader 核心类
将
loadClassLoader
方法注册自动加载,并实例化一个自动加载实现的核心类ClassLoader
spl_autoload_register(array('ComposerAutoloaderInit46b6d6d7d0d5071f29f6ebcb25245e20', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); spl_autoload_unregister(array('ComposerAutoloaderInit46b6d6d7d0d5071f29f6ebcb25245e20', 'loadClassLoader'));
loadClassLoader 方法代码:
public static function loadClassLoader($class) { if ('Composer\Autoload\ClassLoader' === $class) { require __DIR__ . '/ClassLoader.php'; } }
-
初始化核心类对象
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInit46b6d6d7d0d5071f29f6ebcb25245e20::getInitializer($loader)); } else { $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); } }
这个部分主要是对自动加载类的初始化,给自动加载类定义各顶级命名空间的映射。
加载顶级命令空间映射主要有两种方式
- 通过 autoload_static.php 静态初始化
- 调用核心类接口初始化
autoload_static
静态初始化 PHP 版本必须 >= 5.6,而且不支持 HHVM 虚拟机使用。该方法主要通过 getInitializer方法实现初始化,这个方法的实现也非常简单,最终会将自己类中的顶级命名空间的映射给ClassLoader
自动加载类。public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInit46b6d6d7d0d5071f29f6ebcb25245e20::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInit46b6d6d7d0d5071f29f6ebcb25245e20::$prefixDirsPsr4; }, null, ClassLoader::class); }
至此自动加载类就初始化完成了。
运行第三方库
终于到了最核心的部分了,composer 实现自动加载的秘密,如何把获取到的命名空间转换为对应的文件目录。
ClassLoder 的 register() 函数将 loadClass() 函数注册到PHP的 SPL 函数堆栈中,每当PHP遇到不认识的类命名空间时就会自动调用堆栈中的每个函数直到类加载成功或函数调用完为止。所以loadClass() 函数就是类自动加载的关键了。
loadClass() 函数
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
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;
}
从代码中可以看到 loadClass() 函数主要调用 findFile
方法,findFile() 方法主要找到每个类对应的文件所在的位置。
寻找对应的目录文件主要分为两个部分 classMap
和 findFileWithExtension
。classMap 实现较为简单,就是 直接看命名空间是否在映射数组中即可。findFileWithExtension 的实现较为复杂,他需要根据 PSR0 和 PSR4 标准找到对应的文件。