"required" based on the assertion from validation_groups on forms

TL; DR: Required attribute not set according to established validation rules.

validation_groups is a friendly way to define what should be validated on a form (and how). This works for me as expected for the classic "Register" and "Update Profile" forms.

What I am not getting is a small glitch in the UI. In all "required" fields, these fields must be marked with an asterisk *****.

According to Documentation :

The required parameter can be guessed based on validation rules (i.e. it's NotBlank or NotNull field) or Doctrine metadata (i.e. nullable field). This is very useful as your client-side validation will automatically match your validation rules.

It doesn't seem to work, I can of course override the required one, and if I set it to false

, it won't show up as expected.

But if I use my validate_group for profile_update, the password fields are not in the validation_group file, and if empty it will not be flagged as a failed item. But the attribute required

is still set.

So to get to the question: how is the flag required

based on the @Assert

object annotation ?

enter image description here

As you can see in the image, the password fields are marked "required" but are not validated as expected. Again, this is not a validation issue, it is just a UI issue with the required attribute.

Don't think it helps, but here are the relevant (short) parts of the code:

Entity \ User:

class User implements UserInterface
{
    use Timestampable;
    use Blameable;

    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(type="string", unique=true, length=200, nullable=false)
     * @Assert\NotBlank(groups={"default"})
     * @Assert\Email(groups={"default"})
     * @Assert\Length(max = "200", groups={"default"})
     */
    private $email;

    /**
     * @ORM\Column(type="string", length=64, nullable=false)
     * @Assert\NotBlank(groups={"create"})
     * @RollerworksPassword\PasswordStrength(minLength=6, minStrength=2)
     */
    private $password;

    [....]
}

      

Forms \ UserType:

class UserType extends AbstractType
{
    [...]

    /**
     * @param FormBuilderInterface $builder
     * @param array                $options
     *
     * @return misc
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('firstname', 'text', array('label' => 'Firstname'))
            ->add('lastname', 'text', array('label' => 'Lastname'))
            ->add('email', 'email', array('label' => 'EMail'))
            ->add('password', 'repeated', [
                    'type'  => 'password',
                    'label' => 'Password',
                    'invalid_message' => 'Password fields must match',
                    'first_options' => ['label' => 'Password'],
                    'second_options' => ['label' => 'Repeat Password']
                ]
            );


        [...]
        $builder
            ->add('save', 'submit', array('label' => 'Save'));
    }

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'validation_groups' => function(FormInterface $form) {
                $data = $form->getData();
                if ($data->getId() == null) {
                    return array('default', 'create');
                }

                return array('default');
            },
            'data_class' => 'Dpanel\Model\Entity\User',
        ));
    }
    [...]
}

      

view \ form.html.twig

[...]
{{ form(form, {'style': 'horizontal', 'col_size': 'xs', 'align_with_widget': true, 'attr': {'novalidate': 'novalidate'}}) }}
[...]

      

+3


source to share


1 answer


So, without figuring out why this doesn't work, I decided to code the functionality myself.

To make this work a lot of help was found in this article and the source of the JsFormValidatorBundle

What I am doing: Using a FormType extension that calls the service class to get the constraints on the object. Once I know which field elements should be required and which shouldn't, I change the view and set the required variable accordingly.

The Result

Warning This code is not extensible and may not work in your configuration!

Form \ Extension \ AutoRequireExtension.php:



<?php
namespace Cwd\GenericBundle\Form\Extension;

use Cwd\GenericBundle\Form\Subscriber\AutoRequire as AutoRequireSubscriber;
use Cwd\GenericBundle\Form\Service\AutoRequire as AutoRequireService;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;
use JMS\DiExtraBundle\Annotation as DI;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;


/**
 * Class AutoRequireExtension
 *
 * @package Cwd\GenericBundle\Form\Extension
 * @DI\Service("cwd.generic.form.extension.autorequire")
 * @DI\Tag("form.type_extension", attributes={ "alias"="form" })
 */
class AutoRequireExtension extends AbstractTypeExtension
{
    /**
     * @var AutoRequireService
     */
    protected $service;

    /**
     * @var bool
     */
    protected $enabled;

    /**
     * @param AutoRequireService $service
     * @param bool               $enabled
     *
     * @DI\InjectParams({
     *      "service" = @DI\Inject("cwd.generic.form.service.autorequire"),
     *      "enabled" = @DI\Inject("%cwd.genericbundle.form.extension.autorequire.enabled%")
     * })
     */
    public function __construct(AutoRequireService $service, $enabled = false)
    {
        $this->service = $service;
        $this->enabled = $enabled;
    }

    /**
     * @param FormBuilderInterface $builder
     * @param array                $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {

        if ($this->enabled) {
            $builder->addEventSubscriber(new AutoRequireSubscriber($this->service));
        }
    }

    public function buildView(FormView $view, FormInterface $form, array $options)
    {
        if ($this->enabled) {
            if (isset($this->service->fields[$view->vars['name']])) {
                $view->vars['required'] = $this->service->fields[$view->vars['name']];
            }

            // Password Repeat Fallback
            if ($view->vars['name'] == 'first' || $view->vars['name'] == 'second') {
                $view->vars['required'] = $this->service->fields['password'];
            }
        }

    }

    /**
     * Returns the name of the type being extended.
     *
     * @return string The name of the type being extended
     */
    public function getExtendedType()
    {
        return 'form';
    }
}

      

Form \ Subscriber \ AutoRequire.php:

<?php
namespace Cwd\GenericBundle\Form\Subscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Cwd\GenericBundle\Form\Service\AutoRequire as AutoRequireService;

/**
 * Class AutoRequire
 *
 * @package Cwd\GenericBundle\Form\Subscriber
 */
class AutoRequire implements EventSubscriberInterface
{
    protected $service = null;

    /**
     * @param AutoRequireService $service
     */
    public function __construct(AutoRequireService $service)
    {
        $this->service = $service;
    }

    /**
     * @return array
     */
    public static function getSubscribedEvents()
    {
        return array(FormEvents::PRE_SUBMIT => array('onFormSetData', -10));
    }

    /**
     * @param FormEvent $event
     */
    public function onFormSetData(FormEvent $event)
    {
        /** @var Form $form */
        $form   = $event->getForm();
        $this->service->process($this->getParent($form));
    }

    /**
     * @param Form|FormInterface $element
     *
     * @return \Symfony\Component\Form\Form
     */
    protected function getParent($element)
    {
        if (!$element->getParent()) {
            return $element;
        } else {
            return $this->getParent($element->getParent());
        }
    }
}

      

Form \ Service \ AutoRequire.php:

namespace Cwd\GenericBundle\Form\Service;

use JMS\DiExtraBundle\Annotation as DI;
use Symfony\Component\Form\Form;
use Symfony\Component\Validator\Validator\ValidatorInterface;

/**
 * Class AutoRequire
 *
 * @DI\Service("cwd.generic.form.service.autorequire")
 */
class AutoRequire
{
    /**
     * @var ValidatorInterface
     */
    protected $validator;

    public $fields = array();

    protected $groups = null;

    /**
     * @param ValidatorInterface $validator
     *
     * @DI\InjectParams({
     *      "validator" = @DI\Inject("validator")
     * })
     */
    public function __construct(ValidatorInterface $validator)
    {
        $this->validator = $validator;
    }

    /**
     * Add a new form to processing queue
     *
     * @param \Symfony\Component\Form\Form $form
     *
     * @return array
     */
    public function process(Form $form)
    {

        // no need to run for every field
        if ($this->groups === null) {
            $this->groups = $this->getValidationGroups($form);
        }

        // no need to run for every field
        if (count($this->fields) == 0) {
            $this->fields = $this->getValidations($form, $this->groups);
        }
    }

    /**
     * Get validation groups for the specified form
     *
     * @param Form|FormInterface $form
     *
     * @return array|string
     */
    protected function getValidationGroups(Form $form)
    {
        $result = array('Default');
        $groups = $form->getConfig()->getOption('validation_groups');
        if (empty($groups)) {
            // Try to get groups from a parent
            if ($form->getParent()) {
                $result = $this->getValidationGroups($form->getParent());
            }
        } elseif (is_array($groups)) {
            // If groups is an array - return groups as is
            $result = $groups;
        } elseif ($groups instanceof \Closure) {
            $result = call_user_func($groups, $form);
        }

        return $result;
    }

    private function getValidations(Form $form, $groups)
    {
        $fields = array();

        $parent = $form->getParent();
        if ($parent && null !== $parent->getConfig()->getDataClass()) {
            $fields += $this->getConstraints($parent->getConfig()->getDataClass(), $groups);

        }

        if (null !== $form->getConfig()->getDataClass()) {
            $fields += $this->getConstraints($form->getConfig()->getDataClass(), $groups);
        }

        return $fields;
    }

    protected function getConstraints($obj, $groups)
    {
        $metadata = $this->validator->getMetadataFor($obj);
        $fields = array();

        foreach ($metadata->members as $elementName => $d) {
            $fields[$elementName] = false;
            $data = $d[0];
            foreach ($data->constraintsByGroup as $group => $constraints) {
                if (in_array($group, $groups) && count($constraints) > 0) {
                    $fields[$elementName] = true;
                    break;
                }
            }
        }

        return $fields;
    }

    /**
     * Gets metadata from system using the entity class name
     *
     * @param string $className
     *
     * @return ClassMetadata
     * @codeCoverageIgnore
     */
    protected function getMetadataFor($className)
    {
        return $this->validator->getMetadataFactory()->getMetadataFor($className);
    }

    /**
     * Generate an Id for the element by merging the current element name
     * with all the parents names
     *
     * @param Form $form
     *
     * @return string
     */
    protected function getElementId(Form $form)
    {
        /** @var Form $parent */
        $parent = $form->getParent();
        if (null !== $parent) {
            return $this->getElementId($parent) . '_' . $form->getName();
        } else {
            return $form->getName();
        }
    }
}

      

The Up2Date version can be found at https://gitlab.cwd.at/symfony/cwdgenericbundle/tree/master/Form

+2


source







All Articles