“required”基于表单中来自validation_groups的断言

时间:2014-12-27 22:00:10

标签: validation symfony symfony-forms

TL; DR:未根据设置验证规则设置必需属性。

validation_groups是一种用于定义表单中应该验证的内容(以及如何)的一种比较方式。 我有这个经验与#34; Register"和"更新个人资料"形式。

我不能工作的是一个小的UI故障。在所有"必需"字段,此字段应标有*****。

根据Documentation

  

可根据验证规则猜测所需选项(即   是字段NotBlank或NotNull)或Doctrine元数据(即   可空的领域)。作为您的客户端,这非常有用   验证将自动匹配您的验证规则。

这似乎不起作用,我当然可以覆盖所需的内容,如果我将其设置为false,则按预期不会显示。

但是,如果我将validate_group用于profile_update,则密码字段不在validation_group中 - 如果为空,则不会将其标记为失败元素。但required属性仍然设置。

所以提出问题 - required标志如何基于实体的@Assert注释?

enter image description here

正如您在图片上看到的那样,密码字段标记为"必需"但是,按照预期,未经过验证。同样,这不是验证问题,它只是具有必需属性的UI问题。

不要认为它会有多大帮助,但这里是相关的(短端)代码部分:

实体\用户:

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;

    [....]
}

表单\用户类型:

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',
        ));
    }
    [...]
}

视图\ form.html.twig

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

2 个答案:

答案 0 :(得分:2)

所以,在没有找到为什么这不起作用之后,我决定自己编写功能。

为了使这项工作在this ArticleJsFormValidatorBundle

的来源中找到了很大的帮助

我做的是: 使用FormType扩展调用服务类来获取实体的约束。 一旦我知道哪些字段元素不需要,我就修改视图并相应地设置所需的变量。

The Result

警告 此代码无法进行可扩展测试,可能无法在您的配置中使用!

<强>表单\扩展\ 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';
    }
}

<强>表单\订户\ 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());
        }
    }
}

<强>表单\服务\ 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();
        }
    }
}

可以在https://gitlab.cwd.at/symfony/cwdgenericbundle/tree/master/Form

上找到Up2Date版本

答案 1 :(得分:0)

我必须承认我没有花时间详细介绍Rufinus’ own answer。但是,这是一个更简单的解决方案,特别是如果您只需要一个字段使用该解决方案:

// UserType.php

use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;

// ...

$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
    $form = $event->getForm();
    if (in_array('create', $form->getConfig()->getOption('validation_groups'))) {
        $form->add('password');
    }
    else {
        $form->add('password', null, 'required'=>false);
    }
});

想法是使用Event Listener来确定是否设置了create验证组,然后添加带有或不带有'required'=>false的密码字段。