2.6. 扩展包源仓库
概念
在我们了解已存在的不同类型的仓库之前,我们需要了解 Composer 构建的一些基本概念。
包
Composer 是一个依赖管理器。 它能够将包安装在本地。一个包的本质是一个包括了一系列源文件的目录。一般的,一个包中的文件主要是一堆 PHP 代码文件,但理论上一个包里面可以包括任意类型的文件,其中一个文件就是包中用于描述包的名称和版本信息的文件。名称和版本是包的唯一标识。
事实上,Composer 的内部认为每一个版本都是独立的包,虽然对于使用 Composer 管理依赖时这并不重要,但对于你想要改变包中的一些东西时或做一些自定义选项时,这些内容则十分重要。
此外,除了名称和版本信息,还有一些其他用用的元数据。其中与安装包时最紧密相关的信息就是包来源的定义,这个是用于告知从何处去取得包的实际内容。来源分两种:dist 和 source。
Dist: dist 来源的包是一个打包后的分发版。通常是一个已发布的稳定版本。
Source: source 来源的包往往用于开发。通常是一个源代码仓库,例如 git。当你想要修改下载的包,你可以选择使用该来源。
包可以提供任意一种来源选项,或者二者皆有。最终选定哪一个取决于某一些因素,例如用户提供的指定选项、包的稳定性标识。
仓库
仓库是包的来源。它提供了一个包和版本的列表。Composer 通过查阅你配置中定义的所有仓库源里去寻找你项目中所需要的包并将其引入。
Composer 将 Packagist 作为全局默认查找的仓库。当然你也可以通过在你的项目配置文件 —— composer.json
中添加额外的仓库源配置。
所有对于仓库源的配置信息仅在源包( root package )中有效,而该包中所依赖的包内对于仓库源的配置信息则不会再被 Composer 作为查找包的目标仓库的依据。你可以阅读 FAQ entry 了解更多具体原因。
仓库类型
Composer
最主要、也是最常见的仓库类型就是 composer
仓库。这种类型的仓库使用 packages.json
文件来记录这个仓库中所有包的元数据。
这也是 Packagist 所使用的仓库类型。当 Composer 引入这种类型的仓库的包时,首先需要找到这个仓库提供的 packages.json
文件。我们以 Packagist 为例,该文件位于站点根路径的 /packages.json
,所以仓库的地址就是 repo.packagist.org
。再例如 example.org/packages.json
是某仓库的 package.json
文件的实际获取地址,则该仓库源地址就是 example.org
。
packages 字段
唯一必需的字段是packages
。它的 JSON 结构如下:
{
"packages": {
"vendor/package-name": {
"dev-master": { @composer.json },
"1.0.x-dev": { @composer.json },
"0.0.1": { @composer.json },
"1.0.0": { @composer.json }
}
}
}
@composer.json
标记是包的指定版本里composer.json
的内容,其内至少包含以下信息:
- 包名
name
- 版本
version
- 来源
dist
或source
这是一个最简单的包定义:
{
"name": "smarty/smarty",
"version": "3.1.7",
"dist": {
"url": "https://www.smarty.net/files/Smarty-3.1.7.zip",
"type": "zip"
}
}
它还可以包含任何在 composer.json 完全解析 中描述的字段。
notify-batch 字段
notify-batch
字段允许指定 URL,它会在用户每安装一个包时被调用。这个 URL 可以是一个绝对路径(将使用与源仓库相同的域名),或者是一个完整的 URL 地址。
一个示例的值:
{
"notify-batch": "/downloads/"
}
例如example.org/packages.json
里包含monolog/monolog
包,这将会发起一个POST
请求到example.org/downloads/
,并带有 JSON 格式的请求体(request body):
{
"downloads": [
{"name": "monolog/monolog", "version": "1.2.1.0"}
]
}
version
字段包含了标准化的版本号。
该字段是可选的。
provider-includes 和 providers-url 字段
provider-includes
字段允许你设置一个列表,它包含了该仓库提供的所有包名称。这种情况下哈希值应该是文件的 sha256。
providers-url
描述了怎样在服务器上查找 provider 文件,是一个相对源仓库根目录的绝对路径。必须包含%package%
和 %hash%
这两个占位符。
示例:
{
"provider-includes": {
"providers-a.json": {
"sha256": "f5b4bc0b354108ef08614e569c1ed01a2782e67641744864a74e788982886f4c"
},
"providers-b.json": {
"sha256": "b38372163fac0573053536f5b8ef11b86f804ea8b016d239e706191203f6efac"
}
},
"providers-url": "/p/%package%$%hash%.json"
}
这些文件包含包名称和哈希值,以此验证文件完整性,例如:
{
"providers": {
"acme/foo": {
"sha256": "38968de1305c2e17f4de33aea164515bc787c42c7e2d6e25948539a14268bb82"
},
"acme/bar": {
"sha256": "4dd24c930bd6e1103251306d6336ac813b563a220d9ca14f4743c032fb047233"
}
}
}
上面的文件声明了 acme/foo 和 acme/bar 可以在该源仓库中找到,通过加载由providers-url
引用的文件,替换%package%
为发行商命名空间中的包名,替换%hash%
为 sha256 字段值。这些文件均包含如上所述的 packages 字段定义。
这两个字段是可选的。你也许不需要它们来自定义存储库。
流选项
packages.json
文件是使用 PHP 流加载的。你可以使用options
参数设置额外的流选项。查看 上下文(Context)选项和参数 获取更多信息。
VCS
VCS 表示版本控制系统。这包括像 git、svn、fossil 和 hg 这样的版本控制系统。Composer 有一个仓库类型,可以从这些系统中安装软件包。
从 VCS 仓库加载一个包
这里有一些用例。最常见的情况是维护自己 Fork 的第三方库。如果你在项目中使用某个库并想对库做一些修改,你希望让你的项目使用这个修改版本。如果这个库是在 GitHub(这是最常见的)上,你可以直接 Fork 它然后推送更改到你的 Fork。然后更新项目的composer.json
文件。你所需做的就是添加这个 Fork 并更新版本约束指向你自己的分支。在composer.json
里,应该用"dev-"
作为你自己分支的名称前缀。关于版本约束命名规范,请查看 扩展包 获取更多信息。
举个例子,假设你 Fork 了一份 monolog,并在bugfix
分支修复了一个 bug:
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/igorw/monolog"
}
],
"require": {
"monolog/monolog": "dev-bugfix"
}
}
当运行php composer.phar update
时,你应该会获得自己的monolog/monolog
修改版本,而不是 packagist 上的。
注意,你不能重命名软件包,除非你打算脱离原来的包,长期使用 Fork 的。由于自定义仓库优先于 packagist,这样 Composer 就能正确地获取你的包了。如果你一定要重命名包,你应该在默认分支(通常是 master)进行操作,而不是某个特性分支,因为包的名称取自默认分支。
也请注意,如果你在自己 Fork 的仓库修改了composer.json
里的name
属性,覆盖操作将无法工作,因为这需要匹配原来的包让操作生效。
如果其他的包依赖于你的 Fork,可能你需要设置行内别名以匹配对应的版本约束。更多信息请查看 分支别名 。
使用私有源仓库
完全相同的解决方案允许您在 GitHub 和 BitBucket 上使用您的私有存储库:
{
"require": {
"vendor/my-private-repo": "dev-master"
},
"repositories": [
{
"type": "vcs",
"url": "git@bitbucket.org:vendor/my-private-repo.git"
}
]
}
唯一的要求是为 git 客户端安装 SSH 密钥。
Git 备选方案
Git 不是 VCS 存储库支持的唯一版本控制系统。支持以下内容:
- Git: git-scm.com
- Subversion: subversion.apache.org
- Mercurial: mercurial-scm.org
- Fossil: fossil-scm.org
要从这些系统获取软件包,您需要安装它们各自的客户端,这可能很不方便。因此,GitHub 和 BitBucket 支持使用这些站点提供的API,无需安装版本控制系统即可获取软件包。 VCS 存储库为它们提供了 dist
,它们将包作为拉链获取。
- GitHub: github.com (Git)
- BitBucket: bitbucket.org (Git and Mercurial)
Composer 将根据 URL 自动检测要使用的 VCS 驱动, 但是如果因为一些原因需要指定一个,可以使用 git-bitbucket
,hg-bitbucket
,github
,gitlab
,perforce
,fossil
,git
,svn
或 hg
作为存储库类型来替代 vcs
。
如果在 github 上将 no-api
设置为true
,它将像使用任何其他 git 存储库而不是使用 GitHub API 一样克隆存储库。但与直接使用 git
驱动程序不同,Composer仍会尝试使用 github 的zip文件。
请注意:
- 要让 Composer 选择使用哪个驱动程序,需要将存储库类型定义为 “vcs”
- 如果您已经使用了私有存储库,这意味着 Composer 应该已将其克隆到缓存中。如果你想用驱动程序安装相同的软件包,记得启动命令
composer clearcache
,然后使用命令composer update
来更新composer cache 并从 dist 安装软件包。
BitBucket 驱动配置
BitBucket 驱动使用 OAuth 来通过 BitBucket 的 REST API 获取你的私人仓库所以你需要创建一个 OAuth 用户来使用此驱动,详见 Atlassian 的文档。你需要填写回调地址来满足 BitBucket 的需求,但该地址不需要在任意情况下被访问并且不会被 Composer 使用。
在 BitBucket 的控制面板创建了一个 OAuth 用户之后,你需要在 auth.json 文件中像这样填写凭据 (详见 这里):
{
"bitbucket-oauth": {
"bitbucket.org": {
"consumer-key": "myKey",
"consumer-secret": "mySecret"
}
}
}
注意仓库地址要是 HTTPS 而不是 Git。
如果你不想在文件系统中存储 OAuth 凭据,你可以设置 bitbucket-oauth
到 COMPOSER_AUTH 环境变量中来替代。
SVN 设置
由于 SVN 没有原生分支以及标签的概念,Composer 默认代码在 $url/trunk
, $url/branches
和 $url/tags
里。如果你的仓库结构不同,你可以更改那些值。例如你使用首字母大写的名字,你可以像这样配置你的仓库:
{
"repositories": [
{
"type": "vcs",
"url": "http://svn.example.org/projectA/",
"trunk-path": "Trunk",
"branches-path": "Branches",
"tags-path": "Tags"
}
]
}
如果你没有分支或标签目录,你可以把 branches-path
或 tags-path
设置为 false
来完全禁用他们。
如果包在一个二级目录, 例如 /trunk/foo/bar/composer.json
和 /tags/1.0/foo/bar/composer.json
,这时你可以设置 "package-path"
选项为二级目录路径来让 Composer 获取它,在这个例子中是 "package-path": "foo/bar/"
。
如果你用的是私人 SVN 仓库,你可以将凭据保存在设置文件中的 http-basic 项中(详见 Schema):
{
"http-basic": {
"svn.example.org": {
"username": "username",
"password": "password"
}
}
}
如果你的 SVN 客户端设置为存储用户凭据,那该用户的凭据会默认存储,而已存在的凭据会被覆盖。通过在仓库中设置 "svn-cache-credentials"
来禁止该行为:
{
"repositories": [
{
"type": "vcs",
"url": "http://svn.example.org/projectA/",
"svn-cache-credentials": false
}
]
}
PEAR
使用 pear
仓库可以从任何 PEAR 频道安装扩展包。Composer 会以 pear-{channelName}/
前缀来命名所有的扩展包来避免冲突。所有扩展包也会用 pear-{channelAlias}/
前缀来做别名。
比如使用 pear2.php.net
:
{
"repositories": [
{
"type": "pear",
"url": "https://pear2.php.net"
}
],
"require": {
"pear-pear2.php.net/PEAR2_Text_Markdown": "*",
"pear-pear2/PEAR2_HTTP_Request": "*"
}
}
在这个例子中频道的简短命名是 pear2
,所以 PEAR2_HTTP_Request
包命名就变成了 pear-pear2/PEAR2_HTTP_Request
。
注意:
pear
仓库需要为每一个包做一些网络请求,所以这可能会使安装过程变慢。
自定义 vendor 别名
也可以用自定义 vendor 名称来为 PEAR 频道做别名。
例如:
设想下你有一个私人 PEAR 仓库,并且想使用 Composer 来从 VCS 中来包含一些依赖。你的 PEAR 仓库包含以下扩展包:
BasePackage
IntermediatePackage
,依赖BasePackage
TopLevelPackage1
和TopLevelPackage2
都依赖IntermediatePackage
如果没有 vendor 别名,Composer 会使用 PEAR 频道名作为包名的一部分:
pear-pear.foobar.repo/BasePackage
pear-pear.foobar.repo/IntermediatePackage
pear-pear.foobar.repo/TopLevelPackage1
pear-pear.foobar.repo/TopLevelPackage2
设想之后的某个时间你想要把 PEAR 包迁移到一个 Composer 仓库里,并且采用了 foobar
的 vendor 名称。使用你的 PEAR 包的项目不会看到更新后的包,因为它们的 vendor 名字变了 (foobar/IntermediatePackage
对比 pear-pear.foobar.repo/IntermediatePackage
).
通过一开始就为 PEAR 仓库指定 vendor-alias
,你可以避免这种情况并且你的包名都不会过期。
举例来说,下面的例子会从 PEAR 仓库获取 BasePackage
、TopLevelPackage1
和 TopLevelPackage2
包,以及从 Github 仓库获取 IntermediatePackage
包:
{
"repositories": [
{
"type": "git",
"url": "https://github.com/foobar/intermediate.git"
},
{
"type": "pear",
"url": "http://pear.foobar.repo",
"vendor-alias": "foobar"
}
],
"require": {
"foobar/TopLevelPackage1": "*",
"foobar/TopLevelPackage2": "*"
}
}
Package
如果你有一个通过以上任意方法都用不了 Composer 的项目,你仍可以通过使用 package
仓库来自己定义包。
最基础的,定义与 composer
仓库中 packages.json
已包含的相同信息,但这只适用于单独的包。最少必须项是 name
, version
以及 dist
或 source
。
这有一个 smarty 模板引擎的例子:
{
"repositories": [
{
"type": "package",
"package": {
"name": "smarty/smarty",
"version": "3.1.7",
"dist": {
"url": "https://www.smarty.net/files/Smarty-3.1.7.zip",
"type": "zip"
},
"source": {
"url": "http://smarty-php.googlecode.com/svn/",
"type": "svn",
"reference": "tags/Smarty_3_1_7/distribution/"
},
"autoload": {
"classmap": ["libs/"]
}
}
}
],
"require": {
"smarty/smarty": "3.1.*"
}
}
如果你并不真正需要 source
部分,可以把它留空。
注意:仓库类型有一些限制并且应该尽可能地避免:
- Composer 不会升级依赖包除非你修改了
version
字段。 - Composer 不会更新提交记录,所以如果你使用
master
作为引用,你必须把包删除来强制更新,并且处理一个不稳定的 lock 文件。
package
仓库里的 "package"
项可以设置为一个数组来定义包的不同版本:
{
"repositories": [
{
"type": "package",
"package": [
{
"name": "foo/bar",
"version": "1.0.0",
...
},
{
"name": "foo/bar",
"version": "2.0.0",
...
}
]
}
]
}
自建 Composer 仓库
大多数情况下你可能想把包放在 Packagist 上,但也有会使用自建仓库的情况。
- 公司私有包:如果你所在的公司使用内部的 Composer 包,你可能会把那些包作为私有。
- 独立生态系统:如果你有一个有自己生态系统的项目,并且其中的包不会被大多数 PHP 社区所使用,你可能会把他们存储在别处而不是 Packagist。WordPress 插件就是一个例子。
要自建包仓库,原生 composer
仓库类型是必须的,这会提供最好的性能。
有一些工具可以帮助你创建 composer
仓库。
私有的 Packagist
Private Packagist 是一个可以提供私人扩展包主机,以及 GitHub、Packagist.org 和其他包仓库镜像的应用。
详见 Packagist.com。
Satis
Satis 是一个静态 composer
仓库生成器,它很像是一个轻量和静态文件版本的 Packagist。
给他一个包含仓库的 composer.json
文件,典型的 VCS 和包仓库定义。它会获取所有 required
的包并且把它们 dump 到一个 packages.json
文件中,它就是你的 composer
仓库。
详见 Satis GitHub 仓库 和 Satis 文章。
工件
有些情况,无法使用之前提到的任意一种类型的在线仓库,甚至是 VCS。 典型的例子是通过构建工件来跨组织共享资源,当然大多数情况下是私有资源。为了便于维护,可以使用 工件(artifact)
仓库类型来指定包含那些 ZIP 压缩包的文件夹:
{
"repositories": [
{
"type": "artifact",
"url": "path/to/directory/with/zips/"
}
],
"require": {
"private-vendor-one/core": "15.6.2",
"private-vendor-two/connectivity": "*",
"acme-corp/parser": "10.3.5"
}
}
每个 ZIP 压缩包内的根目录都有一个 composer.json
文件:
unzip -l acme-corp-parser-10.3.5.zip
composer.json
...
如果一个包有两个不同版本的压缩文件,那它们可以被同时导入。当新版本的压缩文件被添加到了工件中,并且你执行了 update
,则这个新版本会被导入并且 Composer 会升级此扩展包至最新版本。
文件夹路径
除了工件类型仓库,你还可以使用 文件夹路径(path)
类型仓库,它允许你依赖一个本地目录,可以是相对或绝对路径,在处理比较大的仓库时会显得格外有用。
举例来说,如果你的仓库中目录结构是这样的:
- apps
\_ my-app
\_ composer.json
- packages
\_ my-package
\_ composer.json
接着,在你的 apps/my-app/composer.json
文件中要添加 my/package
包作为一个依赖,你可以使用如下配置:
{
"repositories": [
{
"type": "path",
"url": "../../packages/my-package"
}
],
"require": {
"my/package": "*"
}
}
如果扩展包是一个本地 VCS 仓库,那它的版本会根据当前已检出的分支或标签推断出来。否则,你应该在 composer.json
文件中显式定义出它的版本。如果版本不能被读取或获取,则默认会是 dev-master
。
本地扩展包会尽可能的被做为符号链接来引用,此时控制台输出会是 来自 ../../packages/my-package 的符号链接
。如果符号链接 不可用
,则包会被完整地拷贝(镜像)过来以供引用。这时,控制台输出会是 来自 ../../packages/my-package 的镜像
。
如果不想使用这种默认的 不可用则替代
策略,你可以设置 "symlink": true
来强制使用符号链接或 "symlink": false
来强制使用镜像。在需要部署或是要从大型仓库生成扩展包会时强制使用镜像会很有用。
{
"repositories": [
{
"type": "path",
"url": "../../packages/my-package",
"options": {
"symlink": false
}
}
]
}
~
会被展开成用户的 HOME 文件夹,并且可以根据不同的环境被解析成 Windows 和 Linux/Mac 使用的形式。例如 ~/git/mypackage
会自动从 Linux 的 /home/<username>/git/mypackage
目录加载 mypackage,在 Mac 上是 $HOME/git/mypackage
目录,而在 Windows 则是 %USERPROFILE%/git/mypackage
目录。
注意:仓库路径也可以包含通配符,例如
*
和?
。详见 PHP glob function。
禁用 Packagist.org
你可以将一下内容添加到 composer.json
,以禁用默认的 Packagist.org 仓库:
{
"repositories": [
{
"packagist.org": false
}
]
}
你也可以使用全局配置标识来禁用 Packagist.org:
composer config -g repo.packagist false