craue / CraueFormFlowBundle

Multi-step forms for your Symfony project.
MIT License
736 stars 118 forks source link

@Valid constraint not recognised/triggered! #283

Closed giovkanata closed 7 years ago

giovkanata commented 7 years ago

First of all thanks for this bundle, very usefull (it's a time saver :-)). I have a step form using the "Approach A: One form type for the entire flow" and all works as expected until the validation of the 2° step where I need to validate the RegistrationCompletionDTO:$personalData using the @Valid contraint. But on form submit the error

Expected argument of type "DateTime", "NULL" given

in

at PropertyAccessor ::throwInvalidArgumentException ('Argument 1 passed to My\Bundle\Form\CustomerPersonalDataDTO::setBirthDate() must be an instance of DateTime, null given

tell me that the @Valid constraint annotation was not recognised/triggered (also using the "Default" validation group, instead of "Personal" doesn't resolve the issue).

PS: I posted only the minimal code related to the problem but fell free to ask if you need more.

My RegistrationCompletionFormFlow:

protected function loadStepsConfig()
    {
        $formType = 'My\Bundle\Folder\Form\RegistrationCompletionForm';

        return [
            [
                'label'    => 'data.account_type.label',
                'form_type' => $formType,
                'form_options' => [
                    'validation_groups' => ['AccountType'],
                    'attr' => ['novalidate' => 'novalidate']
                ]
            ],
            [
                'label'     => 'customer.personal_data.label',
                'form_type' => $formType,
                'form_options' => [
                    'validation_groups' => ['Personal', 'MobilePhone'],
                    'attr' => ['novalidate' => 'novalidate']
                ]
            ],
            [
                'label' => 'finish',
            ],
        ];
    }

My RegistrationCompletionForm:

public function buildForm(FormBuilderInterface $builder, array $options)
    {
        switch ($options['flow_step']){
            case 1:
                $builder
                    ->add('accountType', EntityType::class, [
                        'label' => false,
                        'class'         => AccountType::class,
                        'query_builder' => function (EntityRepository $er) {
                            return $er
                                ->createQueryBuilder('a')
                                ->where('a.enabled = :enabled')
                                ->setParameter('enabled', true)
                            ;
                        },
                        'choice_label' => 'getNameByLocale',
                        'expanded'          => true,
                        'multiple'          => false,
                    ]);
            break;
            case 2:
                $builder
                    ->add('personalData', CustomerPersonalDataForm::class, [
                        'label'      => false,
                        'data_class' => CustomerPersonalDataDTO::class
                    ])
                    ->add('phoneMobile', PhoneNumberType::class, [
                        'label'          => 'data.phone_mobile.label',
                        'default_region' => $isoCode2,
                        'format'         => PhoneNumberFormat::NATIONAL
                    ])
                ;
            break;
        }
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver
            ->setDefaults([
                'data_class'         => RegistrationCompletionDTO::class,
                'translation_domain' => 'app',
                'csrf_protection'    => true,
                'csrf_field_name'    => '_token',
                'csrf_token_id'      => 'registration_completion_form',
                'method'             => 'POST',
            ])
        ;
    }

My RegistrationCompletionDTO:


use Symfony\Component\Validator\Constraints\Valid;

/**
     * @var AccountTypeInterface
     *
     * @NotBlank(
     *     message="global.constraint.not_blank",
     *     groups={"AccountType"}
     * )
     * @AccountTypeField(
     *     message="data.account_type.invalid",
     *     groups={"AccountType"}
     * )
     */
    private $accountType;

    /**
     * @var CustomerPersonalDataDTOInterface
     *
     * @Valid()
     */
    private $personalData;

    /**
     * @var PhoneNumber
     *
     * @NotBlank(
     *     message="global.constraint.not_blank",
     *     groups={"MobilePhone"}
     * )
     * @CustomerPhoneUnique(
     *     message="data.phone_mobile.unique",
     *     groups={"MobilePhone"}
     * )
     */
    private $phoneMobile;

    # getters and setters...

My CustomerPersonalDataDTO:

/**
     * @var string
     *
     * @NotBlank(
     *     message="global.constraint.not_blank",
     *     groups={"Personal"}
     * )
     * @Type(
     *     type="string",
     *     message="global.constraint.type_string",
     *     groups={"Personal"}
     * )
     * @Length(
     *     min=2,
     *     max=30,
     *     minMessage="global.constraint.length_min",
     *     maxMessage="global.constraint.length_max",
     *     groups={"Personal"}
     * )
     */
    private $firstName;

    /**
     * @var \DateTime
     *
     * @NotBlank(
     *     message="global.constraint.not_blank",
     *     groups={"Personal"}
     * )
     * @Date(
     *     message="field.date.invalid",
     *     groups={"Personal"}
     * )
     */
    private $birthDate;

    # other properties...

How can I resolve this issue? I'm forced to use the "Approach B: One form type per step" because the @Valid constraint it's simply not supported or there is something I've missed? Thanks for your time.

craue commented 7 years ago

Just read the docs. 😏

giovkanata commented 7 years ago

Thanks for the fast reply @craue ! I've already readed it carefully many times in the last 2 days but I was not be able to get the point. I've used the form_options to add a specific validation group for each step and I've also tried to use flow_registrationCompletionForm_step2 (obviously changing the DTO's properties) as written in the docs but without success (I never saw where is my error). PS: I'm not a native english speaker and maybe is for this that I cannot see it. I really would like to find the error myself but I think I need a clue :-)

craue commented 7 years ago

Could you set up a fork of the Symfony Standard Edition or the demo bundle to demonstrate the issue?

giovkanata commented 7 years ago

As you suggested I've spent other time reading again the "Validation groups" section and then trying to use the auto generated validation group for the flow. But the auto generated flow_registrationCompletionForm_step2 validation group added to the properties of the CustomerPersonalDataDTO did not were recognised at all. Same behavior If I use the form_options to add a custom validation group for each step as described by the docs. I really don't understand where is the problem or where I make an error, because I followed exactly what described in the docs. I will appreciate if someone could point me in the right direction to let me see where the error is. PS: I will do the fork tomorrow :-)

giovkanata commented 7 years ago

Hi @craue ! I've directly created a new project using a fresh new Symfony 3.2.* installation + your bundles. I've added only the files necessary to reproduce the error. You will find the project at CraueFormFlowTest. The same error happens again when you try to submit the 2 step of the form. Let me know if you need something more.

craue commented 7 years ago

The setter public function setBirthDate(\DateTime $birthDate) must allow a null value as well. The model is first populated with data and validated afterwards. So change this to public function setBirthDate(\DateTime $birthDate = null).

Shame on me that I didn't notice that from the error message you posted initially. :flushed:

giovkanata commented 7 years ago

OMG @craue ! And to think that today, before leaving home, I wanted to do that test. I don't know why I always thought that the validation is performed directly on the data passed by the form without binding it to the model. Sorry!