升级Doctrine至2.8与PHPUnit至9.5后单元测试出现唯一约束冲突
问题根源
升级Doctrine 2.8后,对象生命周期管理逻辑有调整:当你通过$this->service->findByName重新获取Document实例时,它关联的Category是从数据库加载的新实例(和之前创建的$category不是同一个对象)。由于Document的categories关联设置了cascade={"persist"},Doctrine会将这个新的Category实例识别为未托管的新实体,尝试执行插入操作——但数据库中已经存在同名的Category,因此触发唯一约束错误。
解决方案
方案1:调整关联的cascade配置
如果Category是预先创建的独立实体(不是由Document创建),不需要对categories关联设置cascade={"persist"},改为只在需要同步外部实体状态时使用cascade={"merge"}:
/** * @ORM\ManyToMany(targetEntity="Entity\Category", cascade={"merge"}) */ protected $categories;
若之前添加cascade={"persist"}是为了支持创建Document时同步创建Category的场景,可以保留persist,但关联已存在Category时,需确保该实体处于EntityManager托管状态。
方案2:确保关联的Category处于托管状态
重新获取Document后,将其关联的Category替换为EntityManager托管的实例:
$document = $this->service->findByName('My new document'); // 获取关联Category的ID,重新加载托管实例 $categoryId = $document->getCategories()->first()->getId(); $managedCategory = $this->entityManager->find(Category::class, $categoryId); // 替换Document中的关联 $document->getCategories()->clear(); $document->addAddOn($managedCategory); $document->setAuthor('Some person'); $this->entityManager->persist($document); $this->entityManager->flush();
方案3:统一service与测试用的EntityManager实例
检查$this->service的findByName方法是否使用了测试中注入的同一EntityManager实例。若service使用独立的EntityManager,会导致加载的实体处于非托管状态,触发不必要的persist操作,需确保两者共享同一实例。
方案4:优化关联方法,处理非托管实体
修改Document的addAddOn方法,避免重复添加并自动合并非托管实体:
/** * @param Category $category * * @return Document */ public function addAddOn(Category $category) { // 避免重复添加 if (!$this->categories->contains($category)) { // 合并非托管实体到当前EntityManager if (!$this->entityManager->contains($category)) { $category = $this->entityManager->merge($category); } $this->categories[] = $category; } return $this; }
注:需根据项目依赖注入方式,确保Document类能获取到EntityManager实例。
内容的提问来源于stack exchange,提问作者mirindaunicorn

