迁移失败主因是实体定义、数据库状态与配置间的认知偏差,symfony 7.2 因强化类型提示、平台校验及事务默认开启,更易暴露旧项目中的隐性矛盾。

迁移失败通常不是 Doctrine 本身坏了,而是实体定义、数据库状态、配置三者之间出现了“认知偏差”——Doctrine 看到的和它以为的不一致。Symfony 7.2 没有引入迁移逻辑的根本性变更,但对 PHP 8.2+ 类型提示、严格模式、以及 PostgreSQL/MySQL 平台行为的校验更敏感,容易暴露旧项目里长期被忽略的配置矛盾。
doctrine:migrations:diff 生成空迁移或删 ID 自增
这是最典型的“假失败”:命令跑完了,却没生成文件,或生成了危险的 ALTER COLUMN id DROP DEFAULT。
- 根本原因是 Doctrine 实体元数据与数据库实际结构不匹配,尤其在 PostgreSQL 中:
@ORM\Column(options={"autoincrement":true})完全无效,且会干扰 diff 判断 - 如果你用了
strategy="SEQUENCE"但没写sequenceName,Doctrine 第一次猜建序列,第二次发现“实体没声明序列但库表有默认值”,就判定为“冗余”,反向移除 - Symfony 7.2 + Doctrine 5.2+ 默认启用更严格的平台比对,对
GENERATED BY DEFAULT AS IDENTITY和SERIAL的识别更准,反而让旧的模糊配置露馅
执行 migrate 时提示 “Table already exists” 或 “Column doesn’t exist”
这不是 SQL 错了,是 Doctrine 的迁移状态表 doctrine_migration_versions 和你本地文件、数据库结构三者脱节。
- 手动改过数据库(比如用 psql 直接
ALTER TABLE),但没同步更新迁移状态表,下次migrate就会重放已执行过的 SQL - 多人协作时,有人合并了迁移文件但没执行,你本地
status显示 “Not executed”,而数据库其实已有对应结构,导致冲突 - Symfony 7.2 默认开启
transactional: true,但如果某个up()方法里混写了$this->addSql()和$schema->createTable(),事务回滚边界可能异常,残留半截状态
连接报错 “could not translate host name” 或 “Connection refused”
这和迁移逻辑无关,但会卡在 diff 前——Doctrine 根本连不上库,自然无法比对。
- 本地开发用 Docker 跑 PostgreSQL,但 Symfony 在宿主机运行,
DATABASE_URL里写的host: db或host: postgres是容器内服务名,宿主机 DNS 解析不了 -
.env里DATABASE_URL密码含@、/、:没做 URL 编码,比如pass@123必须写成pass%40123 - Symfony 7.2 默认启用
pdo_pgsql或pdo_mysql扩展检查,如果 PHP 模块没装(如 Ubuntu 上漏装php-pgsql),会静默失败或报模糊错误
迁移文件里出现奇怪的 $this->addSql() 或 schema 变更顺序错乱
这是 Doctrine 对实体变更的推导逻辑被干扰了,常见于混合使用注解 + 属性类型声明 + YAML 映射。
- PHP 8.2+ 支持
private int $id,但 Doctrine 若同时看到@ORM\Column(type="integer")和原生类型,可能优先信后者,忽略注解里的options或nullable - 实体用了
#[ORM\Embedded]或#[ORM\JoinColumn],但关联表还没生成迁移,diff会试图“补全”缺失字段,结果顺序颠倒、外键引用失效 - 多数据库环境(
--em=tenant)下忘了加参数,diff默认只扫default连接,其他库的变更直接被无视
真正麻烦的从来不是某一行报错,而是 Doctrine 在“自作聪明”地修复它以为的问题——而那个问题其实是你三年前随手写的注解和一次手抖的 psql 命令共同埋下的。动手前先跑 doctrine:migrations:status 和 doctrine:schema:validate,比硬着头皮改迁移文件有用得多。











