标签归档:composer

Composer中replace属性的作用

原始解释:
“Lists packages that are replaced by this package. This allows you to fork a package, publish it under a different name with its own version numbers, while packages requiring the original package continue to work with your fork because it replaces the original package.”

列出被当前包替换的包。这允许你fork一个包,并以不同的名称和它自己的版本号进行发布,由于替换了原始的包,当包依赖原始包时可以使用fork包继续工作。

实际上,第一句话已经解释清楚了,就是当前包替换哪个(或哪些)包。比如:

{
“name": "b/b",
"replace": {
    "a/a": "x.y.z"
}
}

就是b/b这个包替换了a/a这个对应了x.y.z版本的包。如果b/b包中的replace列出了多个包,那么它将替换多个包。在Composer解决依赖的过程中,如果遇到了replace,那么原始的包会被移除,所以它的应用场景非常清晰,就是原始包停止维护了,但是你的项目或包直接或间接依赖一个原始的包,为了可以修复原始包的bug,你可以fork这个包,并在这个包中声明要替换原始包,然后在你的应用或包中依赖这个你fork的包,这样就可以全局替换原始包(原始包被移除)。

由于是包替换,很明显也可能引入安全问题,比如某个包被替换,替换的包中包含恶意代码。

关于replace的第二段解释:
”This is also useful for packages that contain sub-packages, for example the main symfony/symfony package contains all the Symfony Components which are also available as individual packages. If you require the main package it will automatically fulfill any requirement of one of the individual components, since it replaces them.“

这里描述的就是现代框架的组织方式。一个框架(或一个大组件),是很多子包组成的,每个子包都可以单独使用,通常每个子包都会以只读的方式fork到另一个包名,当单独使用时,可以依赖这个只读的包,当依赖整个框架时,框架中声明了替换,则可以移除单独依赖时下载的包。

比如laravel/framework是由很多包组成的,每个包都以只读的方式fork到另一个包名称:

// https://github.com/laravel/framework
"replace": {
        "illuminate/auth": "self.version",
        "illuminate/broadcasting": "self.version",
        "illuminate/bus": "self.version",
        "illuminate/cache": "self.version",
        "illuminate/config": "self.version",
        "illuminate/console": "self.version",
        "illuminate/container": "self.version"
}

这个包包括了子包illuminate/container,而它被只读方式fork了一份到https://github.com/illuminate/container,所以当要仅用这个组件时,可以composer require illuminate/container即可。在依赖了illuminate/container后,如何由依赖laravel/framework,这个时候会引起命名冲突,但是laravel/framework中声明了illuminate/container这个包被laravel/framework替换,所以在依赖laravel/framework后,illuminate/container独立的下载将被移除,从而解决了名称冲突。

第一个用法,提供了一个全局包替换的机会。第二个用法,提供了一个现代化的框架组织方式。

向Composer发布代码

Composer可以很方便管理应用的包与包依赖,自定义的包也可以发布到到packagist.org,然后通过Composer拉回来,已达到快速部署。当然,如果自定义的包非开源的,就不要这么干了。

首先,在github.com上创建账户,并把项目推送到github.com,这样会得到一个链接。
然后到packagist.org,点击使用Github账户登录,接着点击submit,这时候会要求输入github.com项目地址,接下来就是检查,这个检查会通知你当前可能重复的其它包,然后输入你包的名称(会从composer.json中自动检出),然后确认,这样包就发布出去了。

由于代码托管在github上,当向github推送代码时,packagist.org默认并不会同步更新,所以为了让packagist.org可以同步,需要在github中设置,参考:https://packagist.org/about#how-to-update-packages

To do so you can:

Go to your GitHub repository
Click the “Settings” button
Click “Integrations & services”
Add a “Packagist” service, and configure it with your API token, plus your Packagist username
Check the “Active” box and submit the form
You can then hit the “Test Service” button to trigger it and check if Packagist removes the warning about the package not being auto-updated.

简单来说这是一个事件通知服务,在github中设置一个钩子,当由事件触发时,通过packagist提供的API通知packagist,packagist就会去和github做同步操作。

接下来就是运行:

composer require ifeeline/wkhtmltox:dev-master

如果要拉开发分支,在包后加冒号接dev-master即可。

以下是一个例子:
ifeeline_whhtmltox

Composer包依赖管理器快速参考

基本用法:
http://docs.phpcomposer.com/01-basic-usage.html#Basic-usage

一 建立composer.json

{
    "require": {
        "monolog/monolog": "1.0.*"
    }
}

指令”require”表示依赖的包,是一个列表,每个元素是包名对应版本号,报名分两部分,分别是供应商 和 包名称,然后是对应的版本号。版本号的声明可以用使用一般的比较运算符,比如>=1.0,更加常见的是~1.2,表示最低依赖1.2,从1.2到2.0(不包括2.0)。

二 然后执行install

php composer.phar install

安装完成之后(检测composer.json,下载所有依赖的包),就会产生composer.lock文件,意味着当前的项目使用的依赖被锁定,因为如果composer.lock存在,那么在install时会先被检测到,那么composer.json将被跳过而使用composer.lock里面锁定的依赖(更新 或 建立vendor目录,下载包)。

如果需要更新所有依赖,可以运行update(而不是install),这样就会检测composer.json,下载新依赖,重新产生composer.lock文件。如果只要更新一个或几个包,可以php composer.phar update monolog/monolog […],这样也会更新最终的composer.lock文件。

三 自动装载
先依赖包安装之后,就需要使用它:

#只需要保护这个文件
require 'vendor/autoload.php';

它实际返回一个loader,预先扫描vendor/composer/中的autoload_classmap.php、autoload_namespaces.php和autoload_psr4.php,先看刚才的install做了什么工作:

<?php

// autoload_namespaces.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'Monolog' => array($vendorDir . '/monolog/monolog/src'),
);

把依赖的包添加到了这里,那么这个自动loader就到这里来寻找对应的库。意思就是install(或update)时,这里的文件会被更新。

那么如果要自动load自己的类库,编辑这里的文件也可以实现。不过一般情况都不这么干。可以往composer.json里面添加:

"autoload": {
        "classmap": [
            "database"
        ],
        "psr-4": {
            "eBay\\": "app/"
        }
}

然后运行php composer.phar update即可,注意classmap的方式对应的是目录,它扫描目录里面的所有文件,找出类名(支持命名空间),然后对应其路径:

return array(
    'Craw' => $baseDir . '/database/Craw.php',
    'Test' => $baseDir . '/database/test/tt.php',
    'Vfeelit\\Need' => $baseDir . '/database/test/ab.php',
);

这个方法对于引入非规范的类,可以说是一网打尽,美中不足的是我可能不需要扫描整个目录。

看看psr-4文件:

<?php
// autoload_psr4.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'eBay\\' => array($baseDir . '/app'),
);

注:这里的app目录不需要一定存在,因为它不会扫描目录。

引入类库的方式可以直接使用方法的loader实例:

$loader = require 'vendor/autoload.php';
$loader->add('Acme\\Test\\', __DIR__);

四 关于下载包的来源
众所周知的原因,composer下载很慢,可以使用国内镜像(在composer.josn中添加):

"repositories": [
        {"type": "composer", "url": "http://packagist.phpcomposer.com"},
        {"packagist": false}
    ]

五 项目与包
项目叫project,包叫package。只要你有一个 composer.json 文件在目录中,那么整个目录就是一个包。当你添加一个 require 到项目中,你就是在创建一个依赖于其它库的包。你的项目和库之间唯一的区别是,你的项目是一个没有名字的包。为了使它成为一个可安装的包,你需要给它一个名称。你可以通过 composer.json 中的 name 来定义:

{
    "name": "acme/hello-world",
    "require": {
        "monolog/monolog": "1.0.*"
    }
}

在这种情况下项目的名称为 acme/hello-world,其中 acme 是供应商的名称。供应商的名称是必须填写的。

六 平台软件包
Composer 将那些已经安装在系统上,但并不是由 Composer 安装的包视为一个虚拟的平台软件包。这包括PHP本身,PHP扩展和一些系统库。(简单参考一下即可):

"require": {
        "php": ">=5.5.9",  		///这里
        "laravel/framework": "5.1.*",
        "league/flysystem-aws-s3-v3": "~1.0",
        "maatwebsite/excel": "~2.0.0",
        "chumper/zipper": "0.6.x"
    },

七 版本 标签 分支
http://docs.phpcomposer.com/02-libraries.html

八 发布到 packagist

九 命令行
http://docs.phpcomposer.com/03-cli.html,这部分内容中的global不好理解。大概是提供了已给命令行工具。其它都不难理解。

十 脚本
http://docs.phpcomposer.com/articles/scripts.html,这部分内容是在运行composer时触发的事件。

最后,composer.josn完整的参考http://docs.phpcomposer.com/04-schema.html。

PHP包依赖管理工具Composer

Composer是一个管理PHP包依赖关系的工具。可以使用Composer方便地管理项目中一些第三方库和自己的库。官方地址:https://getcomposer.org。(国内镜像:http://pkg.phpcomposer.com/,具体配置参考这里的说明)

安装:(PHP必须是先安装的,并且它的路径必须在PATH中)

//方式1 不需要先进入目录(通过install-dir指定)
$curl -sS https://getcomposer.org/installer | php --install-dir=bin

//方式2 没有curl时
php -r "readfile('https://getcomposer.org/installer');" | php --install-dir=bin

稍做解释,https://getcomposer.org/installer实际是一个PHP脚本,通过管道让PHP来执行这个脚本,所以PHP是必须先安装的。这个脚本执行后就会在指定目录下生成一个叫composer.phar的文件,这个文件就是composer的主程序。

——文件composer.phar—————————————-
查看这个脚本开始部分:

#!/usr/bin/env php
......

我们平时在写一个shell脚本时,应该是:

#!/usr/bin/sh
......
##或者
#!/usr/bin/bash
......

意思是说,使用bash来解释当前这个脚本。那么env php应该就是要用php来解释当前这个脚本,为何要这个写法?实际上,PHP安装到系统上时,由于系统的差异,PHP这个解释器可能安装到了不同的目录,通过env php这种用法就可以去搜索PATH然后获取这个PHP解释器。那么总结来说就是可以直接执行composer.phar(调用PHP来解释这段脚本,所以PHP是必须安装并且可以找到的)。

可以把这个composer.phar移动到某个PATH目录下(比如/usr/bin/),这样就可以随意运行了(意思就是实际composer.phar放在什么地方并不重要,只要在输入命令是能找到对应命令即可):

mv composer.phar /usr/bin/composer

——文件composer.phar—————————————-

——Windows下安装——————————————–
#########################################
Windows下的安装包:
https://getcomposer.org/Composer-Setup.exe

安装过程会提示是否使用代理(http_proxy,注:只能是http代理),依赖PHP,PHP还需要启用相关的扩展(php_openssl等),安装过程会下载composer.phar,如果没有设置代理,可能非常缓慢。按照完成后会修改环境变量:
全局环境变量path:
C:\ProgramData\ComposerSetup\bin
这个路径下的composer是针对*inux的脚本,composer.bat是针对Windows下的脚本,它们都是composer.phar的包装器。Windows下实际总是使用composer.bat脚本(输入时可以省略后缀名)

用户环境变量path:
C:\Users\Administrator\AppData\Roaming\Composer\vendor\bin
这个路径用来寻找到全局安装的二进制命令(或者是脚本),上上级目录(Composer)也是全局全局项目的位置,所有全局的依赖都会写入到Composer/composer.json文件。

更换镜像地址:
composer config repo.packagist composer https://packagist.phpcomposer.com
以上命令把镜像更换为中国大陆的地址,会在C:\Users\Administrator\AppData\Roaming\Composer中放入auth.json、cacert.pem、config.json、keys.dev.pub、keys.tags.pub文件,这些文件都是针对全局配置而引入的,关键是config.json文件,这个文件最终会和具体项目的composer.json合并(从而实现更换镜像地址)。

另外:
Composer会缓存安装包,在Windows下,安装包缓存的位置为:C:\Users\Administrator\AppData\Local\Composer\files
Linux下,composer相关的配置缓存,都在用户目录的.config/composer目录中。
#########################################

–Windows下Composer提供了一个安装包(https://getcomposer.org/Composer-Setup.exe),这个玩意安装完成后
composer-win
玛尼,这个是啥玩意,一个卸载程序,一个安装程序而已(最新安装版本,不会再拷贝安装文件)。实际使用的东西在:
composer-setup
路径:C:\ProgramData\ComposerSetup\bin已经自动注册到了系统的PATH中,意味着可以直接运行composer.bat(composer是SHELL脚本),它实际是composer.phar的包装器,这样就可以在命令行中运行composer命令(之所以需要包装器,那是因为#!/usr/bin/env php指令在Windows下无效)。

Windows下的安装还会注册一个用户级别的PATH环境变量,指向到C:\Users\Administrator\AppData\Roaming\Composer\vendor\bin,实际上使用Composer全局安装时,文件会安装到C:\Users\Administrator\AppData\Roaming\Composer路径,其中的vendor\bin就是全局安装包提供的命令行工具。

实际上,也可以直接去下载composer.phar放入到根目录下,然后定位到这个目录,运行php composer.phar ***, 不过由于Windows下有些特殊,所以还是使用Windows下安装方法为好。
——Windows下安装——————————————–

任何一个项目下,都可以放置一个config.json的文件,它会会composer.json合并,而且对应config字段,对于全局安装可以在C:\Users\Administrator\AppData\Roaming\Composer中放入config.json(Linux: ~/.config/composer/config.json):

{
    "config": {
        "preferred-install": "dist",
        "secure-http": false
    },
    "repositories": [
        {"type": "composer", "url": "http://packagist.phpcomposer.com"},
        {"packagist": false}
    ]
}

修改为首先从本地缓存取数据,然后把仓库改为中国镜像(http://packagist.phpcomposer.com)。本地缓存在Windows下存放在C:\Users\Administrator\AppData\Local\Composer中。

以上配置是通过把仓库指定到国内镜像,否则下载可能非常缓慢。如果国内镜像不可用,或者希望直接使用原始仓库,可以设置HTTP_PROXY环境变量,让其走代理:
http_proxy
也可以命令行下运行:SET HTTP_PROXY=http://127.0.0.1:8087

composer.phar是composer的主程序,还有一个文件是用来存储依赖关系的文件,一般在项目根下建立一个叫composer.json的文件,里面说明需要使用的库:

{
    "require": {
        "monolog/monolog": "1.0.*"
    }
}

然后开始安装:

[root@vfeelit]# ./composer.phar install
Loading composer repositories with package information
Installing dependencies (including require-dev)
  - Installing monolog/monolog (1.0.2)
    Downloading: 100%         

Writing lock file
Generating autoload files

这样在当前目录下,就多了一个叫verdor的文件夹,依赖关系库文件也下载在其中。

composer.json文件的基本设置:
require关键字
require关键字来告诉composer哪些包是你项目所需要的

{
    "require": {
        "monolog/monolog": "1.0.*"
    }
}

如上所示,require的对象将会映射包的名称( monolog/monolog)和包的版本是1.0.*。

基本上包的命名是 主名/项目名称(monolog/monolog),主名必须唯一,但是项目也就是我们的包的名称可以有相同的(这个不需要解释)。

接下来是包的版本,比如使用monolog的版本是1.0.*,意思是只要版本是1.0分支即可,例如1.0.0,1.0.2等。

版本定义的方式:
1)标准的版本:定义保准的版本包文件,如:1.0.2
2)一定范围的版本:使用比较符号来定义有效的版本的范围,有效的符号有>, >=, <, <=, !=
3)通配符:特别的匹配符号*,例如1.0.*就相当于>=1.0,<1.1版本的即可
4)下一个重要的版本:~符号最好的解释就是,~1.2就相当于>1.2,<2.0,但~1.2.3就相当于>=1.2.3,<1.3版本。

—————————————————————
composer.lock文件
设置好composer.json后,运行composer.phar install就会下载对应的依赖包,之后会生成一个叫composer.lock的文件。

注意,在使用install命令时,它首先会判断composer.lock文件是否存在,如果存在,将会下载相对应的版本(忽略composer.json里面的配置),这意味着任何下载项目的人都将会得到一样的版本。如果不存在composer.lock,composer将会通过composer.json来读取需要的包和相对的版本,然后创建composer.lock文件。这样,如果依赖包有新的版本后,就不会被更新到新的版本,可以使用update命令来完成更新,运行了update后将获取到最新的版本同时composer.locak文件也被更新。

php composer.phar update

Packagist仓库
在运行composer.phar install时,对应的依赖包会被下载,那么从哪里下载,这些包存储在什么地方?答案是在https://packagist.org。Packagist是composer的主要仓库,composer仓库包含了包的源码,Packagist的目的建成一个任何人都可以使用的仓库。

在运行composer.phar install下载了对应的依赖包后,在vendor下会生成autoload.php文件,只要:

require 'vendor/autoload.php';

就可以使用依赖包了。

以下以如何使用ZF2类库进行稍微深入的分析:

// 文件vendor/autoload.php
<?php

// autoload.php @generated by Composer

require_once __DIR__ . '/composer' . '/autoload_real.php';

return ComposerAutoloaderInit3c4646160e3ed938b9236365912a65f4::getLoader();

这个哈希码是composer的版本号,这个vendor/composer/autoload_real.php实际就定义了ComposerAutoloaderInit3c4646160e3ed938b9236365912a65f4类:

<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInit3c4646160e3ed938b9236365912a65f4
{
    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;
        }
	//把loadClassLoader方法注册  专门用来加载Composer\Autoload\ClassLoader类
	//注意,最后一个参数表示把这个函数放到堆栈的最前面
	//http://blog.ifeeline.com/169.html
        spl_autoload_register(array('ComposerAutoloaderInit3c4646160e3ed938b9236365912a65f4', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
	//销毁 只需要用一次  现在获得了Composer\Autoload\ClassLoader实例
        spl_autoload_unregister(array('ComposerAutoloaderInit3c4646160e3ed938b9236365912a65f4', 'loadClassLoader'));

        $vendorDir = dirname(__DIR__);	//__DIR__为vendor/composer  那么目录就是vendor
        $baseDir = dirname($vendorDir);	//就是项目根目录

	//autoload_namespaces.php定义了命名空间对应的路径,文件内容:
	//return array(
	//    'Zend\\' => array($vendorDir . '/zendframework/zendframework/library'),
	//    'ZendXml' => array($vendorDir . '/zendframework/zendxml/library'),
	//);
        $map = require __DIR__ . '/autoload_namespaces.php';
        foreach ($map as $namespace => $path) {
            $loader->set($namespace, $path);
        }

	//使用psr4方式
	//return array(
	//);
        $map = require __DIR__ . '/autoload_psr4.php';
        foreach ($map as $namespace => $path) {
            $loader->setPsr4($namespace, $path);
        }

	//classmap 映射类
        $classMap = require __DIR__ . '/autoload_classmap.php';
        if ($classMap) {
            $loader->addClassMap($classMap);
        }
	//public function register($prepend = false)
	//{
	//    spl_autoload_register(array($this, 'loadClass'), true, $prepend);
	//}
	//最终回到$loader对象的loadClass加载对象 这个逻辑不再深入
        $loader->register(true);
	//这个方法最终返回$loader
        return $loader;
    }
}

function composerRequire3c4646160e3ed938b9236365912a65f4($file)
{
    require $file;
}

从以上分析可知:

require 'vendor/autoload.php';

这个require最终返回$load对象,从分析可以知道,在加载类的时候,使用到了$loader->set($namespace, $path),而这个对应关系来自autoload_namespaces.php文件,这个文件的对应关系又是通过运行composer update时通过composer.json的配置来写入的(composer还负责下载对应的库文件)。如果仅仅希望使用Composer的类自动装载功能,可以如下使用:

//比如lib下有一个叫Vfeelit的包,如果要自动装载,只要这样干
$loader->set('Vfeelit','xxx/lib');    // Vfeelit_Test
$loader->set('Vfeelit_','xxx/lib');   // Vfeelit_Test
$loader->set('Vfeelit\\','xxx/lib');  // Vfeelit\Test

注意:一些自定义的库,可以通过放入一个配置文件中(格式参考autoload_namespaces.php),然后在代码中添加类似如下的代码:

        $map = require __DIR__ . '/autoload_namespaces.php';
        foreach ($map as $namespace => $path) {
            $loader->set($namespace, $path);
        }

$loader对象还提供了add()和addPrs4()方法(明显add()就是指prs0),它跟set()和setPrs4()是不一样的。具体来说,set是设置一个命名空间对应一个路径,对应命名空间,通过set只能对应一个目录,但是通过add就可以设置一个命名空间对应多个路径(A路径搜索不到就搜索B路径,看起来意义不大,有意义的是针对空命名空间时,空命名空间搜索多个目录就像require搜索include_path一样)。另外,set()和add()方法的第一参数是命名空间字符串,实际上它是可以为空的,就相当于空命名空间对应的搜索路径。

$loader->add('',$library);

$o = new Vfeelit\Test();
$o = new Vfeelit_TTest();

另外,为了在没有找到类时,去搜索PHP的include_path,可以设置$loader->setUseIncludePath(true),默认是不去搜索include_path的(一般不再使用这个方法,一些旧的类库大量使用了require时,比如zf1.x,就必须设置include_path)。例子:

<?php
$rootPath = dirname(__DIR__);
$library = $rootPath."/library";

///
$includePath = array(
	$library,
	get_include_path()
);
set_include_path(implode(PATH_SEPARATOR, $includePath));

///
chdir($rootPath);
if(file_exists('vendor/autoload.php')){
	$loader = include '/vendor/autoload.php';
}else{
	exit("Autoload Failed. ");
}

///
$loader->setUseIncludePath(true);

///
$loader->add("Vfeelit\\",$library);
$loader->add("Vfeelit",array($library)); // 可以指定数组,表示对应多个路径

///
$test = new \Vfeelit\Test();
$test = new Vfeelit_TTest();

这样,library下的库就会自动被装载(不需要去设置对应关系)。类的自动装载的顺序是先具体后一般,具体来说就是如果找到类的映射,就直接使用,如果找不到就寻找是否有命名空间的映射,再找不到就去搜索include_path(如果启用了)。

一般,我们使用composer主要用来管理库,准确来说是对库的版本更新进行管理,库类的自动装载是其组成部分,所以应该重复利用它对库的强大管理能力,当然,对应一些不在composer仓库中的自定义库,也是可以使用composer提供的自动装载功能的。一般,如果要更新类库(比如有了新版本),只要修改composer.json,然后运行composer.phar update即可更新。

永久链接:http://blog.ifeeline.com/1075.html
原创文章,转载务必保留出处。