ThinkPHP 6.0中应通过迁移(Migrate)与填充(Seed)实现数据库结构和测试数据的版本化管理:先安装对应版本扩展、注册命令并执行migrate:install初始化状态表;再用migrate:create生成时间戳命名的迁移文件,于change()中定义users表结构;运行migrate:run建表后验证字段与索引;接着安装Faker、创建UserSeeder类,在run()中插入10条中文测试数据;最后通过seed:run执行填充并核对数据。

想在ThinkPHP 6.0项目里安全地新增用户表、给订单表加字段,又怕改错数据库导致线上故障?不手动写SQL、不靠记忆同步多个环境,而是用版本化方式管理结构变更和测试数据——这就是迁移(Migrate)与填充(Seed)要解决的实际问题。
第一步:装扩展、注册命令、初始化状态表
执行php think migrate:run报“Command not defined”不是拼错了,是命令压根没加载进来。先确认是否已安装topthink/think-migration:TP6.0.8+必须用^3.0,TP6.0.0–6.0.7只能用^2.0。
安装后打开config/console.php,检查'commands'数组里有没有'\think\migration\Command::class'——没有就手动加进去,漏掉这行,所有migrate命令都无效。
接着运行php think migrate:install。这一步会创建think_migrations表,用来记录哪些迁移文件已执行。如果跳过它直接跑migrate:run,会提示“No migrations to run”,哪怕你migration文件已经写好了。
立即学习“PHP免费学习笔记(深入)”;
【注意】上线部署时若用了composer install --no-dev,而该包在require-dev里,命令会彻底消失——生产环境也得留着它。
第二步:创建并编写用户表迁移文件
在项目根目录执行:php think migrate:create User。它会在database/migrations/下生成一个类似20260608210000_user.php的文件,前14位是当前时间戳,命名错误会导致扫描失败。
打开这个文件,删掉空的change()方法,改写为:
public function change(): void {
$table = $this->table('users');
$table->addColumn('id', 'biginteger', ['identity' => true, 'primary' => true])
->addColumn('username', 'string', ['limit' => 50, 'default' => ''])
->addColumn('email', 'string', ['limit' => 100])
->addColumn('password', 'string', ['limit' => 255])
->addColumn('status', 'tinyint', ['default' => 1])
->addColumn('created_at', 'datetime')
->addColumn('updated_at', 'datetime')
->addIndex(['email'], ['unique' => true])
->create();
}
这里用change()而非up()+down(),是因为Phinx能自动推断回滚逻辑;但如果你用了addColumn却没配down(),rollback时字段不会被删——别指望框架替你兜底。
第三步:执行迁移并验证建表结果
运行php think migrate:run。成功后控制台会输出类似using environment default和2 migrations ran的提示,同时数据库里会出现users表和think_migrations表。
立刻进MySQL执行DESCRIBE users;,核对字段类型和索引是否与迁移文件一致。尤其注意tinyint字段——ThinkPHP默认不支持enum,但tinyint可模拟状态值,比boolean更可控。
如果执行失败,第一反应不是重试,而是看runtime/log/下的最新日志,里面会明确写出SQL报错位置,比如Unknown column type "enum"或Access denied for user。
第四步:创建Seeder填充测试用户数据
迁移只管结构,数据得靠填充(Seeder)。先安装Faker:composer require fzaninotto/faker(TP6原生不带,必须手动装)。
创建填充类:php think seed:create UserSeeder。它会在database/seeds/下生成UserSeeder.php。
编辑该文件,在run()方法中写入:
public function run(): void {
$faker = Faker\Factory::create('zh_CN');
for ($i = 0; $i Db::name('users')->insert([
'username' => $faker->userName,
'email' => $faker->safeEmail,
'password' => md5('123456'),
'status' => $faker->randomElement([1, 2]),
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
}
【关键】千万别在up()里用Db::insert()填数据——rollback不会删,结构和数据立刻脱节。
第五步:运行填充并确认数据入库
执行php think seed:run。如果提示Command "seed:run" is not defined,说明Seeder命令未注册:打开config/console.php,在'commands'数组末尾加上'\think\seed\Command::class',再重试。
填充成功后,进数据库查SELECT COUNT(*) FROM users;,应返回10条。再随机抽一条SELECT * FROM users LIMIT 1;,确认中文名、邮箱格式、密码哈希值都符合预期。
此时整个流程闭环完成:结构由迁移定义,数据由填充注入,两者分离,各自可独立回滚或重放。



















