Symfony version : 2.8.5
Context: I have an entity Restaurant
which has a OneToOne relationship with an entity Coordinates
which have several relations with other entities related to Coordinates informations. I my backend I create a form related to Restaurant
entity with a custom nested form related to Coordinates
.
Nota : I use EasyAdminBundle to generate my backend.
Entities relations scheme :
Restaurant
1 ________ 1 `Coordinates`
* ________ 1 `CoordinatesCountry`
1 ________ 1 `CoordinatesFR`
* ________ 1 `CoordinatesFRLane`
* ________ 1 `CoordinatesFRCity`
Backend view :
At this point I try the following scenario :
- I create a new Restaurant, so I fill the fields related form for the first time. I let the
Coordinates
nested form blank (empty fields). So after form submission, validation messages are displayed (see image below).
I edit the previous form and this time I fill the fields of the
Coordinates
nested form. After form submission, a newCoordinates
entity is hydrated and a relationship is created betweenRestaurant
andCoordinates
.Once again I edit the previous form and this time I clear all the fields of the
Coordinates
nested form. The validation is not triggered and I get the following error :
Expected argument of type "FBN\GuideBundle\Entity\CoordinatesFRCity", "NULL" given
I precise that in CoordinatesFRType
(see code below), to trigger the validations message the first time I had to use the option empty_data
with a closure (like described in the official doc) to instatiate a new CoordinatesFR
instance in case of empty datas (all fields blank). But here, in this article (written by the creator of the Symfony form component), it is explained (see empty_data and datta mappers paragraphs) that the empty_data
is only called at object creation. So I think this the reason why my validation does not work anymore in case of edition.
Question : why the validation is not effective anymore when editing my form and clearing all embedded form ?
The code (only what is necessary) :
Restaurant entity
use Symfony\Component\Validator\Constraints as Assert;
class Restaurant
{
/**
* @ORM\OneToOne(targetEntity="FBN\GuideBundle\Entity\Coordinates", inversedBy="restaurant", cascade={"persist"})
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
* @Assert\Valid()
*/
private $coordinates;
}
Coordinates entity
use Symfony\Component\Validator\Constraints as Assert;
class Coordinates
{
/**
* @ORM\ManyToOne(targetEntity="FBN\GuideBundle\Entity\CoordinatesCountry")
* @ORM\JoinColumn(nullable=false)
*/
private $coordinatesCountry;
/**
* @ORM\OneToOne(targetEntity="FBN\GuideBundle\Entity\CoordinatesFR", inversedBy="coordinates", cascade={"persist"})
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
* @Assert\Valid()
*/
private $coordinatesFR;
/**
* @ORM\OneToOne(targetEntity="FBN\GuideBundle\Entity\Restaurant", mappedBy="coordinates")
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
*/
private $restaurant;
}
CoordinatesFR entity
use Symfony\Component\Validator\Constraints as Assert;
class CoordinatesFR extends CoordinatesISO
{
/**
* @ORM\ManyToOne(targetEntity="FBN\GuideBundle\Entity\CoordinatesFRLane")
* @ORM\JoinColumn(nullable=true)
* @Assert\NotBlank()
*/
private $coordinatesFRLane;
/**
* @ORM\ManyToOne(targetEntity="FBN\GuideBundle\Entity\CoordinatesFRCity")
* @ORM\JoinColumn(nullable=false)
* @Assert\NotBlank()
*/
private $coordinatesFRCity;
/**
* @ORM\OneToOne(targetEntity="FBN\GuideBundle\Entity\Coordinates", mappedBy="coordinatesFR")
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
*/
private $coordinates;
}
Easy Admin config (equivalent to RestaurantType)
easy_admin:
entities:
Restaurant:
class : FBN\GuideBundle\Entity\Restaurant
form:
fields:
- { property: 'coordinates', type: 'FBN\GuideBundle\Form\CoordinatesType' }
CoordinatesType
class CoordinatesType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('CoordinatesCountry', EntityType::class, array(
'class' => 'FBNGuideBundle:CoordinatesCountry',
'property' => 'country',
))
->add('coordinatesFR', CoordinatesFRType::class)
;
}
/**
* @param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'FBN\GuideBundle\Entity\Coordinates',
));
}
}
CoordinatesFRType
class CoordinatesFRType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('laneNum', TextType::class)
->add('coordinatesFRLane', EntityType::class, array(
'class' => 'FBNGuideBundle:CoordinatesFRLane',
'property' => 'lane',
'placeholder' => 'label.form.empty_value',
))
->add('laneName', TextType::class)
->add('miscellaneous', TextType::class)
->add('locality', TextType::class)
->add('metro', TextType::class)
->add('coordinatesFRCity', EntityType::class, array(
'class' => 'FBNGuideBundle:CoordinatesFRCity',
'property' => 'display',
'query_builder' => function (CoordinatesFRCityRepository $repo) {
return $repo->getAscendingSortedCitiesQueryBuilder();
},
'placeholder' => 'label.form.empty_value',
))
;
}
/**
* @param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'FBN\GuideBundle\Entity\CoordinatesFR',
// Ensures that validation error messages will be correctly displayed next to each field
// of the corresponding nested form (i.e if submission and CoordinatesFR nested form with all fields empty)
'empty_data' => function (FormInterface $form) {
return new CoordFR();
},
));
}
}