跳转到内容

常见问题

如何将包安装到我的框架的自定义路径?

Section titled “如何将包安装到我的框架的自定义路径?”

每个框架可能有一个或多个不同的所需包安装路径。通过使用composer/installers,可以将Composer配置为将包安装到默认的vendor文件夹以外的文件夹中。

如果你是一名包作者</b0,并且希望将你的包安装到自定义目录,请要求composer/installers并设置适当的type。指定包类型将覆盖默认的安装路径。如果你的包是为特定框架(如CakePHP、Drupal或WordPress)设计的,这种情况很常见。以下是一个WordPress主题的composer.json文件示例:

{
"name": "you/themename",
"type": "wordpress-theme",
"require": {
"composer/installers": "~1.0"
}
}

现在,当你的主题通过Composer安装时,它将被放置在wp-content/themes/themename/文件夹中。查看你的包的当前支持的类型

作为一名包消费者,你可以通过配置installer-paths额外参数来设置或覆盖需要composer/installers的包的安装路径。一个实用的例子是Drupal多站点设置,此时包应安装到你的站点子目录中。在这里,我们正在覆盖使用composer/installers的模块的安装路径,并将所有drupal-theme类型的包放入themes文件夹中:

{
"extra": {
"installer-paths": {
"sites/example.com/modules/{$name}": ["vendor/package"],
"sites/example.com/themes/{$name}": ["type:drupal-theme"]
}
}
}

现在,包将安装到您指定的文件夹位置,而不是由composer/installers默认确定的位置。此外,installer-paths是按顺序排列的,这意味着按名称移动包应该放在与同一包匹配的type:*的安装路径之前。

注意: 你不能使用此功能更改任何包的路径。这仅适用于需要composer/installers并使用其处理的自定义类型的包。

正如下载页面上所指出的,安装程序脚本包含一个校验和,该校验和会在安装程序代码更改时发生变化,因此不应长期依赖它。

另一种方法是使用此脚本,该脚本仅适用于UNIX工具:

#!/bin/sh
EXPECTED_CHECKSUM="$(php -r 'copy("https://composer.github.io/installer.sig", "php://stdout");')"
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"
if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]
then
>&2 echo 'ERROR: Invalid installer checksum'
rm composer-setup.php
exit 1
fi
php composer-setup.php --quiet
RESULT=$?
rm composer-setup.php
exit $RESULT

脚本在失败时将以1退出,成功时以0退出,且无错误发生时不会输出信息。

或者,如果你想依赖安装程序的精确副本,可以从GitHub的历史记录中获取特定版本。只要你信任GitHub服务器,提交哈希值就足以保证其唯一性和真实性。例如:

wget https://raw.githubusercontent.com/composer/getcomposer.org/f3108f64b4e1c1ce6eb462b159956461592b3e3e/web/installer -O - -q | php -- --quiet

你可以将提交哈希替换为https://github.com/composer/getcomposer.org/commits/main上的最新提交哈希。

如何安全地安装不受信任的软件包?以超级用户或 root 身份运行 Composer 安全吗?

Section titled “如何安全地安装不受信任的软件包?以超级用户或 root 身份运行 Composer 安全吗?”

为什么我会看到“不要以root/超级用户身份运行Composer”的警告/错误?

Section titled “为什么我会看到“不要以root/超级用户身份运行Composer”的警告/错误?”

出于以下详细原因,始终不建议以root用户身份运行Composer。

从Composer 2.4.2版本开始,当以root用户身份运行且没有迹象表明用户是有意识地这样做时,插件会被自动禁用。用户可以通过两种方式表示同意:

  • 如果您以交互方式运行,Composer会提示您是否确定要以root身份继续运行。如果您以非交互方式运行,插件将被禁用,除非……
  • 如果你将COMPOSER_ALLOW_SUPERUSER环境变量设置为1,这也表明你打算以root用户身份运行Composer,并且愿意承担这样做的风险。

以超级用户或 root 身份运行 Composer 是否安全?

Section titled “以超级用户或 root 身份运行 Composer 是否安全?”

某些Composer命令(包括execinstallupdate)允许第三方代码在你的系统上执行。这源于其“插件”和“脚本”功能。插件和脚本拥有运行Composer的用户账户的完全访问权限。因此,强烈建议避免以超级用户/root身份运行Composer。所有命令还会触发可被插件捕获的事件,因此除非明确禁用,否则已安装的插件将被每个Composer命令加载/执行。

你可以使用以下语法在包安装或更新期间禁用插件和脚本,这样只有Composer的代码会执行,而不会执行任何第三方代码:

php composer.phar install --no-plugins --no-scripts ...
php composer.phar update --no-plugins --no-scripts ...

根据操作系统的不同,我们发现存在这样的情况:通过精心设计的composer.json文件,有可能触发仓库中文件的执行。因此,一般来说,如果你确实想要安装不可信的依赖项,应该将它们完全隔离在容器或类似的环境中。

另请注意,exec命令将始终以运行composer的用户身份执行第三方代码。

有关如何禁用警告的更多信息,请参阅COMPOSER_ALLOW_SUPERUSER环境变量。

Composer会尽力检测自己是否在容器内运行,如果是,它将允许以root身份运行,且不会出现任何其他问题。然而,如果检测失败,你将会看到警告,并且插件会被禁用,除非你设置COMPOSER_ALLOW_SUPERUSER环境变量。

Composer 与许多其他工具一样,使用环境变量来控制代理服务器的使用,并且支持:

  • http_proxy - 用于HTTP请求的代理
  • https_proxy - 用于HTTPS请求的代理
  • CGI_HTTP_PROXY - 用于非CLI环境中HTTP请求的代理
  • no_proxy - 不需要代理的域名

这些命名变量是一种约定,而非官方标准,它们在不同操作系统和工具中的演变及使用情况较为复杂。Composer 倾向于使用小写名称,但在适当情况下也接受大写名称。

Composer需要特定的环境变量来处理HTTP和HTTPS请求。例如:

http_proxy=http://proxy.com:80
https_proxy=http://proxy.com:80

也可以使用大写名称。

在非CLI环境下,Composer不会查找http_proxyHTTP_PROXY。如果您通过这种方式运行它(即集成到CMS或类似用例中),则对于HTTP请求,您必须使用CGI_HTTP_PROXY

CGI_HTTP_PROXY=http://proxy.com:80
https_proxy=http://proxy.com:80
# cgi_http_proxy can also be used

**注意:**CGI_HTTP_PROXY由Perl于2001年引入,用于防止请求头操纵,并在2016年该漏洞被广泛报道时得到普及:https://httpoxy.org

使用scheme://host:port,如上述示例所示。尽管缺失的方案默认为http,且对于http/https方案,缺失的端口默认为80/443,但其他工具可能需要这些值。

主机可以指定为IPv4的点分四组表示法的IP地址,或IPv6的方括号括起来的IP地址。

Composer 支持基本授权,使用 scheme://user:pass@host:port 语法。用户名或密码中的保留 URL 字符必须进行百分比编码。例如:

user: me@company
pass: p@ssw$rd
proxy: http://proxy.com:80
# percent-encoded authorization
me%40company:p%40ssw%24rd
scheme://me%40company:p%40ssw%24rd@proxy.com:80

**注意:**用户名和密码组件必须分别进行百分比编码,然后用冒号分隔符组合。用户名不能包含冒号(即使经过百分比编码),因为代理会在找到的第一个冒号处拆分这些组件。

Composer支持HTTPS代理服务器,其中HTTPS是用于连接代理的协议,但这仅适用于PHP 7.3及以上版本且curl版本为7.52.0及以上的环境。

http_proxy=https://proxy.com:443
https_proxy=https://proxy.com:443

使用no_proxy(或NO_PROXY)环境变量来设置一个以逗号分隔的域名列表,代理不应用于这些域名。

no_proxy=example.com
# Bypasses the proxy for example.com and its sub-domains
no_proxy=www.example.com
# Bypasses the proxy for www.example.com and its sub-domains, but not for example.com

一个域名可以被限制在特定端口(例如:80),也可以被指定为IP地址或以CIDR表示法表示的IP地址块。

IPv6地址不需要像http_proxy/https_proxy值那样用方括号括起来,不过这种格式也是可以接受的。

将值设置为*将对所有请求绕过代理。

**注意:**域名中的前导点没有意义,会在处理前被移除。

Composer最初提供了HTTP_PROXY_REQUEST_FULLURIHTTPS_PROXY_REQUEST_FULLURI来帮助缓解代理工作异常的问题。现在这些已不再需要或使用。

我应该提交vendor目录中的依赖项吗?

Section titled “我应该提交vendor目录中的依赖项吗?”

一般建议是。vendor目录(或任何安装依赖项的位置)应添加到.gitignore/svn:ignore等文件中。

最佳实践是让所有开发人员使用Composer来安装依赖项。同样,构建服务器、持续集成、部署工具等也应进行调整,将运行Composer作为其项目引导的一部分。

虽然在某些环境中提交依赖项可能很有诱惑力,但这会导致一些问题:

  • 更新代码时,版本控制系统(VCS)的仓库规模会变得很大,差异也会增多。
  • 在你自己的版本控制系统中复制所有依赖项的历史记录。
  • 将通过git安装的依赖项添加到git仓库中会将它们显示为子模块。这是有问题的,因为它们并不是真正的子模块,你会遇到一些问题。

如果你确实觉得必须要这么做,有几个选项可供选择:

  1. 只安装标记的发行版本(不安装开发版本),这样你只会获得压缩的安装包,并避免出现 git“子模块”相关问题。
  2. 使用 —prefer-dist 或者在你的配置中将preferred-install设置为dist
  3. 安装完成后,删除每个依赖项的.git目录,然后你就可以将它们添加到你的git仓库中。在ZSH中,你可以使用rm -rf vendor/**/.git来执行此操作;在Bash中,可以使用find vendor/ -type d -name ".git" -exec rm -rf {} \;。但这意味着在运行composer update之前,你必须从磁盘中删除这些依赖项。
  4. 添加一条.gitignore规则(/vendor/**/.git)来忽略所有的vendor .git文件夹。这种方法不需要你在运行composer update之前从磁盘中删除依赖项。

Composer自身使用哪种版本编号系统?

Section titled “Composer自身使用哪种版本编号系统?”

Composer 使用语义化版本控制(又名 SemVer)2.0.0

为什么无限制的版本约束是个坏主意?

Section titled “为什么无限制的版本约束是个坏主意?”

没有上限的版本约束(如*>=3.4dev-master)将允许更新到该依赖项的任何未来版本,这包括破坏向后兼容性的主要版本。

一旦你的包的某个版本被标记发布,你就不能再调整其依赖项了,以防某个依赖项破坏了向后兼容性——你必须发布一个新版本,但之前的版本仍然是有问题的。

唯一可行的替代方案是为你的约束设定一个上限,在测试确认你的包与依赖项的新主版本兼容后,你可以在新版本中提高这个上限。

例如,不要使用>=3.4,而应使用^3.4,它允许所有版本 up 到3.999,但不包括4.0及以上版本。^运算符非常适合遵循语义化版本控制的库。

注意: 作为包维护者,您可以通过为开发分支提供别名版本来帮助用户,使其能够匹配绑定约束。

为什么将比较和通配符结合起来的版本约束是个坏主意?

Section titled “为什么将比较和通配符结合起来的版本约束是个坏主意?”

这是人们常犯的一个相当普遍的错误,即在他们的包需求中定义版本约束,比如>=2.*>=1.1.*

不过,如果你仔细想想它的真正含义,你会很快意识到这并没有多大意义。如果我们分解>=2.*,会得到两个部分:

  • >=2 表示该包的版本应在 2.0.0 或更高版本。
  • 2.* 表示该包的版本应在 2.0.0(含)到 3.0.0(不含)之间。

如你所见,两条规则都认同该软件包的版本必须≥2.0.0这一事实,但无法确定你在写下这一要求时,是否考虑的是3.0.0版本的软件包。是因为你要求了>=2就应该匹配,还是因为你要求了2.*就不应该匹配呢?

因此,Composer 会抛出一个错误,提示这是无效的。解决方法是想清楚你真正的意图,并且只使用其中一条规则。

为什么 Composer 不能递归加载仓库?

Section titled “为什么 Composer 不能递归加载仓库?”

使用自定义仓库时,你可能会遇到一些问题,因为Composer不会加载你的依赖项的仓库,所以你必须在所有的composer.json文件中重新定义这些仓库。

在深入探讨为何会出现这种情况之前,你必须明白,自定义版本控制系统(VCS)和包仓库的主要用途是临时尝试一些东西,或者在你的拉取请求被合并之前使用某个项目的分支等。你不应该用它们来跟踪私有包。对于私有包的管理,你应该考虑使用Private Packagist</b0,它能让你在一个地方配置所有私有包,并且可以避免与内联VCS仓库相关的速度变慢问题。

依赖求解器可以通过三种方式处理自定义仓库:

  • 获取根包的存储库,从已定义的存储库中获取所有包,然后解析依赖需求。这是当前的状态,除了不能递归加载存储库这一限制外,它运行良好。
  • 获取根包的仓库,在从已定义的仓库初始化包时,递归初始化在这些包中找到的所有仓库,以及它们的包的包,依此类推,然后解析依赖需求。这可能行得通,但会大大减慢初始化速度,因为每个版本控制系统仓库可能都需要几秒钟,而且可能会导致完全混乱的状态,因为一个包的多个版本可能在包仓库中定义相同的包,但具有不同的发行版/源代码。这其中有很多可能出错的地方。
  • 先获取根包的仓库,然后获取一级依赖项的仓库,接着获取这些依赖项的依赖项的仓库,依此类推,之后解析需求。这听起来更高效,但它存在与第二种解决方案相同的问题,因为加载依赖项的仓库并不像听起来那么容易。你需要加载某个需求的所有潜在匹配项的所有仓库,而这又可能存在冲突的包定义。