一、composer 快速入门
1.1、composer 是什么
① composer 是 PHP 的一个依赖管理工具
②安装 composer
③ 资源插件:解决 js、css 的依赖的:composer. global require "fxp/composer-asset- plugin:^1.2.0"
1.2、composer、github、packagist 之间的关系
例如:有一个 A 包,它依赖于 B、C 包
当执行 composer require A 时,composer 首先去到 packagist(存 放A、B、C三个包的依赖关系) 查找 A 包的依赖(找到 A依赖B、C),然后再到 github(存放 A、B、C 三个包) 下载 A、B、C三个包。
总结:packagist 是存放A、B、C三个包的依赖关系的库、github 是存放 A、B、C 三个包的库、composer 是一款下载工具
注:composer require --prefer-dist A(--prefer-dist 强制使用压缩包)
composer require --prefer-source A(--prefer-source 强制克隆源码)
1.3、Packagist 镜像
由于某些原因,github 和 packagist 在国内访问速度很慢。中国镜像定期把 github 和 packagist 的文件放在服务器,我们只需要把 仓库的路径修改为镜像的路径即可。
有两种方式,一是修改Composer的全局配置(推荐的方式):
composer config -g repo.packagist composer https://packagist.phpcomposer.com
二 是修改单个项目的配置:
composer config repo.packagist composer https://packagist.phpcomposer.com
上述命令将会在当前项目中的 composer.json 文件的末尾自动添加镜像的配置信息( 你也可以自己手工添加):
"repositories": {
"packagist": {
"type": "composer",
"url": "https://packagist.phpcomposer.com"
}
}
1.4、composer 基本命令
composer self-update/selfupdate:更新 composer 版本
composer install:根据当前目录下的 composer.json 文件来安装依赖代码库
composer update:更新依赖 代码库
composer create-project:创建项目
composer init:交互方式在当前目录下创建 composer.json 文件
① composer install
从当前目录读取 composer.json 文件,处理了依赖 关系,并把其安装到 vendor 目录下。如果当前目录下存在 composer.lock 文件,它会从此文件读取依赖版本,而不是根据 composer.json 文件去获取依赖。这确保了该 库的每个使用者都能得到相同的依赖版本。如果没有 composer.lock 文件,composer 将在处理完依赖关系后创建它。
② composer update
更新所有依赖 composer update、更 新指定的包 composer update monolog/monolog
更新指定的多个包 composer update monolog/monolog symfony/dependency-injection
还可以通过通配符匹配包 composer update monolog/monolog symfony/*
二、composer 进阶
2.1、自动加载
①、composer 根据声明的依赖关系,从相关库的源下载代码文件。
②、并根据依赖关系,在 Composer 目录下生成供类自动加载的 PHP 脚本。
③、使用的时候,项目开始处引入 “/vendor/autoload.php” 文件,就可以直接实例化这些第三方类库中的类了。
以monolog/monolog为例:
$log = new Monolog\Logger('name');
$log->pushHandler(new Monolog\Handler\StreamHandler('app.log', Monolog\Logger::WARNING));
$log->addWarning('Foo');
我们不用关心库文件的加载问题,composer 的 autoload.php 文件已经帮我们处理好了各个库的自动加载。
2.2、深入理解composer的autoload自动加载原理
①、进入 autoload.php
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInite2400fa8562d5b5529be6cfd7cb17ca0::getLoader();
②、进入 getLoader() 方法
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInite2400fa8562d5b5529be6cfd7cb17ca0', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInite2400fa8562d5b5529be6cfd7cb17ca0', 'loadClassLoader'));
$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\ComposerStaticInite2400fa8562d5b5529be6cfd7cb17ca0::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);
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInite2400fa8562d5b5529be6cfd7cb17ca0::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequiree2400fa8562d5b5529be6cfd7cb17ca0($fileIdentifier, $file);
}
return $loader;
}
可以明显看到,他将autoload_namespaces.php、autoload_psr4.php、autoload_classmap.php、autoload_files.php等几个配置文件包含了进来,并进行了相关处理(setPsr4),最后注册(register)。
③、那么我们跟进register方法
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
这函数就一行,但简单明了,直接调用php自带的spl_autoload_register函数,注册处理__autoload的方法,也就是loadClass方法。
④、再跟进loadClass方法
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
从函数名字就可以大概知道流程:如果存在$class对应的这个$file,则include进来。
⑤、那么进findFile方法里看看吧
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;
}
⑥、通过类名找文件,最终锁定在findFileWithExtension方法中。
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath.'\\';
if (isset($this->prefixDirsPsr4[$search])) {
foreach ($this->prefixDirsPsr4[$search] as $dir) {
$length = $this->prefixLengthsPsr4[$first][$search];
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;
}
return false;
}
最终实现将命名空间\类这样的类名,给转换成目录名/类名.php这样的路径,并返回完整路径。
2.3、阐述 composer 的自动加载机制
①、composer 根据声明的依赖关系,从相关库的源下载代码文件。
②、并根据依赖关系,在 Composer 目录下生成供类自动加载的 PHP 脚本。
③、使用的时候,项目开始处引入 “/vendor/autoload.php” 文件,就可以直接实例化这些第三方类库中的类了。
2.4、版本稳定性
如果你没 有显式的指定版本的稳定性,composer会根据使用的操作符,默认在内部指定为 -dev 或者 -stable。例如:
如果你想指定版本只要稳定版 本,你可以在版本后面添加后缀 -stable
minimum-stability 配置项定义了包在选择版本时对稳定性的选择的默认行为。默认是 stable。
它的值如下(按照稳定性排序)dev,alpha,beta,RC 和 stable。
除 了修改这个配置去修改这个默认行为,我们还可以通过稳定性标识(例如 @stable 和 @dev)来安装一个相比于默认配置不同稳定性的版本。例如:
{
"require": {
"monolog/monolog": "1.0.*@beta",
"acme/foo": "@dev"
}
}
2.5、其它命令
① remove命令:移除一个包及其依赖(在依赖没有被其他包使用的情况下)
composer remove monolog/monolog
② search 命令:composer search monolog
如果只是想 匹配名称可以使用 --only-name 选项 composer search --only-name monolog
③ show命令:列出项目目前所安装的包的信息
列出所有已经安装的包 composer show、可以通过通配符进行筛选 composer show monolog/*、
显示具体某个包的信息 composer show monolog/monolog
2.6、版本约束
我们可以指 定要下载的包的版本。例如我们想要下载版本 1.19 的 monolog 。我们可以通过 composer.json 文件:
{
"require": {
"monolog/monolog": "1.19"
}
}
然后运行 install 命令,或者通过 require 命令达到目的:composer require monolog/monolog:1.19 或者 composer require monolog/monolog=1.19 或者 composer require monolog/monolog 1.19
① 精确版本:可以指定具体的版本,告诉Composer只能安装这个版本。但是如果其他的依赖需要用到其他的版本,则包的安装或者更新最后会失败并终止。 例子:1.0.2
② 范围:使用比较操作 符你可以指定包的范围。这些操作符包括:>,>=,<,<=,!=。你可以定义多个范围,使用空格 或者逗号:表示逻辑上的与,使用双竖线||:表示逻辑上的或。需要注意的是,使用没有边界的范围有可 能会导致安装不可预知的版本,并破坏向下的兼容性。建议使用折音号操作符。例子:>=1.0 、>=1.0 <2.0 、>=1.0 <1.1 || >=1.2
③ 范围(使用连字符):带连字符的范围表明了包含 的版本范围,意味着肯定是有边界的。其中连字符的左边表明了 >= 的版本,而连字符的右边情况则稍微有点复杂。如果右边的版本不是完整的版本号,则会被使用通配符进行补全。例如 1.0 - 2.0 等同于 >=1.0.0 <2.1(2.0相当于2.0.*),而 1.0.0 - 2.1.0 则等同于 >=1.0.0 <=2.1.0。
④ 通配符:可以使用通配符去定义版本。1.0.* 相当于 >=1.0 <1.1。
⑤ 波浪号~:~1.2 相当于 >=1.2 <2.0.0,而 ~1.2.3 相当于 >=1.2.3 <1.3.0。对于使用 Semantic Versioning 作为版本号标准的项目来说,这种版本 约束方式很实用。例如 ~1.2 定义了最小的小版本号,然后你可以升级 2.0 以下的任何版本而不会出问题,因为按照 Semantic Versioning 的版本定义,小版本的升级不应该有兼容性的问题。简单来说,~定义了最 小的版本,并且允许版本的最后一位版本号进行升级。
需要注意的是,如果 ~ 作用在主版本号上,例如 ~1,按照上面的说法,composer 可以安装版本 1 以后的主版本,但是事实上是 ~1 会被当作 ~1.0 对 待,只能增加小版本,不能增加主版本。
⑥ 折音号^:该操作符的行为跟 Semantic Versioning 有比较大的关联,它允许升级版本到安全的版本。例如,^1.2.3 相当于>=1.2.3 <2.0.0,因为在 2.0 版本前的版本应该都没有兼容性的问题。而对于 1.0 之前的版本,这种约束方式也考虑到了安全问题,例如 ^0.3 会被当作 >=0.3.0 <0.4.0 对待。
三、composer 问题集锦
3.1、错误一
Warning: This development build of composer is over 30 days old. It is recommended to update it by running "C:\ProgramData\ComposerSetup\bin\composer.phar self-update" to get the latest version.
解决方法:composer selfupdate
3.2、错误二
You are running composer with xdebug enabled. This has a major impact on runtime performance. See https://getcomposer.org/xdebug
解决方法:`composer selfupdate`
3.3、错误三
Fatal error: Call to undefined method Fxp\Composer\AssetPlugin\Package\Version\VersionParser::parseLinks() in C:\Documents and Settings\Administrator\Application Data\Composer\vendor\fxp\composer-asset-plugin\Repository\VcsPackageFilter.php on line 272
解决方法:删除composer资源插件,再重新安装
删除——rm -r %APPDATA%\Composer\vendor\fxp
安装——composer global require "fxp/composer-asset-plugin:~1.0.3"
3.4、错误四
The "yiisoft/yii2-composer" plugin requires composer-plugin-api 1.0.0, this *WIL L* break in the future and it should be fixed ASAP (require ^1.0 for example). The "fxp/composer-asset-plugin" plugin requires composer-plugin-api 1.0.0, this*WILL* break in the future and it should be fixed ASAP (require ^1.0 for example ).
解决办法:Yii2 需要 composer-plugin-api 1.0 以上的版本,运行以下命令:
composer global require "fxp/composer-asset-plugin:~1.1.1"
3.5、报 [ErrorException] zlib_decode(): data error 错
解决办法:执行 composer self-update 即可
3.6、在执行迁移或者数据填充时发生「class not found」错误
解决办法:试着先执行 composer dump-autoload 命令后再进行一次。
3.7、提示以下类似的错误时
Problem 1
- The requested package graham-campbell/credentials ~1.0 is satisfiable by g
raham-campbell/credentials[1.0.x-dev] but these conflict with your requirements
or minimum-stability.
解决方法:在composer.json中添加以下(如果存在则修改)
"minimum-stability": "dev",
3.8、如果不需要使用https
,可以这么写,以解决有时候因为 https 造成的问题:
composer config -g secure-http false
3.9、composer update 或者 composer install提示killed解决办法
出现此原因大多因为缓存不足造成,在linux环境可增加缓存解决。
free -m
mkdir -p /var/_swap_
cd /var/_swap_
#Here, 1M * 2000 ~= 2GB of swap memory
dd if=/dev/zero of=swapfile bs=1M count=2000
mkswap swapfile
swapon swapfile
echo “/var/_swap_/swapfile none swap sw 0 0” >> /etc/fstab
#cat /proc/meminfo
free -m
四、composer 的自动加载方式(psr-0、psr-4、class-map、files)
4.1、composer 的五套 PHP 非官方规范
-
PSR-0 (Autoloading Standard) 自动加载标准
-
PSR-1 (Basic Coding Standard) 基础编码标准
-
PSR-2 (Coding Style Guide) 编码风格向导
-
PSR-3 (Logger Interface) 日志接口
-
PSR-4 (Improved Autoloading) 自动加载优化标准
4.2、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 文件之中。
②、PSR-0方式
{
"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 文件中(前提是目录和文件已经存在,否则 composer 在扫描时会报错)
{
"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 直接进行加载。