PHP composer

2016-11-18 鲁鲁槟 2

一、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 直接进行加载。

暂时还没有评论,快来抢沙发吧~

发表评论

您需要登录后才可以评论。登录 | 立即注册
阅读 1140 2