我猜这是一个 Doctrine 错误(我已经在 JIRA 问题跟踪器上提交了一个问题),但如果这只是用户错误,我决定在这里发布。
概要
在某些情况下,使用由 2 个外键和一个元数据字段组成的复合主键将实体集合保存在连接表中会失败。代码基于此处的说明:Doctrine docs
问题详情
SUCCESS:当 FOREIGN KEY 1 在要持久化的集合中的项目之间相同,并且 FOREIGN KEY 2 在任何现有 PRIMARY KEY 中大于 FOREIGN KEY 2 时,集合中的实体和相关实体将正确持久化:
示例:GPA“add val below”存在并具有评估值
{"assessment":6,"value":4}
我们将尝试添加一个新的评估值,其中assessment_id > GPA“add val below”的任何现有评估值请求有效载荷:
{"name":"add val below","courses":[],"assessmentValues":[{"assessment":6,"value":4},{"assessment":7,"value":3}]}
调试日志:
[2013-12-31 11:48:48] app.INFO: GPA ID PRESAVE 在控制器中:9 [] [] [2013-12-31 11:48:48] app.INFO:保存在控制器评估 VAL 评估 ID:7 [] [] [2013-12-31 11:48:48] app.INFO: PRESAVE 在控制器评估 VAL POINTS:3 [] [] [2013-12-31 11:48:48] app.INFO: GPA ID PRESAVE 在控制器中:9 [] [] [2013-12-31 11:48:48] app.INFO:保存在控制器评估 VAL 评估 ID:6 [] [] [2013-12-31 11:48:48] app.INFO: PRESAVE 在控制器评估 VAL POINTS:4 [] [] [2013-12-31 11:48:48] 教义.调试:“开始交易”[][] [2013-12-31 11:48:48] 教义.调试:插入 gpa_assessment_value (point_value,grade_point_average_id,assessment_id) 值 (?, ?, ?) {"1":3,"2":"9"," 3":"7"} [] [2013-12-31 11:48:48] 教义。调试:更新 gpa_assessment_value SET point_value = ? 在哪里grade_point_average_id =?AND 评估 ID = ? [4,9,6] [] [2013-12-31 11:48:48] 学说.DEBUG:“提交”[][]
FAILURE:当集合中的项之间的 FOREIGN KEY 1 相同,并且 FOREIGN KEY 2 小于任何现有的 FOREIGN KEY 2 时,工作单元会尝试插入现有实体并且不对新实体进行操作。
示例:GPA“add val above”存在并具有评估值
{"assessment":8,"value":2}
我们将尝试添加一个新的评估值,其中assessment_id < GPA“add val above”的任何现有评估值请求有效载荷:
{"name":"add val above","courses":[],"assessmentValues":[{"assessment":6,"value":4},{"assessment":8,"value":2}]}
调试日志:
[2013-12-31 11:53:59] app.INFO: GPA ID PRESAVE IN CONTROLLER:10 [] [] [2013-12-31 11:53:59] app.INFO:保存在控制器评估 VAL 评估 ID:8 [] [] [2013-12-31 11:53:59] app.INFO: PRESAVE 在控制器评估 VAL POINTS:2 [] [] [2013-12-31 11:53:59] app.INFO: GPA ID PRESAVE IN CONTROLLER:10 [] [] [2013-12-31 11:53:59] app.INFO:保存在控制器评估 VAL 评估 ID:6 [] [] [2013-12-31 11:53:59] app.INFO: PRESAVE 在控制器评估 VAL POINTS:4 [] [] [2013-12-31 11:53:59] 学说。调试:“开始交易”[][] [2013-12-31 11:53:59]教义.DEBUG:插入gpa_assessment_value(point_value,grade_point_average_id,assessment_id)值(?,?,?){“1”:2,“2”:“10”, 3":"8"} [] [2013-12-31 11:53:59] 教义.调试:“回滚”[][] [2013-12-31 11:53:59] request.CRITICAL:未捕获的 PHP 异常 Doctrine\DBAL\DBALException:“执行'INSERT INTO gpa_assessment_value (point_value,grade_point_average_id,assessment_id) VALUES (?, ?, ?) ' 带参数 [2, "10", "8"]: SQLSTATE [23505]:唯一违规:7 错误:重复键值违反唯一约束“gpa_assessment_value_pkey”
代码
迁移.sql
创建表评估 ( id bigserial NOT NULL, scale_id bigint 非空, 标题 varchar NOT NULL, 传递布尔值 NOT NULL, 排名int, 主键(id) ); 创建表评估_规模 ( id bigserial NOT NULL, 名称 varchar NOT NULL, 主键(id) ); -- ... 创建表grade_point_average ( id bigserial NOT NULL, 名称 varchar NOT NULL, additional_credit_allowance numeric(4, 2), 主键(id) ); -- ... 创建表 gpa_assessment_value ( grade_point_average_id bigint 非空, 评估id bigint 非空, point_value numeric(4, 2) NOT NULL, 主键(assessment_id,grade_point_average_id), FOREIGN KEY (assesment_id) REFERENCES 评估, 外键 (grade_point_average_id) 参考 Grade_point_average );
模型/GradePointAverage.php
命名空间 MyApp\Model; 使用 Doctrine\ORM\Mapping\Entity; 使用 Doctrine\ORM\Mapping\Id; 使用 Doctrine\ORM\Mapping\GeneratedValue; 使用 Doctrine\ORM\Mapping\Column; //... 使用 Doctrine\Common\Collections\Collection; 使用 Doctrine\Common\Collections\ArrayCollection; 使用 MyApp\Util\ConstructorArgs; 使用 MyApp\Model\GradePointAverage\AssessmentValue; // ... /** * @Entity("MyApp\Repository\GradePointAverageRepository") */ 班级 GradePointAverage { 使用 ConstructorArgs; /** * @ID * @GeneratedValue * @Column(type="bigint") * * @var 整数 */ 私人 $id; // ... /** * @OneToMany(targetEntity="MyApp\Model\GradePointAverage\AssessmentValue", mappedBy="gradePointAverage", cascade="persist") * * @var 集合 */ 私人 $assesmentValues; // ... /** * @param 数组 $args */ 公共函数 __construct(数组 $args = []) { $this->assessmentValues = new ArrayCollection; // ... $this->handleArgs($args); } // ... /** * @return 集合 */ 公共函数 getAssessmentValues() { 返回 $this->assessmentValues; } /** * @param ArrayCollection $assessmentValues */ 公共函数 setAssessmentValues(ArrayCollection $assessmentValues) { $this->assessmentValues = $assessmentValues; } /** * @param AssessmentValue $assessmentValue */ 公共函数 addAssessmentValue(AssessmentValue $assessmentValue) { $this->assessmentValues->add($assessmentValue); } /** * @param AssessmentValue $assessmentValue */ 公共函数 removeAssessmentValue(AssessmentValue $assessmentValue) { $this->assessmentValues->removeElement($assessmentValue); } // ... }
模型/GradePointAverage/AssessmentValue.php
命名空间 MyApp\Model\GradePointAverage; 使用 Doctrine\ORM\Mapping\Entity; 使用 Doctrine\ORM\Mapping\Table; 使用 Doctrine\ORM\Mapping\Column; 使用 Doctrine\ORM\Mapping\Id; 使用 Doctrine\ORM\Mapping\GeneratedValue; 使用 Doctrine\ORM\Mapping\ManyToOne; 使用 Doctrine\ORM\Mapping\JoinColumn; 使用 MyApp\Model\GradePointAverage; 使用 MyApp\Model\Assessment; 使用 MyApp\Util\ConstructorArgs; /** * @Entity("MyApp\Repository\GradePointAverage\AssessmentValueRepository") * @Table("gpa_assessment_value") */ 类评估值 { 使用 ConstructorArgs; /** * @ID * @ManyToOne(targetEntity="MyApp\Model\GradePointAverage") */ 私人 $gradePointAverage; /** * @ID * @ManyToOne(targetEntity="MyApp\Model\Assessment") */ 私人$评估; /** * @Column("point_value") * * @var 浮动 */ 私人价值; /** * @param 数组 $args */ 公共函数 __construct(数组 $args = []) { $this->handleArgs($args); } /** * @return GradePointAverage */ 公共函数 getGradePointAverage() { 返回 $this->gradePointAverage; } /** * @param GradePointAverage $gradePointAverage */ 公共函数 setGradePointAverage(GradePointAverage $gradePointAverage) { $this->gradePointAverage = $gradePointAverage; } /** * @return 评估 */ 公共函数 getAssessment() { 返回 $this-> 评估; } /** * @param 评估 $assesment */ 公共函数 setAssessment(Assessment $assessment) { $this->评估 = $评估; } /** * @return 浮点数 */ 公共函数 getValue() { 返回 $this-> 值; } /** * @param 浮动 $value */ 公共函数 setValue($value) { $this->value = $value; } /** * @return 评估量表 */ 公共函数 getAssessmentScale() { 返回 $this->assessment->getScale(); } }
模型/评估.php
命名空间 MyApp\Model; 使用 Doctrine\ORM\Mapping\Entity; 使用 Doctrine\ORM\Mapping\Id; 使用 Doctrine\ORM\Mapping\GeneratedValue; 使用 Doctrine\ORM\Mapping\Column; 使用 Doctrine\ORM\Mapping\ManyToOne; 使用 MyApp\Model\Assessment\Scale; 使用 MyApp\Util\ConstructorArgs; /** * @Entity("MyApp\Repository\AssessmentRepository") */ 班级评估 { 使用 ConstructorArgs; /** * @ID * @GeneratedValue * @Column(type="bigint") * * @var 整数 */ 私人 $id; // ... /** * @param 数组 $args */ 公共函数 __construct(数组 $args = []) { $this->handleArgs($args); } /** * @return 整数 */ 公共函数 getId() { 返回 $this->id; } // ... }
存储库/GradePointAverageRepository.php
命名空间 MyApp\Repository; 使用 Doctrine\ORM\EntityRepository; // ... 使用 MyApp\Model\GradePointAverage; 类 GradePointAverageRepository 扩展 BaseRepository 实现 GradePointAverageRepositoryInterface { // ... /** * @param GradePointAverage $gradePointAverage */ 公共函数保存(GradePointAverage $gradePointAverage) { $this->getEntityManager()->persist($gradePointAverage); $this->getEntityManager()->flush(); } }
存储库/GradePointAverage/AssessmentValueRepository.php
命名空间 MyApp\Repository\GradePointAverage; 使用 Doctrine\ORM\EntityRepository; 使用 MyApp\Model\GradePointAverage\AssessmentValue; 类 AssessmentValueRepository 扩展了 EntityRepository { /** * @param AssessmentValue $assessmentValue */ 公共功能保存(评估值$评估值) { $this->getEntityManager()->persist($assessmentValue); $this->getEntityManager()->flush(); } }
经理/GradePointAverageManager.php
命名空间 MyApp\Manager; 使用 InvalidArgumentException; 使用 Symfony\Component\Validator\ValidatorInterface; 使用 JMS\DiExtraBundle\Annotation\Service; 使用 JMS\DiExtraBundle\Annotation\InjectParams; 使用 JMS\SecurityExtraBundle\Annotation\PreAuthorize; 使用 Knp\Component\Pager\Pagination\PaginationInterface; 使用 MyApp\Repository\GradePointAverageRepository; 使用 MyApp\PaginationFactory\GradePointAveragePaginationFactory 接口; 使用 MyApp\Model\GradePointAverage; /** * @Service("grade_point_average_manager") */ 类 GradePointAverageManager { /** * @var GradePointAverageRepository */ 私人 $gradePointAverageRepository; /** * @var GradePointAveragePaginationFactoryInterface */ 私人 $gradePointAveragePaginationFactory; /** * @var 验证器接口 */ 私人 $validator; /** * @InjectParams * * @param GradePointAverageRepository $gradePointAverageRepository * @param GradePointAveragePaginationFactoryInterface $gradePointAveragePaginationFactory * @param ValidatorInterface $validator */ 公共函数 __construct( GradePointAverageRepository $gradePointAverageRepository, GradePointAveragePaginationFactoryInterface $gradePointAveragePaginationFactory, 验证器接口 $validator ) { $this->gradePointAverageRepository = $gradePointAverageRepository; $this->gradePointAveragePaginationFactory = $gradePointAveragePaginationFactory; $this->validator = $validator; } /** * @PreAuthorize("isAllowedToManageTheGradePointAverage(#gradePointAverage)") * @param GradePointAverage $gradePointAverage * @throws InvalidArgumentException */ 公共函数保存(GradePointAverage $gradePointAverage) { $violationList = $this->validator->validate($gradePointAverage); if ($violationList->count()) { 抛出新的 InvalidArgumentException; } $this->gradePointAverageRepository->save($gradePointAverage); } }
控制器/GradePointAverageController.php
命名空间 MyApp\Controller; 使用 Symfony\Component\HttpFoundation\Request; 使用 Symfony\Component\HttpFoundation\Response; 使用 Symfony\Component\HttpKernel\Log\LoggerInterface; 使用 Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 使用 Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; 使用 Doctrine\Common\Collections\ArrayCollection; 使用 FOS\RestBundle\View\View; 使用 JMS\DiExtraBundle\Annotation\Service; 使用 JMS\DiExtraBundle\Annotation\InjectParams; 使用 JMS\SecurityExtraBundle\Annotation\PreAuthorize; 使用 Knp\Component\Pager\Pagination\PaginationInterface; 使用 MyApp\Manager\GradePointAverageManager; 使用 MyApp\Model\GradePointAverage; 使用 MyApp\Model\GradePointAverage\AssessmentValue; /** * @Service("grade_point_average_controller", parent="app.controller.abstract") * @Route("/gpa", service="grade_point_average_controller") */ 类 GradePointAverageController 扩展 BaseController { /** * @var GradePointAverageManager */ 私人 $gradePointAverageManager; 私人 $logger; /** * @InjectParams * * @param GradePointAverageManager $gradePointAverageManager * @param LoggerInterface $logger */ 公共函数 __construct(GradePointAverageManager $gradePointAverageManager, LoggerInterface $logger) { $this->gradePointAverageManager = $gradePointAverageManager; $this->logger = $logger; } // ... /** * @Route("/{id}", name="gpa.edit", requirements={"id" = "\d+"}) * @Method("PUT") * * @param 请求 $request * @param GradePointAverage $gpa * @return 查看 */ 公共函数editAction(请求$request,GradePointAverage $gpa) { $form = $this->formFactory->createNamed(null, 'gpa', $gpa, [ '方法' => 'PUT', ]); $form->handleRequest($request); foreach ($gpa->getAssessmentValues() as $av) { $this->logger->info('GPA ID 在控制器中预验证:'.$gpa->getId()); $this->logger->info('在控制器评估 VAL 评估 ID 中预验证:'.$av->getAssessment()->getId()); $this->logger->info('在控制器评估 VAL 点中预验证:'.$av->getValue()); } /* // 尝试颠倒集合的顺序,看看是否有帮助 $assessmentVals = $gpa->getAssessmentValues()->toArray(); $reversed = array_reverse($assessmentVals); $reversedColl = new ArrayCollection($reversed); $gpa->setAssessmentValues($reversedColl); */ if ($form->isValid()) { foreach ($gpa->getAssessmentValues() as $av) { $this->logger->info('GPA ID PRESAVE IN CONTROLLER:'.$gpa->getId()); $this->logger->info('PRESAVE IN CONTROLLER ASSESSMENT VAL ASSESSMENT ID:'.$av->getAssessment()->getId()); $this->logger->info('PRESAVE IN CONTROLLER ASSESSMENT VAL POINTS:'.$av->getValue()); } $this->gradePointAverageManager->save($gpa); 返回新视图($gpa,204); } 返回新视图($form); } // ... }