composer 的 bin 机制不提供运行时沙箱,vendor/bin 命令共享主进程环境,易引发类重声明、dev 组件污染等问题;真正隔离需 proc_open 启动独立 vendor 的子进程并清理常量与 autoload。

Composer 本身不提供二进制文件的运行时安全沙箱机制,所谓“隔离执行”必须靠分进程 + 独立 vendor 实现,否则类重声明、路径污染、权限越界都会直接发生。
为什么 vendor/bin 下的命令不能算沙箱
vendor/bin 中的 phpunit、phpcs 等二进制文件,本质只是指向依赖包内脚本的符号链接或 .bat 包装器。它们在当前 PHP 进程中运行,共享同一 autoloader、同一全局命名空间、同一 get_defined_constants()。一旦你的项目或某个依赖加载了冲突版本的 monolog、symfony/event-dispatcher,或者启用了 dev autoload 映射,就可能:
- 触发 Fatal error: Cannot redeclare class
- 意外加载 vendor/composer/autoload_dev.php 中的测试工具(如 Prophecy)到生产环境
- 调用到被 replace 或 conflict 声明屏蔽但实际已加载的类
真正可行的沙箱:proc_open + 独立 vendor 目录
要让不同版本的二进制工具互不干扰,只能构造物理隔离的子进程环境:
- 每个沙箱对应一个独立子目录(如 sandboxes/phpunit-9.6/),内含自己的 composer.json、vendor/ 和入口脚本
- 主进程用 proc_open() 启动子进程,例如:php ./sandboxes/phpunit-9.6/bin/phpunit --testdox tests/
- 子进程启动前必须清理常量:foreach (get_defined_constants(true)['user'] as $k => $_) { if (strpos($k, 'APP_') === 0) unset($$k); }
- 安装时强制禁用 dev 依赖:composer install --no-dev --optimize-autoloader --no-scripts,并手动确认 vendor/composer/autoload_dev.php 不存在
bin-compat 和 PATH 不解决隔离问题
"bin-compat": "full" 只影响 Windows 下 .bat 文件生成逻辑,export PATH="./tools:$PATH 只解决命令可发现性——两者都不改变运行时环境:
- composer exec phpunit 仍运行在主进程,autoload 映射未重置
- 即使你把 bin-dir 改成 tools/,只要没删掉旧 vendor/ 并重装,旧链接和旧 autoload 就还在内存里
- 所有通过 require 加载的主项目代码,若隐式依赖 vendor/ 中的类,就会在沙箱进程里报 Class not found
关键点在于:Composer 的 bin 机制只管“怎么放”,不管“怎么跑”。想安全执行,就得亲手切进程、清上下文、验 autoload 文件——没有捷径,也别信配置开关能自动隔离。











