You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 11:40:21