Composer 工程手册:从依赖解析到受控执行
Composer 最容易制造误判的地方,是 composer install 返回成功,却没有证明应用能在目标环境运行。开发机可能装着额外 PHP 扩展,config.platform 又让解析器假装成另一套平台;安装脚本在开发者电脑上悄悄生成文件,CI 用 --no-scripts 后产物缺失;私有仓库配置正确,但 token 被写进了 auth.json 并提交。
Composer 不只是“下载 PHP 包”。它同时处理版本约束、平台依赖、仓库优先级、自动加载生成和生命周期代码执行。架构师要治理的不是某条命令,而是从 manifest 到 lock、从仓库到 vendor、从受信插件到运行平台的一整条证据链。
先确认失败发生在锁定、平台还是执行阶段
composer install 成功只能证明当前 PHP CLI 能恢复一组依赖,不能替目标运行环境背书。排查时先区分三条链路:composer.json 与仓库元数据如何求解并写入 composer.lock;PHP 版本、扩展和 config.platform 如何限制可安装集合;scripts 与 plugins 又在安装过程中执行了哪些代码。混淆这三层,常见结果就是开发机锁得住、CI 装得上、服务启动后才暴露扩展缺失或生成物不完整。
Composer 负责依赖求解、锁定恢复、自动加载生成和受控生命周期执行。Laravel、Symfony 等框架行为,PHP 运行时性能,以及 Satis、Private Packagist 和制品仓库服务端的部署分别由相邻工程域负责;客户端仍需明确仓库优先级、认证作用域和不可用时的恢复策略。这个职责划分让依赖问题停在 Composer 证据链内,也避免把服务端故障误判为 lockfile 故障。
复现依赖问题前,先对齐 PHP 入口与权限
准备一台非生产开发机、可删除的练习目录和受支持的 PHP CLI。先检查真实入口与扩展:
php --version
php --ini
php -m
php -r "echo PHP_BINARY, PHP_EOL;"再检查 Composer:
composer --version
composer diagnose
composer config --list --globalComposer 版本、PHP 最低要求和安全公告会持续变化。安装或升级前从 Composer 下载页读取当前稳定通道,并以项目批准的 PHP 兼容组合为准。PHP CLI 与 Web/FPM 可能加载不同 php.ini,命令行安装成功并不能证明服务进程扩展齐全。
企业环境还要确认 HTTPS proxy、根 CA、Git/SSH 客户端、解压扩展、私有源只读凭证和用户缓存目录权限。
PHP 与 Composer 的职责边界
PHP 是运行 Composer 和应用的解释器。Composer 的依赖图中不仅有普通包,还有 php、ext-json、ext-curl、lib-* 等平台包。它们代表当前或模拟平台能力,不会被 Composer 下载到 vendor/。
Composer 负责:
读取根项目的版本与稳定性约束。从仓库元数据中求解一组可安装版本。把解析结果写入 lockfile。
下载、校验和安装包。生成自动加载文件。在允许时执行 scripts 与 plugins。
Composer 不负责配置 PHP-FPM、Web Server、数据库和业务框架,也不能替代目标环境运行验证。
安装 Composer,并验证安装器
交互式开发机可以按 官方安装步骤下载安装器。关键不是机械复制固定摘要,而是在执行前实时获取官方签名并比较:
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "copy('https://composer.github.io/installer.sig', 'installer.sig');"
php -r "if (hash_file('sha384', 'composer-setup.php') === trim(file_get_contents('installer.sig'))) { echo 'Installer verified', PHP_EOL; } else { echo 'Installer corrupt', PHP_EOL; unlink('composer-setup.php'); exit(1); }"
php composer-setup.php --install-dir=bin --filename=composer
php -r "unlink('composer-setup.php'); unlink('installer.sig');"
php bin/composer --version全局安装需要目标目录写权限,不应默认使用 root。团队可以把经过摘要校验的 composer.phar 放入受控工具镜像,或在 CI 使用固定版本的官方 action/镜像。无论入口如何,构建日志都要打印 php --version 与 composer --version。
Windows 可使用官方安装器,也可以运行 PHAR。先用 Get-Command composer 和 Get-Command php 检查 PATH,避免 Composer 绑定到另一套 PHP。
建立一个最小可验证项目
创建目录和 composer.json:
{
"name": "example/build-lab",
"description": "Composer reproducibility lab",
"type": "project",
"require": {
"php": "^8.3",
"psr/log": "^3.0"
},
"autoload": {
"psr-4": {
"BuildLab\\": "src/"
}
},
"config": {
"sort-packages": true,
"allow-plugins": {}
}
}安装并验证:
composer validate --strict
composer install
composer check-platform-reqs
composer show --direct
composer audit第一次安装会解析版本并生成 composer.lock;后续安装应按 lockfile 恢复。项目应提交 composer.json 和 composer.lock,通常不提交 vendor/。库包是否提交 lockfile 要按发布与测试策略决定,不能把应用规则机械复制给库。
composer.json 是意图,lockfile 是解析结果
composer.json 中的 ^3.0 表达允许范围,不是最终版本。求解器还会考虑 PHP、扩展、稳定性、冲突、替换、仓库优先级和根项目约束。
composer.lock 记录选中的包版本、来源引用、内容摘要和平台相关元数据。install 在存在 lockfile 时恢复这组结果;update 重新求解并改写 lockfile。
composer install --dry-run
composer update --dry-run
composer outdated --direct这三条命令回答的问题不同:安装演练检查当前锁定结果,更新演练展示允许变化,outdated 展示可升级信息。CI 只运行 install,依赖升级放在独立变更中执行 update 并审查 lock diff。
发现 composer.json 与 lockfile 不一致时,install 会警告甚至失败。正确修复是回到依赖变更分支运行定向更新,而不是手改 lockfile:
composer update vendor/package --with-all-dependencies
composer validate --strict
composer install --dry-run版本约束与稳定性
常见约束:
^2.4 通常允许兼容的非破坏性升级。~2.4.1 限制在更窄的次版本范围。2.4.* 允许补丁漂移。
精确版本适合需要强控制的根项目,但增加安全更新成本。dev-* 与分支别名会引入漂移,生产应用应给出明确退出条件。
根项目的 minimum-stability 和 prefer-stable 会影响整个求解。不要为了安装一个预发布包把全局稳定性降到 dev;可以只对目标包声明显式稳定性标记,并记录何时退出。
查看求解原因:
composer why vendor/package
composer why-not vendor/package 3.0.0
composer prohibits php 8.4
composer show vendor/package --all依赖冲突排查的入口是 why/why-not,不是随机删除 lockfile。
平台依赖:解析时通过不等于运行时满足
平台包来自执行 Composer 的 PHP 环境:
composer show --platform
composer check-platform-reqsconfig.platform 可以模拟 PHP 或扩展版本,让不同开发机得到一致解析:
{
"config": {
"platform": {
"php": "8.3.0",
"ext-json": "8.3.0"
}
}
}它只影响依赖解析,不会安装 PHP,也不会让缺失扩展凭空出现。部署产物必须在目标镜像运行 composer check-platform-reqs --no-dev,并由应用启动测试确认真实扩展。
--ignore-platform-reqs 只能用于诊断或特殊构建阶段,不能成为 CI 默认参数。它会把不可运行的依赖组合推进到后续阶段。
自动加载是运行时契约
Composer 支持 PSR-4、PSR-0、classmap 和 files 自动加载。PSR-4 示例:
{
"autoload": {
"psr-4": {
"BuildLab\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"BuildLab\\Tests\\": "tests/"
}
}
}修改映射后执行:
composer dump-autoload
composer dump-autoload --optimize --strict-psr --strict-ambiguous生产应用常使用 --optimize-autoloader,项目可根据部署模型再评估 authoritative classmap 或 APCu。优化前先解决大小写和重复类问题。Linux 上类路径大小写错误常在 Windows/macOS 开发机被掩盖。
自动加载验证不能只看文件生成。写一个最小启动检查:
php -r "require 'vendor/autoload.php'; echo class_exists('Psr\\Log\\LoggerInterface') ? 'autoload-ok' : 'autoload-failed';"Scripts 与 Plugins 都能执行代码
Composer scripts 是事件回调,可以执行 PHP 方法、Composer 命令和 shell 命令:
{
"scripts": {
"check": [
"@php -l src/Example.php"
],
"post-install-cmd": [
"@check"
]
}
}插件则在 Composer 进程内扩展行为。二者都属于供应链执行面,而不是普通元数据。首次接入陌生仓库时,先禁用执行完成静态检查:
composer install --no-scripts --no-plugins
composer validate --strict
composer show --locked审查通过后再显式允许。allow-plugins 默认不应写成 true:
{
"config": {
"allow-plugins": {
"trusted-vendor/required-plugin": true,
"unneeded-vendor/*": false
}
}
}脚本应可重复执行、有明确输入输出、失败时返回非零退出码,并避免修改仓库外状态。数据库迁移、生产发布和长期服务启动不应隐藏在普通 post-install-cmd 中。
Repository 与 Packagist 优先级
默认情况下,Composer 使用 Packagist。根项目可以声明额外仓库:
{
"repositories": [
{
"type": "composer",
"url": "https://packages.example.invalid"
}
]
}示例域名只是占位符。Composer 2 中仓库通常是 canonical 的:高优先级仓库一旦提供某包,低优先级仓库不会再提供候选版本。这能阻止同名依赖混淆,也会在私有仓库元数据不完整时让可用公共版本“消失”。
查看最终仓库配置:
composer repo list
composer config --list --source
composer show vendor/package --all私有包名应使用组织命名空间,并确保内部仓库明确占据优先级。需要回退公共源时,要把可回退范围写清楚,不能用全局关闭 canonical 的方式掩盖仓库同步故障。
认证:凭证不能进入项目事实
Composer 支持 auth.json、环境变量 COMPOSER_AUTH、HTTP Basic、Bearer、GitHub/GitLab token 等方式。选择原则是:项目文件保存仓库位置,凭证由用户或 CI 运行时注入。
composer config --global --auth http-basic.packages.example.invalid <username> <token>
composer config --list --source上例会把凭证写入用户级 auth 文件,只适合受控开发机;执行历史也可能记录参数。CI 更适合由 secret 管理器生成临时 COMPOSER_AUTH,并在 job 结束后销毁。
不要把 token 放进 repository URL、composer.json、lockfile、Docker build argument 或日志。泄露后应撤销与轮换,而不是只从 Git 最新提交删除。
网络出口和 CA 排查时使用:
composer diagnose -vvv
composer config --list --source
php -r "var_export(openssl_get_cert_locations());"详细日志可能含私有包名、仓库和请求头,分享前脱敏。正确修复是配置受控根 CA 与网络出口信任,不是关闭 secure-http 或 TLS 校验。
缓存与离线边界
查看缓存位置与状态:
composer config cache-dir
composer config cache-files-dir
composer config cache-repo-dir
composer diagnoseComposer 缓存包含仓库元数据、dist 包和 VCS clone。缓存是共享加速层,不是 lockfile,也不是长期制品仓库。遇到损坏应先在隔离缓存中复现,而不是立即清空所有项目缓存:
COMPOSER_CACHE_DIR=/tmp/composer-cache-lab composer install --dry-run -vvvCOMPOSER_DISABLE_NETWORK=1 可帮助验证缓存是否足以支持离线,但这是 best-effort 调试入口,不能自动证明所有插件和脚本都不联网。完整离线验收需要新工作目录、预热或镜像的依赖、禁网环境和目标平台检查。
真正受控的离线方案通常依赖内部 Composer 仓库或批准的 dist 制品快照,而不是复制某位开发者的缓存目录。
安全审计与来源治理
运行:
composer audit
composer audit --locked
composer show --locked
composer licensesaudit 使用仓库提供的安全公告,并可报告 abandoned 包。它是准入证据之一,不代表代码无漏洞。审计结果应有失败策略、例外 owner、到期时间和升级路径。
依赖变更审查至少看:直接与传递版本变化、source/dist URL、包类型、scripts/plugins、许可证、abandoned 状态和平台约束。新增 Composer plugin 的风险等级应高于普通库,因为它在构建机执行代码。
Root 与容器构建风险
Composer 检测到 root 运行时会警告,因为 scripts 和 plugins 可用该权限执行代码。容器构建中 root 很常见,但这不等于风险消失。
更稳妥的容器步骤是:
在依赖描述文件已经审查的前提下运行 Composer。用 --no-dev --prefer-dist --no-interaction --no-progress 收敛行为。只允许明确插件,必要时先 --no-scripts --no-plugins。
不把宿主 SSH agent、长期 token 和整个用户目录挂进构建。最终运行镜像不携带 Composer、缓存和认证文件。
不要为了消除警告无条件设置 COMPOSER_ALLOW_SUPERUSER=1。它只表示你接受 root 场景,不会限制第三方代码能力。
CI:锁定安装而不是现场求解
一个应用项目的常用入口:
steps:
- name: Versions
run: |
php --version
composer --version
- name: Validate
run: composer validate --strict
- name: Install
run: composer install --no-interaction --no-progress --prefer-dist --optimize-autoloader
- name: Platform
run: composer check-platform-reqs
- name: Audit
run: composer audit --locked生产依赖安装通常增加 --no-dev。是否运行 scripts 必须显式决定;不能一套流水线默认启用,另一套默认禁用而没有产物差异验证。
CI 缓存键至少包含 OS、PHP 主次版本、lockfile 摘要和 Composer 大版本。不要缓存 vendor/ 跨分支复用为真值;优先缓存下载层,并定期用空缓存验证可重建。
普通验证 job 只使用私有源只读凭证。发布包、写仓库和生产部署使用独立 job、独立身份和审批。
常见失败:从现象回到证据
lockfile 与 manifest 不一致
composer install 提示 lockfile 不包含兼容集合。
运行 composer validate --strict,查看 composer.json 与 composer.lock 是否来自同一变更。
在依赖更新分支执行定向 composer update <package> --with-all-dependencies,审查 lock diff。
干净 clone 中 composer install 不改文件,平台检查和测试通过。
依赖无法解析
Your requirements could not be resolved。
用 composer why-not <package> <version>、composer prohibits php <version> 找阻断约束,检查稳定性和平台包。
升级阻断依赖、调整真实业务约束或补齐目标扩展;不要直接使用 --ignore-platform-reqs。
定向更新成功,lockfile 变化可解释,目标平台 check-platform-reqs 通过。
401/403 或包找不到
私有源返回未授权,或明明存在的包解析不到。
检查 composer config --list --source、仓库 canonical 优先级、凭证作用域和网络出口/CA;服务端可能用 404 隐藏无权限资源。
使用最小只读凭证,修正仓库顺序与包命名空间,安装受控 CA。
隔离用户配置后由运行时凭证完成安装,日志不含 secret,服务端审计显示预期身份。
插件被阻止或非交互 CI 卡住
出现 allow-plugins 警告,CI 等待交互。
确认包是否是 composer-plugin,审查其来源、代码执行职责和必要性。
只对白名单包设置 true,不需要的插件显式设 false 或移除。
--no-interaction 安装无提示,插件版本和行为由 lockfile 固定。
开发机成功,目标环境缺扩展
部署后提示平台要求不满足或类/函数不存在。
比较 CLI 与 FPM 的 php.ini、php -m,查看 config.platform 是否模拟了并不存在的扩展。
在目标镜像安装扩展,或修正依赖与平台声明;不要把模拟平台当作安装动作。
目标运行镜像执行 composer check-platform-reqs --no-dev 和应用启动检查。
自动加载在 Linux 失败
本地能加载,Linux 报 class not found。
运行 composer dump-autoload --optimize --strict-psr --strict-ambiguous,检查命名空间与路径大小写。
统一 PSR-4 前缀、目录和类名大小写,移除重复类。
Linux 干净环境重新生成 autoload 并运行代表性启动测试。
从 Composer 1/旧项目迁移
Composer 1 已不应作为现代项目默认基线。迁移前先记录 PHP 版本、插件、repositories、全局配置、安装结果和部署脚本。然后:
composer diagnose
composer validate --strict
composer show --locked
composer audit --locked升级 Composer 大版本时重点检查:插件 API 兼容、仓库 canonical 行为、allow-plugins、弃用配置、脚本时序和 PHP 最低要求。不要同时大规模更新业务依赖;先让旧 lockfile 在新 Composer 下可重复安装,再单独升级依赖。
旧项目没有 lockfile 时,第一次求解本身就是高风险变更。先在隔离分支生成,记录直接和传递包、许可证、公告与平台要求,再通过完整测试决定是否接纳。
升级与回滚
升级前保存:
php --version
composer --version
composer validate --strict
composer install --dry-run
composer audit --locked依赖升级优先定向执行,避免一次更新整棵树:
composer update vendor/package --with-all-dependencies --minimal-changes
composer validate --strict
composer audit --locked回滚必须恢复 composer.json 与 composer.lock 的同一提交,并用批准 Composer/PHP 基线重新安装。不要保留新 vendor/ 再只回退 lockfile。若插件或脚本在升级期间修改了仓库外状态,还要按其独立回滚方案恢复。
config.platform 能稳定解析,也能制造虚假环境
它适合让开发机按部署 PHP 求解,却可能掩盖真实扩展缺失。判断标准是目标镜像中的 check-platform-reqs,不是开发机 install 成功。平台模拟值必须有运行环境 owner,并随基础镜像升级同步修改。
scripts 与 plugins 把依赖安装变成代码执行
风险不止恶意包。一个善意插件也可能访问网络、读取环境变量或在新版本改变生成结果。团队要把新增插件作为高风险依赖变更,默认拒绝未知插件,并记录执行时机、权限、输出和退出方案。
仓库优先级决定供应链边界
私有同名包若没有 canonical 边界,公共高版本可能进入解析;规则过严又会让内部元数据故障阻断安全更新。选型时明确哪些命名空间只能来自内部、哪些允许公共回退,并用仓库审计和 lock source 验证,不靠口头约定。
缓存命中会掩盖不可恢复依赖
开发机已有 dist 文件时,已下线版本仍能安装;新 CI 节点则失败。每个发布周期都应把空缓存恢复设为阻断性检查。长期可用性由内部仓库、上游保留策略和归档决定,不由个人缓存承担。
root、宿主挂载和构建 secret 会叠加风险
容器内 root 加宿主 SSH agent,再运行未审查插件,相当于把构建依赖变成宿主权限入口。最小化挂载和 secret 作用域,构建阶段与运行阶段分离;发现异常访问立即撤销凭证并审查构建产物。
自动加载优化会把大小写与重复类问题推迟到发布
非优化模式可能在开发机动态找到类,authoritative classmap 则直接失败。优化策略必须在 CI 和生产一致,严格 PSR 检查提前执行。修复类映射,而不是在生产关闭优化回避。
安全审计没有自动等于升级决策
公告命中后要判断受影响代码路径、可用修复版本、PHP 兼容和回滚成本。例外必须有 owner、到期时间和补偿控制。composer audit 退出码可以触发发布阻断,但不是完整风险结论。
团队落地清单
PHP CLI、服务运行时与 Composer 版本基线可查询,支持期与兼容组合明确。Composer 安装包或 PHAR 来源和摘要可验证,CI 不执行未校验安装器。应用提交 composer.json 与 composer.lock,CI 只做锁定安装,不现场更新。
平台要求写入依赖模型,并在目标运行镜像执行 check-platform-reqs。config.platform 有 owner,没有被误当成真实扩展安装证明。autoload 映射通过严格 PSR 与重复类检查,Linux 大小写行为已验证。
scripts 与 plugins 已审查,allow-plugins 是最小白名单而非全开。私有仓库优先级、canonical 策略与公共回退边界可解释。凭证不进入 URL、manifest、lockfile、镜像层和日志,验证 job 只有读权限。
缓存只加速,空缓存和受控离线场景都能按计划恢复。composer audit --locked、许可证和 abandoned 包有准入检查与例外到期机制。Composer、PHP 与依赖升级分步执行,manifest、lock、vendor 和外部状态可成组回滚。
延伸阅读
Composer 下载与安装。Composer 基本用法。composer.json 架构
