要让 composer create-project 正确使用自定义模板,必须将包设为 "type": "project",并确保根目录含 composer.json 和可运行入口;默认只搜 stable 版本,需加 --stability=dev 或打 tag;post-create-project-cmd 钩子须定义在模板的根 composer.json 中,用于替换占位符等操作。

怎么让 composer create-project 用你自己的模板
核心就一条:你的包必须声明 "type": "project",且根目录下有 composer.json 和可运行的入口结构(比如 public/index.php 或 bin/console)。Composer 不会自动识别“模板”,只认 type 字段和安装后能否直接跑起来。
常见错误是把包设成 "type": "library" 或没写 type —— 这样 create-project 会把它当普通依赖装进 vendor,而不是复制整个项目骨架。
- 确保
composer.json顶层有"type": "project" - 不要在模板里写
"require": { "your-own-template": "*" },这会导致循环依赖 - 如果模板需要替换占位符(如 vendor/name),用
composer create-project --stability=dev --no-interaction配合scripts中的post-create-project-cmd自动处理,而不是靠用户手动改
为什么 composer create-project vendor/name 报错 “Could not find package”
不是包不存在,而是默认只搜 stable 版本,而你的模板包很可能还没打正式 tag,只有 dev-main 或 dev-master 分支。
典型现象:composer show vendor/name 能查到,但 create-project 找不到——因为 show 默认查所有稳定性,create-project 默认只查 stable。
- 加
--stability=dev强制允许开发版:composer create-project vendor/name --stability=dev - 或者在模板包的
composer.json里显式写"minimum-stability": "dev"(不推荐,会影响下游) - 更稳妥的做法:打一个
v1.0.0或dev-maintag,并确保 packagist.org 已同步
post-create-project-cmd 脚本为什么不执行
这个钩子只在目标项目(即新生成的项目)的 composer.json 里定义才生效,不是在模板包自己的 composer.json 里定义。很多人在这儿绕半天。
也就是说:你要让脚手架生成后自动执行清理、重命名、生成密钥等操作,得把 "scripts" 写进模板包的 composer.json 根层级,而不是写在 require 里的某个依赖中。
- 正确位置:
templates/my-skeleton/composer.json的顶层"scripts"字段 - 命令名必须是
"post-create-project-cmd",大小写敏感 - 脚本执行时工作目录是新建项目的根目录,不是模板源码目录
- 别依赖
$PWD或相对路径去读模板里的文件——那些文件已被复制,原始模板不在现场
如何避免模板里硬编码的 vendor/name 在生成后变成错的命名空间
Composer 不会自动替换 PHP 命名空间或配置文件里的字符串。所谓“自动替换”只是某些脚手架工具(如 Laravel Installer)自己实现的逻辑,不是 Composer 原生能力。
如果你的模板里写了 namespace Vendor\Name; 或 APP_NAME=MyProject,生成后它还是原样,不会变成用户指定的名字。
- 最轻量做法:在
post-create-project-cmd脚本里调用sed或php -r批量替换(Linux/macOS 可靠,Windows 需额外处理) - 更健壮做法:模板里用占位符,比如
namespace {VENDOR}\{NAME};,再由脚本替换成实际值 - 别试图用 Composer 的
autoload/psr-4映射来绕过命名空间问题——它只管自动加载,不改源码
真正麻烦的从来不是创建动作本身,而是模板和生成后项目之间的“语义割裂”:Composer 管复制,不管语义一致性。这部分必须自己补全,而且得在不同系统上都跑得稳。


















