Composer Vendor Binaries 完全指南:vendor/bin目录的原理、代理文件与运行环境变量
【免费下载链接】composerDependency Manager for PHP项目地址: https://gitcode.com/gh_mirrors/co/composer
导读
本文围绕 Composer 的 Vendor Binaries 机制展开,系统讲解如何通过composer.json的bin字段把包中的命令行脚本暴露给使用者、composer install时 Composer 如何在vendor/bin中生成代理文件(proxy),以及如何在二进制脚本中通过$_composer_autoload_path、$_composer_bin_dir、COMPOSER_RUNTIME_BIN_DIR定位 autoloader 与 bin 目录。读完本文,你将能正确地在自己的包中声明并消费二进制脚本,兼容 Windows/WSL,并灵活自定义二进制安装目录。
什么是 Vendor Binary
Vendor binary(供应商二进制脚本)指任何由 Composer 包希望传递给它使用者的命令行脚本。只要一个包内存在需要暴露给使用者的 CLI 程序(例如phpunit、phpcs、psalm),就应该把它声明为 vendor binary。
与此相对,如果包内还有一些仅服务于包本身开发流程、使用者并不需要的脚本(如构建脚本、编译脚本),则不应该将其声明为 vendor binary——它们应留在包的源码树深处,而不是被安装到使用者的vendor/bin中。
如何在composer.json中声明二进制脚本
在项目(包)的composer.json中通过bin键声明。它是一个数组,因此可以为同一个项目声明多个二进制脚本:
{ "bin": ["bin/my-script", "bin/my-other-script"] }从 Composer 的 schema 定义看(见 04-schema.md),bin是可选的,含义为“一组应当被当作二进制处理并放入bin-dir(来自 config)的文件”。也就是说,数组中的每一项都是相对于包根目录的路径,指向包内一个可执行文件。
声明后会发生什么:声明者与依赖者的区别
在 Composer 中,二进制脚本的安装行为取决于你是声明者还是依赖者:
- 声明者(root package):当 Composer 在你自己的
composer.json上运行时(例如composer install),对于该包自身直接声明的二进制脚本,什么都不做。因为这是你的源码树,脚本本来就在那里,无需代理。 - 依赖者(依赖了该包的项目):Composer 会遍历所有依赖包中声明的二进制脚本,为每个二进制在
vendor/bin下生成一个代理文件(proxy file)(在 Windows/WSL 上会生成一个或两个代理文件,详见后文 Windows 小节)。
一个完整的示例
假设包my-vendor/project-a声明了二进制脚本:
{ "name": "my-vendor/project-a", "bin": ["bin/project-a-bin"] }此时,对这个composer.json运行composer install并不会对bin/project-a-bin做任何事(它是声明者)。
再假设项目my-vendor/project-b依赖了它:
{ "name": "my-vendor/project-b", "require": { "my-vendor/project-a": "*" } }对 project-b 运行composer install时,Composer 会查看 project-a 声明的所有二进制脚本,并将它们安装到 project-b 的vendor/bin下。具体结果是:vendor/my-vendor/project-a/bin/project-a-bin通过代理文件以vendor/bin/project-a-bin的形式可供调用。
这正是把散落在vendor/目录深处的脚本“提升”到统一入口的便捷机制。
代理文件的生成原理(源码级解析)
在源码层面,二进制代理文件的生成由 BinaryInstaller.php 负责。核心方法installBinaries()(第 52-105 行)的执行逻辑如下:
- 通过
$package->getBinaries()取回包声明的二进制列表;若为空直接返回。 - 逐项检查二进制文件是否存在(不存在或指向目录时会跳过并给出 warning)。
- 调用
initializeBinDir()确保bin-dir目录存在。 - 根据
bin-compat配置选择安装方式:full:调用installFullBinaries()——同时生成 Unix 风格代理和.bat代理;- 否则:调用
installUnixyProxyBinaries()——仅生成 Unix 风格代理。
PHP 二进制:生成 PHP 代理文件
对于内容是 PHP 的脚本,generateUnixyProxyCode()(第 206-367 行)会生成一个PHP 代理文件而非 shell 脚本,这样可以用自定义的 PHP 进程来调用代理。代理文件的关键片段包括:
<?php namespace Composer; $GLOBALS['_composer_bin_dir'] = __DIR__; // bin 目录 $GLOBALS['_composer_autoload_path'] = ...; // 指向 vendor/autoload.php return include $binPathExported; // 真正加载原二进制也就是说,$_composer_bin_dir与$_composer_autoload_path这两个全局变量正是在生成代理文件时写入的。源码还针对带 shebang 的 PHP 文件在 PHP 8 以下做了 stream wrapper(phpvfscomposer://)兼容处理,并对 phpunit 做了进程隔离的特殊处理,这里不再展开。
非 PHP 二进制:生成 shell 代理文件
对于非 PHP 脚本(shell、Perl 等),generateUnixyProxyCode()会生成一个#!/usr/bin/env sh的代理文件:先解析出真实路径,cd到目标目录后用exec执行原脚本,并显式导出COMPOSER_RUNTIME_BIN_DIR环境变量(第 395 行):
export COMPOSER_RUNTIME_BIN_DIR="$(cd "${self%[/\\]*}" > /dev/null; pwd)"如何判断一个脚本是 PHP 还是其他语言?看determineBinaryCaller()(第 131-145 行):.bat/.exe后缀返回call;读取文件首行,若匹配 shebang 则返回解释器名;否则默认按php处理。
在二进制脚本中定位 Composer autoloader
自 Composer 2.2 起,bin 代理文件会定义一个名为$_composer_autoload_path的全局变量。当你的二进制脚本被执行时,可以借助它轻松定位到项目(依赖者)的 autoloader:
<?php include $_composer_autoload_path ?? __DIR__ . '/../vendor/autoload.php';需要注意:当二进制由root 包自身定义并运行时,该全局变量并不会被定义,因此必须提供上面这种 fallback。另外,vendor/autoload.php本身并不会设置这个变量(见 07-runtime.md)——它由代理文件注入,如果由 autoloader 自己设置,就等于指回自身,毫无意义。
如果要在自己的包里依赖这个特性,应在composer.json中要求"composer-runtime-api": "^2.2",确保安装方使用的 Composer 版本支持该功能:
{ "require": { "composer-runtime-api": "^2.2" } }在二进制脚本中定位 Composer bin 目录
自 Composer 2.2.2 起,代理文件还会定义$_composer_bin_dir全局变量,方便脚本定位项目(依赖者)的 bin 目录。对于非 PHP 二进制,自 Composer 2.2.6 起,代理会额外设置名为COMPOSER_RUNTIME_BIN_DIR的环境变量(这正是源码中export COMPOSER_RUNTIME_BIN_DIR一行所做的)。
同样,root 包自身定义的二进制运行时不会得到该全局变量,需要 fallback。PHP 场景的示例:
<?php $binDir = $_composer_bin_dir ?? __DIR__ . '/../vendor/bin';bash 场景的示例:
#!/bin/bash if [[ -z "$COMPOSER_RUNTIME_BIN_DIR" ]]; then BIN_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" else BIN_DIR="$COMPOSER_RUNTIME_BIN_DIR" fi依赖该特性的包应要求"composer-runtime-api": "^2.2.2":
{ "require": { "composer-runtime-api": "^2.2.2" } }相关背景可参考 07-runtime.md:这些变量同样由二进制代理设置,而不是由vendor/autoload.php提供。
Windows 与.bat文件
完全由 Composer 管理的包不需要为了 Windows 兼容性自带任何.bat文件。当 Composer 在 Windows 环境下运行时,会以特殊方式处理二进制的安装:
- 自动生成一个引用原二进制的
.bat文件; - 同时生成一个与二进制同名的Unix 风格代理文件,供 WSL、Linux 虚拟机等环境使用。
在源码中,installFullBinaries()(第 155-169 行)正是“先装 Unix 代理、再补.bat”的实现,而generateWindowsProxyCode()(第 183-204 行)会生成形如下面的批处理内容(以 PHP 目标为例):
@ECHO OFF setlocal DISABLEDELAYEDEXPANSION SET BIN_TARGET=%~dp0/project-a-bin SET COMPOSER_RUNTIME_BIN_DIR=%~dp0 php "%BIN_TARGET%" %*注意其中的COMPOSER_RUNTIME_BIN_DIR=%~dp0——这正是 Windows 批处理场景下该环境变量的来源。
bin-compat配置
是否生成.bat文件由bin-compat配置决定(默认auto,详见 06-config.md):
auto(默认):仅在 Windows 或 WSL 环境下安装.bat代理文件;full:为每个二进制同时安装 Windows 的.bat文件与 Unix 脚本。主要用于“在 Linux 虚拟机里跑 Composer,但希望在 Windows 宿主机上使用.bat代理”的场景;proxy:只创建 bash/Unix 风格代理文件,即使在 Windows/WSL 上也不生成.bat。
也可以不修改配置文件,直接用环境变量 COMPOSER_BIN_COMPAT 覆盖bin-compat设置。
另一方面,如果包需要支持“不经由 Composer 分发”的工作流(例如直接从源码仓库使用),欢迎维护自定义.bat文件。这种情况下,包不应再把.bat文件声明为 binary,因为 Composer 会自己生成,无需重复。
让 vendor binaries 安装到其他目录
可以,有两种方式可以把 vendor binary 的安装目录从默认的vendor/bin改到别处:
- 在
composer.json中设置config.bin-dir; - 设置环境变量
COMPOSER_BIN_DIR。
bin-dir的默认值是{$vendor-dir}/bin(即vendor/bin,见 Config.php 与 06-config.md)。通过配置文件修改的示例:
{ "config": { "bin-dir": "scripts" } }对该composer.json运行composer install后,所有 vendor binary 会被安装到scripts/而不是vendor/bin/。还可以把bin-dir设为./,把二进制直接放到项目根目录。
环境变量方式的说明见 03-cli.md:设置COMPOSER_BIN_DIR即可把 bin 目录改为vendor/bin以外的位置。两种方式二选一即可满足定制需求。
小结
bin字段把包内的可执行脚本“发布”给依赖者,composer install会为依赖包中的每个二进制在vendor/bin(或其定制目录)生成代理文件。- root 包自己声明的二进制不会生成代理,因此脚本中用到运行时全局变量时必须提供 fallback。
- PHP 二进制会得到 PHP 代理并注入
$_composer_autoload_path、$_composer_bin_dir全局变量;非 PHP 二进制则通过COMPOSER_RUNTIME_BIN_DIR环境变量感知 bin 目录。 - 依赖这些特性的包应声明
"composer-runtime-api": "^2.2"(或^2.2.2)约束。 - Windows 下的
.bat与 Unix 代理由 Composer 按bin-compat(auto/full/proxy)自动生成,包内无需自带。 - 通过
config.bin-dir或COMPOSER_BIN_DIR可以自由定制二进制安装目录。
更详细的机制可以参考 Composer 源码中的 BinaryInstaller.php,以及配套文档 04-schema.md、06-config.md、07-runtime.md 与 03-cli.md。
【免费下载链接】composerDependency Manager for PHP项目地址: https://gitcode.com/gh_mirrors/co/composer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考