【问题标题】:Null values in new entities in symfony 3.4 forms without defaultssymfony 3.4 形式的新实体中的空值,没有默认值
【发布时间】:2019-05-14 21:28:40
【问题描述】:

我有一个实体“管理”,它有一个字段“firstPeriod”。该字段不可为空(无论是在类定义中还是在数据库中),也不应该为空,因为该字段永远不应为空,应用程序将失败。

但是,这个字段没有默认值,因为如果一个不知情的用户只是简单地提交表单而不更改默认值,就会出现混乱。用户必须在这里做出有意识的选择。已进行验证以确保该字段不为空且在可接受的范围内。

当我尝试渲染表单时,formbuilder 组件的“propertyAccessor”会抛出此异常:

类型错误:AppBundle\Entity\Administration::getFirstPeriod()的返回值必须是整数类型,返回null

看起来表单构建器在设置字段之前尝试获取字段的值,这当然会导致上述异常。

如何处理这种情况,以便在不向用户提供默认值的情况下呈现表单?

进一步澄清:Null 是不行的,但我也不能提供任何默认值,用户必须做出有意识的决定。任何直接实例化该实体的开发人员也是如此。它必须在实体被持久化之前提供,但我不能给出默认值,因为如果保留默认值,应用程序将无法运行 13 次中的 12 次。

  • 如果我在实体字段“?int”上允许为空,我实际上是在使一个字段可以为空永远不应该为空
  • 如果我提供默认值,则可能会盲目接受默认值,这会导致应用程序中的错误结果进一步导致大多数用户很难发现。
  • 我已经尝试在formType中设置'empty_data => 0',但无济于事

抱歉下面的混乱,“代码示例”不能很好地处理此代码

我的(截断的)实体:

namespace AppBundle\Entity;

使用 Doctrine\ORM\Mapping 作为 ORM;

/** * 行政 * * @ORM\Table(name="管理") * @ORM\Entity(repositoryClass="AppBundle\Repository\AdministrationRepository") */ 班级管理 { /** * @var 整数 * * @ORM\Column(name="id", type="integer") * @ORM\ID * @ORM\GeneratedValue(strategy="AUTO") */ 私人 $id;

/**
 * @var int
 *
 * @ORM\Column(name="first_period", type="smallint", nullable=false)
 */
private $firstPeriod;

/**
 * Get id.
 *
 * @return int
 */
public function getId()
{
    return $this->id;
}


/**
 * @return int
 */
public function getFirstPeriod(): int
{
    return $this->firstPeriod;
}

/**
 * @param int $firstPeriod
 */
public function setFirstPeriod(int $firstPeriod): void
{
    $this->firstPeriod = $firstPeriod;
}

}

我的(截断的)formType(尽我所能在这里格式化):

    public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('firstPeriod', null, [
            'label' => 'First period'
        ])
    ;
}

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults([
        'data_class' => Administration::class
    ]);
}

public function getBlockPrefix()
{
    return 'app_bundle_administration_type';
}

}

我的控制器:


namespace AppBundle\Controller\Admin;

class AdministrationController extends Controller
{
    public function editAction(
        EntityManager $em,
        Router $router,
        Security $security,
        Session $session,
        LoggerInterface $logger,
        Request $request,
        Administration $administration = null
    ): Response {

        if ($administration === null) {
            $new = true;
            $administration = new Administration();
            $pageTitle = 'New';
        } else {
            $new = false;
            $pageTitle = 'Edit';
        }
        $breadcrumbs->add($crumbs);

        $form = $this->createForm(AdministrationType::class, $administration);

        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {

            /** @var Administration $administration */
            $administration = $form->getData();

            try {
                $em->persist($administration);
                $em->flush();
            } catch (ORMException $e) {
                $logger->critical($e->getMessage());

                $session->getFlashBag()->add('error', 'Database error.');

                if ($new) {
                    return $this->redirectToRoute('administration_new');
                } else {
                    return $this->redirectToRoute(
                        'administration_edit',
                        ['administration' => $administration->getId()]
                    );
                }
            }

            $session->getFlashBag()->add('success', 'Success!');

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

        return $this->render(':Admin/Administration:edit.html.twig', [
            'administrationForm' => $form->createView(),
            'pageTitle' => $pageTitle
        ]);
    }
}

我的验证:

AppBundle\Entity\Administration:
properties:
    firstPeriod:
        - NotBlank:
              message: 'adm.firstperiod.empty'
        - Range:
              min: 1
              max: 13
              minMessage: 'adm.firstperiod.too_low'
              maxMessage: 'adm.firstperiod.too_high'

【问题讨论】:

  • 只需在您的实体中使用默认值。私人 $firstPeriod = 1;无论如何,这是您想做的事情。如果你能帮助它,让一个实体漂浮在无效数据周围是没有意义的。
  • @Cerad 你读过这个问题吗?
  • 当然没看过。随机发布 cmets 更有趣。故意让实体拥有无效数据让我觉得这是一个非常糟糕的主意。但是如果 null 没问题,那么只需将 getFirstPeriod 上的返回类型更改为 ?int。
  • @Cerad Null 不行,但我也不能提供任何默认值,用户必须做出有意识的决定。任何直接实例化该实体的开发人员也是如此。它必须在实体被持久化之前提供,但我不能给出默认值,因为如果保留默认值,应用程序将无法运行 13 次中的 12 次。我将编辑这个问题,希望能更清楚地说明这一点。
  • @Cerad,我希望避免这种情况,当我说我截断了我在此处发布的实体时,我并不是在开玩笑,还有 很多 其他属性和复杂性。我认为这是一件微不足道的事情,我根本不知道修复,比如“使用 ignore_getters 约束”或其他东西:(也许这个问题不像我想象的那么普遍,但它在我们的领域很常见。

标签: symfony symfony-forms


【解决方案1】:

由于 symfony 表单使用属性访问器,正如@Cerad 所说,您可以向表单添加一个未映射的字段,并在表单事件中获取/设置该字段,添加一个特定的方法来获取未初始化的 $first_period ...

示例代码可能是...

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\AdministrationRepository")
 */ 
class Administration
{ 
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()   
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="integer")
     */
    private $first_period;

    public function getId(): ?int
    {
        return $this->id;
    }
  
    public function getFirstPeriod(): int
    {
        return $this->first_period;
    }

    public function setFirstPeriod(int $first_period): self
    {
        $this->first_period = $first_period;

        return $this;
    }

    public function getFirstPeriodOrNull(): ?int
    {
        return $this->first_period;
    }
}

表格

<?php

namespace App\Form;

use App\Entity\Administration;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
  
class AdministrationType extends AbstractType
{ 
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('first_period', null, [
                'mapped' => false,
                'required' => false,
            ])
            ->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) {
                /** @var Administration */
                $a = $event->getData();

                $event->getForm()->get('first_period')->setData($a->getFirstPeriodOrNull());
  
            })
            ->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) {
                $f = $event->getForm()->get('first_period')->getData();

                if (is_int($f)) {
                    /** @var Administration */
                    $a = $event->getData();
                    $a->setFirstPeriod($f);
                }
            });
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Administration::class,
        ]);
    }
}

这在 Symfony 4.2 中运行良好

【讨论】:

  • 我稍微摆弄了一下,让它在 3.4 上工作,但增加的复杂性和“虚拟”吸气剂使它有点违背了寻找一种不那么骇人听闻的方法来做到这一点的目的。就像我对 Cerad 说的那样,我将在 getter 上允许 null,但现在不允许在 setter 和数据库上。不过,这仍然是一个很好的建议,因为它是唯一的答案并且确实有效,所以我会接受它。
  • 我同意做一个允许 null 的 getter 更容易
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-01
  • 1970-01-01
相关资源
最近更新 更多