4

For now I've successful used validation groups, but now I'm stuck with validation groups and nested mapped entities.

I'll explain the problem by a simplified example.

My entities: Address, Damage, Appliance

/**
 * @ORM\Entity()
 */
class Address extends ...
{
    /**
     * @var string
     * @ORM\Column(type="string", name="postcode", nullable=true)
     * @Assert\NotBlank(
     *     groups={
     *     "damage_responsible_address",
     *     "appliance_repairer_address",
     *     })
     */
    private $postcode;

    ...


/**
 * @ORM\Entity()
 */
class Damage extends ...
{
    /**
     * @var boolean
     * @ORM\Column(type="boolean", name="responsible", nullable=true)
     * @Assert\NotBlank(groups={"damage"})
     */
    private $responsible;

    /**
     * @ORM\OneToOne(targetEntity="Address", cascade={"persist","remove"})
     * @ORM\JoinColumn(name="responsible_address_id", referencedColumnName="id")
     * @Assert\Valid()
     */
    private $responsibleAddress;

    /**
     * @ORM\ManyToMany(targetEntity="Appliance", orphanRemoval=true, cascade={"persist", "remove"})
     * @ORM\JoinTable(name="coronadirect_cuzo_home_damage_appliances",
     *      joinColumns={@ORM\JoinColumn(name="damage_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="appliance_id", referencedColumnName="id")}
     *      )
     */
    private $appliances;

    ...


/**
 * @ORM\Entity()
 */
class Appliance extends ...
{
    /**
     * @var boolean
     * @ORM\Column(type="boolean", name="to_repair", nullable=true)
     * @Assert\NotBlank(groups={"appliance"})
     */
    private $toRepair;

     /**
     * @ORM\OneToOne(targetEntity="Address", cascade={"persist","remove"})
     * @ORM\JoinColumn(name="repairer_address_id", referencedColumnName="id")
     * @Assert\Valid()
     */
    private $repairAddress;

    ...

To define my forms I use a AddressType, DamageType and ApplianceType:

class DamageType extends ...
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
         $builder->add('appliances', 'collection', array(
            'type' => 'home_damage_appliance_type',
            'allow_add' => true,
            'allow_delete' => true,
            'prototype' => true,
            'options' => array(
                'cascade_validation' => true,
            )
        ));

        $builder->add('responsible', 'choice', array(
            'choices' => $this->getYesNoChoiceArray(),
            'expanded' => true,
            'multiple' => false,
        ));

        $builder->add('responsibleAddress', 'address_type', array(
            'required' => true
        ));

        ...
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
                'data_class' => 'Damage',
                'cascade_validation' => true,
                'validation_groups' =>
                    function(FormInterface $form) {

                        $groups = array('damage');

                        if ($form->getData()->getResponsible() == true) {
                            $groups[] = 'damage_responsible_address';
                        }

                        return $groups;
                    }
        ));
    }

I'm adding the damage_responsible_address group when responsible is set to true in the form. Otherwise I don't want the address to be validated.

class ApplianceType extends ...
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {

        $builder->add('toRepair', 'choice', array(
            'choices' => $this->getYesNoChoiceArray(),
            'expanded' => true,
            'multiple' => false,
        ));

        $builder->add('repairAddress', 'address_type', array(
            'required' => true
        ));

        ...
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
                'data_class' => 'Appliannce',
                'cascade_validation' => true,
                'validation_groups' =>
                    function(FormInterface $form) {

                        $groups = array('appliance');

                        if ($form->getData()->getToRepair() == true) {
                            $groups[] = 'appliance_repairer_address';
                        }

                        return $groups;
                    }
        ));
    }

Same as previous, when toRepair is true I want to validate the address.

What's going wrong ?

I the Damage responsible is true and the appliance toRepair is false, the form does give validation errors on the responsible address BUT also on the the appliance address.

The same for the other way arround: When an appliance address is invalid (toRepar is true), then the responsibleAddress is also invalid (even when responsible is false).

The address validation groups don't look on which form they are defined, but just attatch them to every address item in the form.

Is it possible to define validation groups specific for a form only?

I am using Doctrine and Symfony 2.3.6.

Alexey B.
  • 11,965
  • 2
  • 49
  • 73
jayv
  • 495
  • 4
  • 13
  • Did you have any success ? – jillro Jan 30 '14 at 18:02
  • it would be nice if for such important questions you would provide feedback. – jillro Jan 31 '14 at 16:10
  • I fixed it by using a in class constraint on each Entity that want's the address to be valid. Cause some of them don't need to be validated. As @forgottenbas already said, it's OR validation which doesn't make this work. If once the group is applied on Address, it will be used for all Address Entities in the form. – jayv Feb 06 '14 at 11:58

3 Answers3

1

The problem is that symfony uses OR-logic for validation groups. So then your apply one of groups to form it will validate address in both cases.

If you move the groups to a parent entity, it will not solve the problem?

/**
 * @ORM\Entity()
 */
class Damage extends ...
{
    /**
     * @ORM\OneToOne(targetEntity="Address", cascade={"persist","remove"})
     * @ORM\JoinColumn(name="responsible_address_id", referencedColumnName="id")
     * @Assert\Valid(
     *     groups={
     *     "damage_responsible_address"
     *     })
     * )
     */
    private $responsibleAddress;
}
Alexey B.
  • 11,965
  • 2
  • 49
  • 73
  • They do not use OR-logic, and the problem is not that they validate in both case, but that all addresses *invalidate*, non ? – jillro Jan 28 '14 at 22:06
1

italic is for general case, bold for the particular case of this question.

The address validation groups don't look on which form they are defined, but just attach them to every address item in the form.

True, your validation_group callback in DamageType set validation_group for all the form, and as you use cascade_validation, for all embed AddressType the form contains. There is no reason it should understand that you want it set only for responsible address.

Generally, setting a validation group through callback for a parent form, with cascade validation set to true, will make all child forms validated against this validation group. If we want to set different validation groups for each children, we have to put a validation group callback in children formtype.

You must place your validation callback in AddressType, so it will be called for each Address Form. Of course, in order to do so, each Adress entity must be aware of its owner.

An alternative to validation callback is Group Sequence Provider. But in any case you still have to set your validation groups in each Address entity and not in Damage, so your Adress must still be aware of its owner.

jillro
  • 4,456
  • 2
  • 20
  • 26
  • By the way @gremo are you going to award your bounties ? – jillro Jan 31 '14 at 16:09
  • This post really helped me. I had groups set for a parent form, forcing the wrong validation on a child form even when default validation groups where set in the form. The solution was to either move simple validation into a constraint in the relevant form class, or use a specific callback within the child form type. – Ben Stinton Sep 08 '14 at 16:15
1

I know it has been a while, but here is the documentation that may answer the problem with symfony v3:

http://symfony.com/doc/current/form/data_based_validation.html

Alexander Farber
  • 21,519
  • 75
  • 241
  • 416
rmasclef
  • 108
  • 10