Doctrine Migrations 是管理数据库 schema 版本的基础设施,通过 diff 自动生成带时间戳的 PHP 迁移类,仅变更表、字段、索引等结构,不管理数据;回滚依赖手动编写的 down() 方法与事务保障。

Doctrine Migrations 不是用来“执行 SQL 脚本”的工具,而是把数据库结构变更变成可版本化、可协作、可回溯的 PHP 类;它不管理数据内容,只管表、字段、索引、外键这些 schema 层东西。
生成迁移文件:改完 Entity 再 diff,别手写
绝大多数迁移不该手动创建,而应靠 doctrine:migrations:diff 自动推导。你改了 Entity 类(比如加 $email 属性并配上 @ORM\Column(type="string", length=255)),再运行这条命令,它会对比当前数据库实际结构和 Entity 映射,生成带时间戳的迁移类(如 Version20260722103000.php)。
常见错误现象:
- 运行
doctrine:migrations:diff后提示 “No changes detected”:检查 Entity 是否漏了@ORM\Entity或@ORM\Table注解;或数据库里已有该字段但 Entity 没同步 - 生成空迁移文件:确认是否在正确 EntityManager 上执行(多库项目需加
--em=other_manager) - 用了已废弃的
doctrine:generate:entities命令(Symfony 5.4+ 已移除),导致映射元数据没刷新
执行迁移前必须验证三件事
直接跑 doctrine:migrations:migrate 很危险,尤其在生产环境。先做这三步:
- 运行
doctrine:migrations:status,确认新生成的迁移出现在 “Available migrations” 列表里,且 “Executed migrations” 还没包含它 - 打开刚生成的迁移文件,检查
up()方法里有没有手写的$this->addSql()—— 如果有,确认语句是否兼容目标数据库(例如 MySQL 8 的JSON类型写法和 PostgreSQL 不同) - 确认
config/packages/doctrine_migrations.yaml中transactional: true已启用(默认是 true,但老项目可能被关掉;v3+ 配置里对应键名是all_or_nothing: true)
安全回滚只能靠 down() + 事务,不能指望自动逆向
down() 方法不会由 Doctrine 自动生成,必须你手动写对称逻辑。比如 up() 里用 $schema->getTable('user')->addColumn('email', 'string'),down() 就得写 $schema->getTable('user')->dropColumn('email')。
容易踩的坑:
- 删列前没检查外键依赖:其他表如果引用了这个列,
dropColumn()会直接报错;先查SHOW CREATE TABLE或用 DBAL SchemaManager 检查约束 - 在
down()里用TRUNCATE TABLE:MySQL 下它不进事务,一旦失败无法回滚;改用$this->addSql('DELETE FROM foo WHERE 1=1') - 修改列类型(如从
string(255)改成text)后,在down()中无法无损还原长度;真要支持回滚,up()就别改类型,或显式用changeColumn()并指定长度 - 误用
doctrine:migrations:rollback:它只执行最后一次迁移的down(),跳过中间依赖项,极易导致状态错乱;应该统一用doctrine:migrations:migrate prev或具体版本号(如Version20260722103000)
第三方表干扰时 diff 和回滚都会出错
如果你的数据库里混有 WordPress、Shopify 或其他系统建的表,doctrine:migrations:diff 可能误把它们当成本项目实体,生成错误的 DROP 或 ALTER 语句;回滚时也可能误删。
解决办法是在 Doctrine DBAL 配置中加 schema_filter,例如只处理你的表:
doctrine:
dbal:
schema_filter: '~^app_'
这个正则会过滤掉所有不以 app_ 开头的表,让 diff 和迁移只作用于你控制的 schema 范围内。
最常被忽略的一点:migration 文件本身是代码,不是配置;它一旦提交到 Git,就代表一个不可变的结构变更契约——哪怕你后来发现逻辑错了,也不能直接改已提交的迁移文件,而应新建一次迁移来修复。否则团队协作或重装数据库时,状态必然撕裂。

















