简体   繁体   中英

Null values in new entities in symfony 3.4 forms without defaults

I have an entity 'administration' which has a field 'firstPeriod'. This field is not nullable (neither in the class definition nor in the database) and nor should it be, as this field should never ever be empty, the application would fail.

However, this field does not have a default value because if an oblivious user would simply submit the form without changing the default value, chaos would ensue. The user must make a conscious choice here. There is validation in place to ensure the field is not empty and within accepted range.

When I try to render the form, the 'propertyAccessor' of the formbuilder component throws this exception:

Type error: Return value of AppBundle\\Entity\\Administration::getFirstPeriod() must be of the type integer, null returned

It looks like the formbuilder tries to get the value of the field before it is set, which ofcourse leads to said exception.

How can I handle this situation so that the form will render without providing the user with a default value?

To further clarify: Null is not okay, but neither is any default I can provide, the user must make a conscious decision. The same goes for any dev that instantiates this entity directly. It must be provided before the entity is persisted, but I cannot give a default because if the default is left as it is, the application will not function 12 out of 13 times.

  • If I allow null on the entity field "?int" I'm effectively making a field nullable that should never be null
  • If I provide a default, the default might be accepted blindly, which would lead to wrong results further on in the application that are very hard to spot for most users.
  • I've already tried to set the 'empty_data => 0' in the formType, to no avail

sorry for the mess below, the 'Code Sample' does not handle this code well

My (truncated) entity:

namespace AppBundle\Entity;

use Doctrine\\ORM\\Mapping as ORM;

/** * Administration * * @ORM\\Table(name="administration") * @ORM\\Entity(repositoryClass="AppBundle\\Repository\\AdministrationRepository") */ class Administration { /** * @var int * * @ORM\\Column(name="id", type="integer") * @ORM\\Id * @ORM\\GeneratedValue(strategy="AUTO") */ private $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;
}

}

My (truncated) formType (as best as i could get it formatted here):

    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';
}

}

My controller:


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
        ]);
    }
}

My validation:

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'

Since symfony form uses property accessor, as @Cerad says, you can add a unmapped field to the form, and get/set the field in form events, adding a specific method for get uninitialized $first_period ...

An example code may be ...

<?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;
    }
}

Form

<?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,
        ]);
    }
}

This works fine in Symfony 4.2

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM