Abhoryo / APYJsFormValidationBundle

This bundle performs validations of a form in javascript. (i18n compatible and several javascript frameworks supported)
92 stars 23 forks source link

Symfony 2.1 annotation constraints not working #16

Closed zd-dalibor closed 11 years ago

zd-dalibor commented 11 years ago

This validation bundle do not read constraints from php anotations in symfony 2.1.

recipe commented 11 years ago

Provide us with code snippet please. It works on my end.

recipe commented 11 years ago

Test for this has been implemented. You may update this bundle

composer.phar update apy/jsfv-bundle

and run the test as described here

zd-dalibor commented 11 years ago

I have class defined like this:

namespace ...

use Symfony\Component\Validator\Constraints as Assert;

class UserData
{
    /**
     * @var string
     *
     * @Assert\NotBlank()
     * @Assert\Email()
     */
    protected $email;

    /**
     * @var string
     *
     * @Assert\NotBlank()
     */
    protected $password;

    /**
     * @var string
     *
     * @Assert\NotBlank()
     */
    protected $username;

    /**
     * @var string
     */
    protected $geopcCountry;

    /**
     * @var string
     */
    protected $geopcLanguage;

    /**
     * @var integer
     */
    protected $geopcId;

    /**
     * @var \DateTime
     *
     * @Assert\NotBlank()
     */
    protected $birthday;

    /**
     * @var bool
     *
     * @Assert\NotBlank()
     */
    protected $gender;

    ...
}

end form type defined like this:

namespace ...

use Symfony\Component\Form\AbstractType;
use Symfony\Component\HttpKernel\Log\LoggerInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use EWZ\Bundle\RecaptchaBundle\Validator\Constraints\True;

class UserDataType extends AbstractType
{
    protected $help = array();

    protected $logger;

    function __construct()
    {
        $this->help = array(
            'email' => implode(' ', array(
                'Your email will not be shown publicly.',
                'You can change your privacy settings at any time.'
            )),
            'password' => 'Minimum of 6 characters long.',
            'username' => implode(' ', array(
                'Please enter a username between 6 and  25 characters long.',
                'It can only contain letters A-Z or numbers 0-9; no spaces.',
                'Your public profile will be: http://zugme.com/USERNAME'
            )),
            'gender' => implode(' ', array(
                'Your date of birth and gender will not be shown publicly.',
                'You can change your privacy settings at any time.',
                'For security reasons, you might be asked to confirm your date of birth and gender.'
            ))
        );
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('email', 'text', array(
                'label' => 'E-mail address:',
                'help' => $this->help['email'],
            ))
            ->add('password', 'repeated', array(
                'type' => 'password',
                'invalid_message' => 'The password fields must match.',
                'options' => array('required' => true),
                'first_options'  => array('label' => 'Password:', 'help' => $this->help['password']),
                'second_options' => array('label' => 'Confirm Password:'),
            ))
            ->add('username', 'text', array(
                'label' => 'Username:',
                'help' => $this->help['username'],
            ))
            ->add('geopcCountry', 'hidden')
            ->add('geopcLanguage', 'hidden')
            ->add('geopcId', 'hidden')
            ->add('country', 'entity', array(
                'label' => 'Country/Region:',
                'empty_value' => '',
                'class' => 'ZugmeRepositoryBundle:CountryCode',
                'property' => 'country',
                'expanded' => false,
                'multiple' => false,
                'property_path' => false,
            ))
            ->add('zip', 'text', array(
                'label' => 'Postal Code:',
                'property_path' => false
            ))
            ->add('birthday', 'birthday', array(
                'label' => 'Date of Birth:',
                'widget' => 'choice',
                'format' => 'yyyy MMMM dd',
                'empty_value' => '',
            ))
            ->add('gender', 'choice', array(
                'label' => 'Gender:',
                'choices' => array(
                    'Male',
                    'Female',
                ),
                'expanded' => true,
                'multiple' => false,
                'help' => $this->help['gender']
            ))
            ->add('recaptcha', 'ewz_recaptcha', array(
                'label' => 'Security code:',
                'attr' => array(
                    'options' => array(
                        'theme' => 'clean',
                        'lang' => 'en'
                    )
                ),
                'property_path' => false,
                'constraints' => array(
                    new True()
                ),
            ));
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => '...\UserData',
        ));
    }

    /**
     * Returns the name of this type.
     *
     * @return string The name of this type
     */
    public function getName()
    {
        return 'user_data';
    }
}

and this is what this bundle generate for js validation script:

/**
 * This file is part of the JsFormValidationBundle.
 *
 * (c) Abhoryo <abhoryo@free.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

var jsfv = new function () {
    function getComputeMessage(key, placeholders, number) {
        ExposeTranslation.placeHolderPrefix = '{{ ';
        ExposeTranslation.placeHolderSuffix = ' }}';
        // Default translations
        if (!ExposeTranslation.has('validators:'+key)) {
            ExposeTranslation.add('validators:'+key, key);
        }

        return ExposeTranslation.get('validators:'+key, placeholders, number);
    }

    function isNotDefined(value) {
        return (typeof(value) == 'undefined' || null === value || '' === value);
    }

    function checkError(field, checkFunction, parameters, value) {
        field = jsfv.id(field);
        // Remove old errors of the field
        jsfv.removeErrors(field);        // Check the value
        errorMessage = checkFunction((value === undefined ? field : value), parameters);
        /*//  */

        if (errorMessage != true) {
        jsfv.addError(field, errorMessage);
            return false;
        }

        return true;
    }

function Repeated(field, params)
{
    var value = field && field.nodeName ? $(field).val() : field;

    first = document.getElementById(params.first_name).value;
    second = document.getElementById(params.second_name).value;

    if (first !== second) {
        return getComputeMessage(params.invalid_message, params.invalid_message_parameters);
    }

    return true;
}

    return {
        id: function (id) {
            return document.getElementById(id);
        },
        removeErrors: function (field) {
            $(field).siblings('ul.error_list').remove();
        },
        addError: function (field, errorMessage) {
            // Add errors block
            field = $(field);
            if (field.siblings('ul.error_list').length == 0)  {
                field.before('<ul class="error_list"></ul>');
            }

            // Add error
            field.prev().filter('ul').append('<li>'+errorMessage+'</li>');
        },
        onEvent: function (field, eventType, handler) {
            if (typeof(field) == 'string') {
                field = jsfv.id(field);
            }
            $(field).bind(eventType, handler);
        },
        check_user_data_password_second: function() {
            var gv;
            result = true;
            result = result && checkError('user_data_password_second', Repeated, {first_name: 'user_data_password_first', second_name: 'user_data_password_second', invalid_message: 'The password fields must match.', invalid_message_parameters: null} );
            return result;
        },
        onReady: function() {
            // On submit checks
            var form = jsfv.id('user_data');

            // Form exists ?
            if (form) {
                // Get the form
                if ( form.nodeName.toLowerCase() != 'form') {
                    form = jsfv.id('ma_form__token').form;
                }

                jsfv.onEvent(form, 'submit', function() {
                    var gv, submitForm = true;
                    submitForm = jsfv.check_user_data_password_second() && submitForm;
                    return submitForm;
                });
            }

            // On blur checks
            jsfv.onEvent('user_data_password_second', 'blur', jsfv.check_user_data_password_second);
        }
    };
}

$(jsfv.onReady);

As you can see the js validation bundle is recognize only repeated field and nothing else (required, email etc. look in UserData class).

Abhoryo commented 11 years ago

I think there is a problem when the bundle retreive the entity of the form.

// FormValidationScriptGenerator.php - Line 201
$formViewValue = $formView->get('value');

I don't know how you have build your form in your controller. Can you debug $formViewValue variable and see what entity is used by the bundle ?

I think that the bundle can't handle multiple entities in the same form builder.

recipe commented 11 years ago

What about phpunit test? Has it passed? I looked in your code and your constraints should work. However, I do not see your controller. Here in the TestBundle you can found simple example:

.../TestBundle/Entity/Product.php

.../TestBundle/Controller/DefaultController.php

zd-dalibor commented 11 years ago

$formView->get('value') return null until request is not bound to form eq.

$form = $this->createForm(new UserDataType());
$formView = $form->createView();
$formViewValue = $formView->get('value'); // $formViewValue is null
$request = $this->getRequest();
$form = $this->createForm(new UserDataType());
if ($request->getMethod() == 'POST') {
    $form->bind($request);
    $formView = $form->createView();
    $formViewValue = $formView->get('value'); // $formViewValue is typeof ...\UserData
}
recipe commented 11 years ago

Try to use

$user = new UserData();
//...
$form = $this->createForm(new UserDataType(), $user);

This is the cause.

Abhoryo commented 11 years ago

Good catch.

zd-dalibor commented 11 years ago

The purpose of symfony form types is to use createForm function of controller without supplying instance of data carrier class (second parameter). that instance can be got with $form->getData();

zd-dalibor commented 11 years ago

You should add this in doc:

"If you wont this bundle working properly you must supply second parameter to createForm controller function."

recipe commented 11 years ago

I think we should consider an possibility to fetch Entity class name from the data_class property of the FormType instance.

Abhoryo commented 11 years ago

Yeah data_class is better.

recipe commented 11 years ago

It has been fixed. composer.phar update apy/jsfv-bundle