Composer脚本执行时应使用PHP原生JSON操作安全修改配置:先json_decode读取并容错解析,修改数组后用json_encode(带JSON_UNESCAPED_UNICODE和JSON_PRETTY_PRINT)写入,写入前备份原文件,写入后用json_last_error校验,失败则自动还原;禁用sed等文本替换,因其无法可靠处理跨行、特殊字符及格式变化。

Composer脚本执行时如何安全修改配置文件
Composer 的 scripts 不是 shell 脚本沙盒,它默认在项目根目录执行,且环境变量、工作路径、权限都受当前用户和 Composer 运行方式(如全局 vs 本地)影响。直接用 sed 或 php -r 写配置文件容易覆盖内容、破坏 JSON 格式或忽略 UTF-8 BOM,尤其当配置含注释或非标准结构时。
实操建议优先使用 PHP 原生 JSON 操作,而非文本替换:
- 用
json_decode(file_get_contents($file), true)读取,确保解析容错(如尾逗号、多余空格) - 修改数组后,用
json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)输出,避免中文变 \uXXXX - 写入前先
file_put_contents($file . '.bak', $original_content)备份原始文件 - 写入后用
json_last_error()校验结果是否合法,失败则自动还原备份
在 composer.json 中定义可复用的重写脚本
不要把逻辑塞进一行 php -r,而是拆成独立 PHP 文件并注册为脚本命令。这样便于调试、单元测试和参数化。
例如,在项目根目录新建 bin/update-config.php:
#!/usr/bin/env php
<?php
$configPath = __DIR__ . '/../config/app.php';
if (!file_exists($configPath)) {
die("Config file not found: $configPath\n");
}
$data = json_decode(file_get_contents($configPath), true) ?: [];
$data['debug'] = ($_SERVER['APP_ENV'] ?? 'dev') === 'dev';
$data['version'] = $_SERVER['COMPOSER_ROOT_VERSION'] ?? 'dev';
file_put_contents($configPath . '.bak', file_get_contents($configPath));
if (false === file_put_contents($configPath, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n")) {
file_put_contents($configPath, file_get_contents($configPath . '.bak'));
die("Failed to write config\n");
}
然后在 composer.json 的 scripts 中注册:
使用ydata-profiling(前身为pandas-profiling)生成全面的数据质量报告,包含相关性分析、缺失值模式和基数检测。导出交互式HTML仪表板和JSON摘要。
"scripts": {
"post-install-cmd": [
"php bin/update-config.php"
],
"post-update-cmd": [
"php bin/update-config.php"
]
}
为什么不能用 shell 脚本直接 sed 替换 version 字段
因为 sed -i 's/\"version\":.*$/\"version\": \"1.2.3\",/' config.json 类操作极不可靠:
- JSON 值可能跨行(尤其开启
JSON_PRETTY_PRINT后) - 字段顺序不固定,
version可能在末尾无逗号,也可能在中间需补逗号 - 值中含斜杠、引号、反斜杠时,
sed正则易崩溃或误替换 - Windows 下
sed行为与 GNU sed 不一致,CI 环境常出错
真正稳定的方式是走 JSON 解析链:读 → 改 → 校验 → 写。哪怕多几行 PHP,也比花半天调 sed 正则强。
注意 post-root-package-install 钩子的执行时机
这个钩子只在 create-project 时触发一次,且此时 vendor/ 尚未生成,autoload 未就绪,无法 require 第三方包。如果脚本依赖 symfony/filesystem 或 spatie/json-api,会直接 fatal error。
可行方案只有两个:
- 纯原生 PHP 实现(如上例),不引入任何外部依赖
- 改用
post-create-project-cmd,它在vendor/初始化完成后执行,可安全 require 自定义类
多数人卡在这里——以为钩子名带 “root” 就能最早运行,结果发现连 file_get_contents 都报错,其实是路径或 autoload 问题。

















