Reputation: 567
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.
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'
Upvotes: 2
Views: 4091
Reputation: 2072
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;
}
}
<?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
Upvotes: 2