Symfony 5.4: 阻止Doctrine更新空WorkPlanningDay对象
问题:Symfony 5.4中阻止Doctrine更新未选中的关联实体为NULL
问题背景
在Symfony 5.4项目中,提交表单时要求在WorkPlanningDay与WorkPlanningHour中二选一,选择对应选项则向对应数据表插入数据。但提交WorkPlanningHour时,Doctrine仍会执行将WorkPlanningDay字段更新为NULL的SQL语句,尝试用$form->remove()方法解决但未成功。
请求数据
{ "statuses": [6], "work_planning": 1, "work_planning_hour": { "count_jrtt": 1, "end_at": "31-12", "jrtt": true, "lower_hour_jrtt": 35, "nbr_jrtt": 24, "start_at": "01-01", "upper_hour_jrtt": 39 }, "work_planning_period": 0, "working_time_aspect": { "days": ["MON", "TUE", "WED", "THU", "FRI"], "time": 0, "weekly_hours": 35 } }
表单及事件监听器代码
class WorkingTimeParametersRESTType extends AbstractType { /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('work_planning', Field\ChoiceType::class, array( 'property_path' => 'workPlanning', 'required' => true, 'choices' => WorkingTimeParameters::getAllowedWorkPlannings(), )) ->add('work_planning_period', Field\ChoiceType::class, array( 'property_path' => 'workPlanningPeriod', 'required' => false, 'choices' => WorkingTimeParameters::getAllowedWorkPlanningPeriods(), )) ->add('working_time_aspect', WorkingTimeAspectType::class, array( 'property_path' => 'workingTimeAspect', 'required' => true, 'constraints' => array(new Valid()) )) ->add('work_planning_hour', WorkPlanningHourType::class, array( 'property_path' => 'workPlanningHour', 'required' => true, 'constraints' => array(new Valid()) )) ->add('work_planning_day', WorkPlanningDayType::class, array( 'property_path' => 'workPlanningDay', 'required' => true, 'constraints' => array(new Valid()) )) ->add('statuses', Field\CollectionType::class, array( 'entry_type' => CategoryIntegerType::class, 'entry_options' => array( 'category_type' => Category::TYPE_STATUS, ), 'allow_add' => true, 'allow_delete' => true, 'required' => true, 'error_bubbling' => false, )) ->addEventListener(FormEvents::PRE_SET_DATA, array($this, 'onPreSetData')) ->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) { /** @var WorkingTimeParameters $contract */ $parameters = $event->getData(); $form = $event->getForm(); // Remove useless work planning if ($parameters->getWorkPlanning() == Contract::WORK_PLANNING_HOUR) { $parameters->setWorkPlanningDay(null); $form->remove('work_planning_day'); } else if ($parameters->getWorkPlanning() == Contract::WORK_PLANNING_DAY) { $parameters->setWorkPlanningHour(null); $form->remove('work_planning_hour'); } else { dd('rerer'); $form->remove('work_planning_day'); $form->remove('work_planning_hour'); $parameters->setWorkPlanningDay(null); $parameters->setWorkPlanningHour(null); $parameters->setWorkPlanningPeriod(null); } }) ; } /** * @param FormEvent $event */ public function onPreSetData(FormEvent $event) { $rttParameters = $event->getData(); $form = $event->getForm(); // The company can be set only on creation if (!$rttParameters || !$rttParameters->getId()) { $form->add('company', CompanyIntegerType::class, array( 'required' => true, )); } } }
Doctrine执行的SQL语句
INSERT INTO w2d_tm_work_planning_hour (startAt, endAt, jrtt, countJrtt, lowerHourJrtt, upperHourJrtt, nbrJrtt) VALUES ('1970-01-01 00:00:00', '1970-12-31 00:00:00', 1, 1, 35, 39, 24); UPDATE w2d_tm_work_planning_day SET startAt = NULL, endAt = NULL, nbrDays = NULL, nbrJrtt = NULL WHERE id = 1;
解决方案
1. 移除手动设置实体属性为NULL的代码
你当前在SUBMIT事件里调用了$parameters->setWorkPlanningDay(null);,这会让Doctrine跟踪到实体属性的变化,从而生成UPDATE语句。直接删除这行代码,不需要手动将属性设为空。
2. 改用PRE_SUBMIT事件处理字段移除
SUBMIT事件是在表单数据已经映射到实体之后触发的,此时实体属性已经被修改(即使请求中没有对应数据,表单字段的存在会导致属性被设为NULL)。而PRE_SUBMIT事件是在数据映射到实体之前触发的,此时移除不需要的字段,表单就不会处理该字段的映射,实体对应的属性也不会被修改。
修改后的事件监听器代码如下:
->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) { $data = $event->getData(); $form = $event->getForm(); $workPlanning = $data['work_planning'] ?? null; if ($workPlanning == Contract::WORK_PLANNING_HOUR) { // 移除work_planning_day字段,避免表单处理该字段 $form->remove('work_planning_day'); // 从请求数据中移除该字段,防止后续处理干扰 unset($data['work_planning_day']); $event->setData($data); } elseif ($workPlanning == Contract::WORK_PLANNING_DAY) { $form->remove('work_planning_hour'); unset($data['work_planning_hour']); $event->setData($data); } else { $form->remove('work_planning_day'); $form->remove('work_planning_hour'); unset($data['work_planning_day'], $data['work_planning_hour']); $event->setData($data); } })
3. 检查实体关联配置
确保WorkingTimeParameters实体中与WorkPlanningDay、WorkPlanningHour的关联配置正确:
- 关联设置为可选(
nullable=true) - 关闭
orphanRemoval(设为false),避免Doctrine自动删除未关联的实体
示例实体关联配置:
/** * @ORM\OneToOne(targetEntity=WorkPlanningDay::class, mappedBy="workingTimeParameters", cascade={"persist"}, orphanRemoval=false, nullable=true) */ private $workPlanningDay; /** * @ORM\OneToOne(targetEntity=WorkPlanningHour::class, mappedBy="workingTimeParameters", cascade={"persist"}, orphanRemoval=false, nullable=true) */ private $workPlanningHour;
内容的提问来源于stack exchange,提问作者oracle972
相关产品推荐
相关产品推荐

