从具有多个对象的表单中保存数据
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
是一个独立的实体,这很好。它有两个关联,每个关联 PatientSample
和 PatientOrder
。 PatientSample
和PatientOrder
也有关联
您的表单创建了一个新的 PatientOrder
,它自动与 Patient
相关联,因为 PatientOrder
是该关联的拥有方,并且由于cascade="PERSIST"
在映射中。
PatientOrder
也自动与 PatientSample
相关联,您可能也将其标记为级联持久化。 但是,据我所知,PatientSample
的 patient
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 Expression 对 PatientOrder
的约束(实体,而不是表单类型):
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 平均不会有太大帮助)
更新:在验证部分混淆了患者订单和患者样本
按照本指南 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
是一个独立的实体,这很好。它有两个关联,每个关联 PatientSample
和 PatientOrder
。 PatientSample
和PatientOrder
也有关联
您的表单创建了一个新的 PatientOrder
,它自动与 Patient
相关联,因为 PatientOrder
是该关联的拥有方,并且由于cascade="PERSIST"
在映射中。
PatientOrder
也自动与 PatientSample
相关联,您可能也将其标记为级联持久化。 但是,据我所知,PatientSample
的 patient
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 Expression 对 PatientOrder
的约束(实体,而不是表单类型):
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 平均不会有太大帮助)
更新:在验证部分混淆了患者订单和患者样本