doctrine migrations 是 symfony 生产环境必需的数据库版本控制基础设施,通过自动生成带时间戳的 php 迁移文件(如 version20260310190500.php)实现结构变更,需先修改实体再执行 doctrine:migrations:diff,迁移执行前须验证状态、检查 sql 兼容性并确保事务开启,回滚依赖手动编写的对称 down() 方法,状态由 doctrine_migration_versions 表维护,严禁手动删表。

Doctrine Migrations 是 Symfony 项目里控制数据库结构版本的默认方式,不是“可选插件”,而是生产环境必须依赖的基础设施。它不靠手动 SQL 或临时脚本,而是把每次表结构变更固化为带时间戳的 PHP 类,靠 doctrine:migrations:migrate 命令驱动执行与回滚。
怎么生成迁移文件:别直接写,先改实体再 diff
新手常误以为要手写 up() 和 down() —— 实际上绝大多数场景应让 Doctrine 自动推导差异。流程是:改 Entity 类(比如加个 $email 属性并配好 @ORM\Column),然后运行:
php bin/console doctrine:migrations:diff
这条命令会对比当前实体映射和数据库实际结构,自动生成一个类似 Version20260310190500.php 的迁移文件到 migrations/ 目录。它生成的 SQL 是安全、幂等的(比如对已存在的字段不会重复 ADD COLUMN)。
- 如果没生成任何内容,检查是否漏了
@ORM\Entity或@ORM\Table注解 - 若提示 “No changes detected”,可能是数据库里已有该字段但实体没同步,或用了
doctrine:generate:entities这类过时命令(Symfony 5.4+ 已弃用) -
doctrine:migrations:diff默认只作用于默认连接;多数据库时需加--em=other_manager
执行迁移前必须确认的三件事
执行 doctrine:migrations:migrate 不是“点一下就完事”,它会真实修改数据库。跳过验证容易炸库。
- 先跑
doctrine:migrations:status:看 “Available migrations” 是否有未执行项,“Executed migrations” 是否包含你刚生成的版本号 - 检查生成的迁移文件里有没有
$this->addSql()手写语句——如果有,确认它是否兼容目标数据库(如 MySQL 8 vs PostgreSQL 的 UUID 写法) - 生产环境务必开启事务保护:确保
config/packages/doctrine_migrations.yaml中transactional: true(默认已是 true,但老项目可能被改过)
特别注意:up() 方法里调用 $schema->createTable() 是安全的,但直接写 $this->addSql('DROP TABLE users') 在 up() 里属于高危操作,除非你明确知道后果。
回滚失败常见原因:down() 方法不是自动反向生成的
很多人以为 doctrine:migrations:rollback 能无脑倒退,结果报错说 down() 里缺字段或表已删。这是因为 Doctrine 不会自动推导 down() 逻辑 —— 它只保证你写的 down() 能把 up() 的效果撤销。
- 如果你在
up()里用$table->addColumn('status', 'string'),那down()就得写$table->dropColumn('status'),不能留空 - 删除整张表?
down()必须显式调用$schema->dropTable('xxx'),否则 rollback 时表还挂着,下次 migrate 又报 “table already exists” - 使用
$this->addSql()写的原生 SQL,down()里也得手写对应逆操作(比如 CREATE → DROP,ALTER → 没有通用逆操作,得按需还原)
真正可靠的回滚,前提是 down() 方法经过本地测试,且和 up() 形成严格对称。
迁移状态存在哪?别手动删表
Doctrine 记录哪些迁移已执行,靠的是数据库里的 doctrine_migration_versions 表(Sylius 等项目可能叫 sylius_migrations)。有人遇到“迁移重复执行”就去删表,这是最危险的操作之一。
- 删表会导致所有迁移记录丢失,下次
migrate会重跑全部历史迁移,极大概率报错(比如重复建表) - 正确做法是用
doctrine:migrations:execute --down [version]单独回退某次,或用--no-interaction --force强制标记某次为已执行(仅调试用) - 表名由配置决定:
storage.table_storage.table_name,默认是doctrine_migration_versions,改名后记得同步检查
迁移的本质是状态机,不是脚本集合。一旦绕过它的状态跟踪机制,后续所有操作都会失去确定性。









