Symfony2 - проверка не работает для встроенного типа формы

У меня есть форма, объединяющая два объекта (User и Profile).

Валидация, похоже, работает с первой частью формы, которая формируется из пользовательской сущности и является основой формы.

Профиль TypeType включен внутри UserType. Форма корректно отображает и отображает правильную информацию, поэтому кажется, что она правильно подключена к объекту Profile. Это просто проверка, которая разбита на ProfileType.

Любая идея относительно того, почему одна часть будет проверяться, а другая не будет?

Код ниже:

Validation.yml

DEMO\DemoBundle\Entity\User\Profile:
    properties:
        address1:
            - NotBlank: { groups: [profile] }
        name:
            - NotBlank: { groups: [profile] }
        companyName:
            - NotBlank: { groups: [profile] }

DEMO\DemoBundle\Entity\User\User:
    properties:
        username:
            - NotBlank:
                groups: profile
                message: Username cannot be left blank.
        email:
            - NotBlank:
                groups: profile
                message: Email cannot be left blank
            - Email:
                groups: profile
                message: The email "{{ value }}" is not a valid email.
                checkMX: true
        password:
            - MaxLength: { limit: 20, message: "Your password must not exceed {{ limit }} characters." }
            - MinLength: { limit: 4, message: "Your password must have at least {{ limit }} characters." }
            - NotBlank: ~

UserType.php

namespace DEMO\DemoBundle\Form\Type\User;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackValidator;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormError;

use DEMO\DemoBundle\Form\Type\User\ProfileType;

class UserType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('username');
        $builder->add('email');
        $builder->add('profile', new ProfileType());
    }

    public function getDefaultOptions(array $options)
    {
        return array(
            'data_class' => 'DEMO\DemoBundle\Entity\User\User',
            'validation_groups' => array('profile')
        );
    }

    public function getName()
    {
        return 'user';
    }
}

ProfileType.php

namespace DEMO\DemoBundle\Form\Type\User;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackValidator;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormError;

class ProfileType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('name');
        $builder->add('companyName', null, array('label' => 'Company Name'));
        $builder->add('address1', null, array('label' => 'Address 1'));
        $builder->add('address2', null, array('label' => 'Address 2'));
        $builder->add('city');
        $builder->add('county');
        $builder->add('postcode');
        $builder->add('telephone');
    }

    public function getDefaultOptions(array $options)
    {
        return array(
            'data_class' => 'DEMO\DemoBundle\Entity\User\Profile',
        );
    }

    public function getName()
    {
        return 'profile';
    }
}

контроллер

$user = $this->get('security.context')->getToken()->getUser();

        $form = $this->createForm(new UserType(), $user);

        if ($request->getMethod() == 'POST') {
            $form->bindRequest($request);

            if ($form->isValid()) {
                // Get $_POST data and submit to DB
                $em = $this->getDoctrine()->getEntityManager();
                $em->persist($user);
                $em->flush();

                // Set "success" flash notification
                $this->get('session')->setFlash('success', 'Profile saved.');
            }

        }

        return $this->render('DEMODemoBundle:User\Dashboard:profile.html.twig', array('form' => $form->createView()));

Ответ 1

Я провел поиск по возрасту и обнаружил, что он добавлял 'cascade_validation' => true в массив setDefaults() в моем классе родительского типа, который исправил его (как уже упоминалось в потоке). Это приводит к тому, что валидация ограничений сущности запускается в дочерних типах, указанных в форме. например.

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(            
        ...
        'cascade_validation' => true,
    ));
}

Для коллекций также обязательно добавьте 'cascade_validation' => true в массив $options для поля коллекции в форме. например.

$builder->add('children', 'collection', array(
    'type'         => new ChildType(),
    'cascade_validation' => true,
));

Это будет иметь проверку UniqueEntity, как и в дочернем объекте, используемом в коллекции.

Ответ 2

Заметка для тех, кто использует Symfony 3.0 и вверх: cascade_validation вариант удален. Вместо этого используйте встроенные формы:

$builder->add('embedded_data', CustomFormType::class, array(
    'constraints' => array(new Valid()),
));

Извините за добавление в этот старый поток с немного неответным ответом (Symfony 3 vs. 2), но найти эту информацию здесь бы спасли меня несколько часов сегодня.

Ответ 3

В соответствии с формой типа документации вы также можете использовать ограничение Valid вместо параметра cascade_validation.

$builder->add('children', 'collection', array(
    'type'        => new ChildType(),
    'constraints' => array(new Valid()),
));

Пример из объекта владельца:

/**
 * @var Collection
 *
 * @ORM\OneToMany(targetEntity="Child", ...)
 * @Assert\Valid()
 */
private $children

Ответ 4

Принадлежит Symfony 2.3

Работа со встроенными формами и группами проверки может быть довольно болезненной: Annotation @Assert\Valid() не работает для меня (без групп это нормально). Вставьте ключ "cascade_validation" = > true в DefaultOptions. Вам не нужно повторять это на → add(). Будьте осторожны: проверка HTML 5 не работает вместе с группами проверки.

Пример:

Коллекция из 2-х адресов. Оба 1:1 однонаправленные. Каждый с другой (!) Группой проверки.

  class TestCollection{

//(...)

/**
 * @var string
 * @Assert\NotBlank(groups={"parentValGroup"})
 * @ORM\Column(name="name", type="string", length=255, nullable=true)
 */
protected $name;

/**
 * @var \Demo\Bundle\Entity\TestAddress  
 * @Assert\Type(type="Demo\Bundle\Entity\TestAddress")
 * @ORM\OneToOne(targetEntity="TestAddress",cascade={"persist","remove"},orphanRemoval=true)
 * @ORM\JoinColumn(name="billing_address__id", referencedColumnName="id")
 */
protected $billingAddress;

/**
 * @var \Demo\Bundle\Entity\TestAddress
 * @Assert\Type(type="Demo\Bundle\Entity\TestAddress")
 * @ORM\OneToOne(targetEntity="TestAddress",cascade={"persist","remove"}, orphanRemoval=true)
 * @ORM\JoinColumn(name="shipping_address__id", referencedColumnName="id")
 */ 
protected $shippingAddress;

//(...)
}

Адресная структура

class TestAddress {
/**
 * @var string
 * @Assert\NotBlank(groups={"firstname"})
 * @ORM\Column(name="firstname", type="string", length=255, nullable=true)
 */
private $firstname;

/**
 * @var string
 * @Assert\NotBlank(groups={"lastname"})
 * @ORM\Column(name="lastname", type="string", length=255, nullable=true)
 */
private $lastname;

/**
 * @var string
 * @Assert\Email(groups={"firstname","lastname"}) 
 * @ORM\Column(name="email", type="string", length=255, nullable=true)
 */
private $email;

Тип адреса - возможность изменения группы проверки

class TestAddressType extends AbstractType {    
protected $validation_group=['lastname'];//switch group

public function __construct($validation_group=null) {
    if($validation_group!=null) $this->validation_group=$validation_group;
}

public function buildForm(FormBuilderInterface $builder, array $options)
{
    //disable html5 validation: it suchs with groups 

    $builder
        ->add('firstname',null,array('required'=>false))
        ->add('lastname',null,array('required'=>false))
        ->add('email',null,array('required'=>false))
    ;
}

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'Demo\Bundle\Entity\TestAddress',           
        'validation_groups' => $this->validation_group,
    ));
}
(...)

И последний тип CollectionType

class TestCollectionType extends AbstractType { 

public function buildForm(FormBuilderInterface $builder, array $options)
{   $builder
        ->add('name')           
        ->add('billingAddress', new TestAddressType(['lastname','firstname']))
        ->add('shippingAddress', new TestAddressType(['firstname']))            
    ;
}

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'Demo\Bundle\Entity\TestCollection',
        'validation_groups' => array('parentValGroup'),         
        'cascade_validation' => true
    ));
}

//(...)    

Надеюсь, что это поможет.

Ответ 5

Вы также должны добавить validation_groups в свой ProfiletType. Валидация выполняется в каждом типе формы отдельно на основе их data_class, если существует.

Ответ 6

используете ли вы YML или аннотации?

Я попробовал применить параметр cascade_validation в своем классе родительской формы, но проверка еще не произошла. Прочитав небольшую документацию, я пошел в app/config/config.yml и обнаружил, что enable_annotations в разделе framework->validation установлен в true. По-видимому, если это так, служба проверки не имеет права на чтение любых файлов validation.yml. Поэтому я просто изменил его на false, и теперь форма хорошо проверяется.

Ответ 7

Я искал то же самое, и вот что я нашел

http://symfony.com/doc/master/book/forms.html#forms-embedding-single-object

Вам нужно сообщить основному объекту, чтобы проверить его сущности следующим образом:

/**
 * @Assert\Type(type="AppBundle\Entity\Category")
 * @Assert\Valid()
 */
 private $subentity;

Я тестировал это на symfony 2.8, и он работает.

Ответ 8

От моего контроллера:

$form = $this->get('form.factory')
        ->createNamedBuilder('form_data', 'form', $item, array('cascade_validation' => true))
        ->add('data', new ItemDataType())
        ->add('assets', new ItemAssetsType($this->locale))
        ->add('contact', new ItemContactType())
        ->add('save', 'submit',
            array(
                'label' => 'Save',
                'attr' => array('class' => 'btn')
            )
        )
        ->getForm();

Четвертый параметр в:: createNamedBuilder - array('cascade_validation' => true))