从具有多个对象的表单中保存数据

Saving data from form with multiple objects

按照本指南 https://symfony.com/doc/current/form/embedded.html 我创建了一个包含三个实体的表单。患者、患者样本和患者订单。当我提交表单时,出现错误 patient_id 不能为空。

我一直在尝试各种方法在 PatientOrderController 中创建一个新的 Patient 对象并在那里获取数据然后调用 $entityManager->persist($patient) 但在使用表单数据填充新的 Patient 对象时遇到了问题.

患者订单类型

public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('accession_number')
            ->add('patient', PatientType::class)
            ->add('patientSample', PatientSampleType::class);
    }

PatientOrderController

    {
        $patientOrder = new PatientOrder();

        $form = $this->createForm(PatientOrderType::class, $patientOrder);

        $form->handleRequest($request);

        if($form->isSubmitted() && $form->isValid()){

            $patientOrder = $form->getData();

            $entityManager = $this->getDoctrine()->getManager();
            $entityManager->persist($patientOrder);
            $entityManager->flush();

            return $this->redirectToRoute('patient_order_index');
   }

患者class

class Patient
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    public $id;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\PatientOrder", 
     *   mappedBy="patient")
     */
    public $patientOrders;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\PatientSample", 
     * mappedBy="patient")
     */
    public $patientSamples;

患者订单Class

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Patient", 
     *  inversedBy="patientOrders", cascade={"persist"})
     * @ORM\JoinColumn(nullable=false)
     * @Assert\Valid()
     */
    protected  $patient;


/**
     * @ORM\ManyToOne(targetEntity="App\Entity\PatientSample", 
     *inversedBy="patientOrders", cascade={"persist"})
     * @ORM\JoinColumn(nullable=false)
     * @Assert\Valid()
     */
    private $patientSample;

患者样本class

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Patient", 
     * inversedBy="patientSamples")
     * @ORM\JoinColumn(nullable=false)
     */
    private $patient;

   /**
     * @ORM\OneToMany(targetEntity="App\Entity\PatientOrder", 
     * mappedBy="patientSample")
     */
    private $patientOrders;

我的期望是,当用户单击保存按钮时,它将向数据库中的 3 个单独表中插入 3 个新行。相反,我得到的是 SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'patient_id' cannot be null

好的。所以问题如下:

Patient是一个独立的实体,这很好。它有两个关联,每个关联 PatientSamplePatientOrderPatientSamplePatientOrder也有关联

您的表单创建了一个新的 PatientOrder,它自动与 Patient 相关联,因为 PatientOrder 是该关联的拥有方,并且由于cascade="PERSIST" 在映射中。

PatientOrder 也自动与 PatientSample 相关联,您可能也将其标记为级联持久化。 但是,据我所知,PatientSamplepatient property/association 不会自动设置 。那至少是一个的问题,如果不是一个的问题。 - 幸运的是,这是一个小问题,因为它只会在你的依赖图中创建一个三角形,但是 - 因为 Patient 是独立的,所以它不会创建一个循环(那将是一个大问题)。但是,您的表单不容易 能够设置这些类型的依赖关系,尤其是因为它们并不明显。从技术上讲,该样本可能属于另一名患者......(请参阅答案末尾的验证评论)

简单的解决方案:明确设置

可能有几种解决方案,最简单的,但不是最好的:

    if($form->isSubmitted() && $form->isValid()){

        $patientOrder = $form->getData();
        // NEW! However, I don't know if this violates the Law of Demeter:
        $patientOrder->getSample()->setPatient($patientOrder->getPatient());

        $entityManager = $this->getDoctrine()->getManager();
        $entityManager->persist($patientOrder);
        $entityManager->flush();

        return $this->redirectToRoute('patient_order_index');
    }

更简洁的方法是在表单事件的某个地方执行此操作...

添加 form event 处理程序(代替)

您应该将 PatientOrderType 代码改写成这样:

// be aware you need to set the following use clauses:
// use Symfony\Component\Form\FormEvent;
// use Symfony\Component\Form\FormEvents;

$builder
   // ...
   ->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) {
         $patientOrder = $event->getData(); // view data, i.e. your objects
         $patientSample = $patientOrder->getPatientSample();
         if(!$patientSample->getPatient()) { 
             $patientSample->setPatient($patientOrder->getPatient());
         }
   })
;

这种方法有点温和。如果 $patientSample 已经分配了 不同的 患者(表格可以 pre-filled/pre-set 与实体),您也可能会抛出异常。但是,这种方法将设置一个缺失的患者,从而避免留下 PatientSample 而没有 Patient.

的形式

为什么要在表单事件中执行此操作而不是直接在控制器中设置?因为这样表单类型可以重复使用而无需记住 "oh, i have to explicitly set the person on the PersonSample with this form type".

奖励:添加验证

for validating that a patient sample belongs to a patient order for the same patient, you could add the following ExpressionPatientOrder 的约束(实体,而不是表单类型):

use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(...)
 * @Assert\Expression("this.getPatientSample().getPatient() == this.getPatient()",
      message="PatientOrder's patient must be PatientSample's patient")
 */
class PatientOrder {...}

或回调约束,基本上做同样的事情......实际上在注释中编写代码感觉很奇怪(IDE 平均不会有太大帮助)

更新:在验证部分混淆了患者订单和患者样本