October 27, 2016
什么是composer
Composer 是 PHP 的一个依赖管理工具。它允许你申明项目所依赖的代码库,并根据所声明的依赖在项目中为你安装他们。composer不是包管理工具,因为真正的包位于packagist和github上面,composer相当于中间介质帮你安装你所声明的包,所以composer是一个依赖管理工具。
安装composer
composer需要php>=5.3.2,安装之前请确保你已经安装php,且版本不低于5.3.2。
composer支持多平台,这里只介绍linux上安装,我们使用composer中国全量镜像上提供的安装方式,执行以下命令:
php -r "readfile('https://getcomposer.org/installer');" > composer-setup.php
php composer-setup.php
php -r "unlink('composer-setup.php');"
这时composer就已经下载好了,你会在当前目录找到一个composer.phar二进制文件,执行php composer.phar
即可看到composer相关信息和一些命令的用法,但是往往我们不会这么使用composer,而是通过composer + 命令
这样使用,这里我们需要对composer进行全局安装,将composer.phar文件移动到PATH变量的某个目录中即可,如mv composer.phar /usr/local/bin/composer
,这样你就可以在任意目录执行composer
使用composer了。
如何使用composer
- 创建composer.json文件
- 执行composer install安装依赖包
- 在php文件中引入依赖,require vendor/autoload.php
注:这里可以不用创建composer.json文件,直接通过
composer require 供应商/包名
即可安装依赖包,也推荐这样使用,更加方便。
配置资源镜像
由于默认的包是从国外的服务器上下载下来的,下载速度很慢,在使用composer之前请将资源下载地址配置到国内的镜像服务器上,执行:
composer config -g repo.packagist composer https://packagist.phpcomposer.com
如果不加-g参数,请确保当前目录存在一个合法的composer.json文件,且该配置只对当前composer.json有效。
demo
这里我用一个例子来演示下,比如我的项目中现在需要一个解析配置文件的通用方法,我在github上找到一个包hassankhan/config,它支持ini,xml,json,yaml,php等多种文件格式的解析(该包的具体使用方法请自行查看),下面我来安装这个包到我的项目中并解析获取一个ini配置文件的参数。 首先,在项目根目录下建立一个包含以下内容的composer.json文件,声明需要依赖的包。
{
"require": {
"hassankhan/config": "0.10.*"
}
}
OK,执行composer install
,然后再创建一个config.ini和index.php文件:
# config.ini
[mysql]
user=root
password=root
port=3306
[mongodb]
user=root
password=root
port=27017
<?php
// index.php
require ('vendor/autoload.php');
use Noodlehaus\Config;
$conf = Config::load('config.ini');
echo $config->get('mysql.port') . PHP_EOL;
在index.php中,我要获取config.ini中mysql区块下的port值,执行php index.php
,得到以下结果,至此,我们成功的引用了这个包到我们的项目中,so easy!
composer 的原理
通过上面的demo,我们已经知道如何使用composer了,但它是如何工作的呢?现在来分析下composer的原理,在执行composer install时究竟发生了什么?
可以看出,composer先是安装依赖包,然后将安装的包信息写入lock锁文件,最后生成自动加载文件。查看当前目录下的内容,会发现多了一个composer.lock文件和一个vendor目录,这是composer自动生成的。
composer.lock
composer.lock记录了安装的包的具体版本,包的信息,md5值等。注意:在你执行composer install
时,composer会检查当前目录是否存在composer.lock文件,若存在,则根据该lock文件来安装依赖包而忽略composer.json声明的依赖;若不存在才根据composer.json中的声明来安装。
那么,如果我更改了composer.json需要用到其他的依赖包呢?执行composer update
(或者通过composer require 供应商/包名称
来引入)即可,update命令会同时更新composer.lock文件和vendor目录里面的内容。
vendor目录
再来看看vendor目录
autoload.php 需要引入的入口文件
composer 自动加载文件的目录
hasankhan 项目源代码目录
大家有没有想过,为什么只需要引入autoload.php就能调用这个包呢?composer是如何处理自动加载关系的呢?来看看autoload.php
可以看到,它引入了composer/autoload_real.php文件,然后调用了这个类文件的getLoader()静态方法
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInitba35a65e937248ede03565e79e5211fe
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInitba35a65e937248ede03565e79e5211fe', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInitba35a65e937248ede03565e79e5211fe', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitba35a65e937248ede03565e79e5211fe::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);
}
}
$loader->register(true);
return $loader;
}
}
看看getLoader()这个静态方法,前面几行spl_autoload_register之类的其实就是函数注册,不用细看,主要看后面几个require,它引入了几个autoload_*.php文件,composer加载依赖关系的精髓就在这几个文件中。
自动加载
composer是通过composer.json文件中的autoload字段来声明自动加载方式的。它提供了4种加载类型:
- psr-0
- psr-4
- classmap
- files
psr-0
主要用于带有命名空间的类的自动加载,对应了命名空间和目录的映射,可用于带有下划线的类。此规范已被FIG官方废弃,请使用psr-4代替。
{
"autoload": {
"psr-0": {
"Monolog\\": "src/",
"Vendor\\Namespace\\": "src/",
"Vendor_Namespace_": "src/"
}
}
}
如若要查找Monolog\Logger这个class时,则其会去寻找src/Monolog/Logger.php,在生成自动加载文件时,会将命名空间与目录的映射以数组的形式写入vendor/composer/autoload_namespaces.php
psr-4
和psr-0作用一样,用于替代psr-0,但对于相同的命名空间与目录映射关系,psr-4对应的目录结构比psr-0浅,例如要查找Monolog\Logger这个class,则其会去寻找src/Logger.php,与psr-0相比少了一层命名空间目录,使用起来更加方便。 在生成自动加载文件时,会将命名空间与目录的映射以数组的形式写入vendor/composer/autoload_psr4.php
{
"autoload": {
"psr-4": {
"Monolog\\": "src/"
}
}
}
注意:命名空间必须以\\结尾,第一个\是对第二个\进行转义,\分隔符是为了防止匹配到相似的命名空间,假设存在MonologTest这样的命名空间,若不加\\,在匹配Monolog时可能会匹配到MonologTest。
classmap
该自动加载类型用于声明不包含命名空间的类
{
"autoload": {
"classmap":["lib/","src/other.php"]
}
}
如上述classmap加载方式会包含lib目录下所有.php和.inc结尾的文件和src/other.php,在生成自动加载文件时,会将类名与文件路径的映射以数组的形式写入vendor/composer/autoload_classmap.php
files
主要用于声明全局函数的文件而不是类文件
{
"autoload": {
"files": ["src/functions.php"]
}
}
这种加载方式使得src/functions.php中的函数可以在全局调用了,同样,在生成自动加载文件时,会将映射关系以数组的形式写入vendor/composer/autoload_files.php
自动加载演示
例如我的项目根目录test下的类库文件目录结构是这样的
test/
src/
lib/
DB.php
Main.php
functions.php
index.php
composer.json
src目录下的内容是源代码,其代码分别如下
<?php
// DB.php
class DB
{
static public function getInstance()
{
echo 'you are calling getInstance method' . PHP_EOL;
}
}
<?php
// Main.php
namespace Ths;
class Main
{
public function getData()
{
echo 'you are calling getData method' . PHP_EOL;
}
}
<?php
// functions.php
function getCommon()
{
echo 'you are calling getCommon method' . PHP_EOL;
}
根据类库文件,在composer.json中定义自动加载方式(其路径都是相对composer.json所在路径来定义的)
{
"autoload": {
"psr-4": { "Ths\\": "src/"
},
"classmap":["src/lib/"],
"files": ["src/functions.php"]
}
}
然后执行composer install
,执行完后进入vendor/composer/,查看autoload_*.php文件,均已生成自动加载对应的映射关系(因为并未定义psr-0,所以其返回的是一个空数组)。
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Ths\\' => array($baseDir . '/src'),
);
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'DB' => $baseDir . '/src/lib/DB.php',
);
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'5d80ba682afba25d348d62676196765b' => $baseDir . '/src/functions.php',
);
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);
现在我们在index.php中调用这些文件里的几个方法,代码如下:
<?php
require 'vendor/autoload.php';
use Ths\Main;
$main = new Main();
$main->getData(); // 输出:you are calling getData method
DB::getInstance(); // 输出:you are calling getInstance method
getCommon(); // 输出:you are calling getCommon method
到这里,我们成功的定义了自动加载方式,并使用了它,我想现在大家应该很清楚了,composer的自动加载其实就是根据autoload的定义,将对应的映射关系写入各自的autoload_*.php中,然后require进来,就可以直接调用各个类中的方法了。建议大家亲自试试,对自动加载的更加容易理解。
那么,我现在需要修改类库,比如在lib/下新增了一个类文件,现在能直接调用吗?不能,因为我们的autoload_.php中并没有该类文件的映射,这时我们就要更新composer/下的autoload_.php了,执行composer dump-autoload
命令即可,这样我们就可以调用新增的类库文件了。
composer.json架构
具体架构请参考官方文档,这里只讲解几个字段 autoload 自动加载 autoload-dev 开发环境自动加载 requrie 依赖声明 requrie-dev 开发环境依赖声明 比如在开发时,我们往往会写一个单元测试包,需要用到phpunit,这时可以在composer.json中这样定义
{
"require-dev":{
"phpunit/phpunit":"~4.0"
},
"autoload-dev": {
"psr-4": { "Ths\\Test\\": "tests/"
}
}
}
其中,tests是自己写的单元测试。
如果composer.json包含require-dev字段,则composer install会默认安装dev声明的依赖包,如果不想这么做,在install命令后面加上–no-dev参数即可,即composer install --no-dev
(完)