Symfony2 - Validierung funktioniert nicht für eingebetteten Formulartyp

74

Ich habe ein Formular, das zwei Entitäten (Benutzer und Profil) kombiniert.

Die Validierung scheint für den ersten Teil des Formulars zu funktionieren, der von der Benutzerentität stammt und die Grundlage des Formulars bildet.

Der ProfileType ist im UserType enthalten. Das Formular wird korrekt gerendert und zeigt die richtigen Informationen an, sodass es anscheinend ordnungsgemäß mit der Profilentität verbunden ist. Es ist nur die Validierung, die im ProfileType unterbrochen ist.

Irgendeine Idee, warum ein Teil validieren würde und der andere nicht?

Code unten:

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';
    }
}

Regler

$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()));
Herr Pablo
quelle

Antworten:

116

Ich habe ein Alter lang gesucht und festgestellt, dass es 'cascade_validation' => truedem setDefaults()Array in der Klasse meines übergeordneten Typs hinzugefügt wurde , das es behoben hat (wie bereits im Thread erwähnt). Dadurch wird die Validierung der Entitätsbeschränkung in den im Formular angezeigten untergeordneten Typen ausgelöst. z.B

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

Stellen Sie bei Sammlungen auch sicher, dass Sie 'cascade_validation' => truedem $optionsArray für das Sammlungsfeld im Formular etwas hinzufügen . z.B

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

Dadurch wird die UniqueEntity-Validierung wie in der in der Auflistung verwendeten untergeordneten Entität durchgeführt.

daftv4der
quelle
3
Wie haben Sie herausgefunden, dass Sie das Kaskadenflag zum Optionsarray für die Sammlung hinzufügen müssen? Dies war mein Problem, aber ich bin in Symfony-Dokumenten nicht darauf gestoßen?
Redbirdo
4
Durch Durchsuchen vieler anderer Kommentare zu ähnlichen Themen hier: /
daftv4der
6
Einfacher können Sie in validation.yml eine "gültige" Einschränkung angeben, um die Validierung für Objekte zu aktivieren, die als Eigenschaften für ein zu validierendes Objekt eingebettet sind. Symfony.com/doc/current/reference/constraints/Valid.html
sdespont
8
Für Benutzer von Symfony 3.0 und höher: cascade_validation wurde entfernt. Siehe meine Antwort unten für Details.
cg.
@sdespont Wenn eine Entität eine andere erweitert, in der die Eigenschaft mit Assert\Valid()Annotation definiert ist, führt die erste Entität keine Kaskadenvalidierung für diese Eigenschaft durch. Also cascade_validationdas Problem lösen.
Paaacman
79

Ein Hinweis für Benutzer von Symfony 3.0 und höher: Die cascade_validationOption wurde entfernt . Verwenden Sie stattdessen Folgendes für eingebettete Formulare:

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

Es tut mir leid, dass ich diesen alten Thread mit einer leicht vom Thema abweichenden Antwort (Symfony 3 vs. 2) ergänzt habe, aber das Finden dieser Informationen hier hätte mir heute ein paar Stunden gespart.

cg.
quelle
Super, danke. Die validierte Antwort sollte auch Ihre enthalten.
Aly
3
Dies ist die einzige Option, die in Symfony 3.1.4 für mich funktioniert. Das Befolgen der Dokumentation symfony.com/doc/current/form/embedded.html und das Hinzufügen von @Assert \ Valid () Constrait zu Entity, wie es geschrieben wurde, funktioniert nicht. In meinem Fall verwende ich die Entität FOSUserBundle und Custom User, die mit UserProfile (OneToOne) zusammenhängt. Vielen Dank, dass Sie @cg.
Heilung85
1
Mit dieser Option möchten Sie möglicherweise das Sprudeln von Fehlern deaktivieren .
Jorr.it
Verdammt, ich habe einige Zeit damit verbracht, danach zu suchen ... was ist der Zweck von @Assert \ Valid dann -.- ... diese Art von Hacks sind ein echtes Problem, jetzt müssen wir uns an zwei weiteren Stellen um die Validierung kümmern. Vielen Dank @cg. !
Benjamin Vison
Zusätzlich musste ich use Symfony\Component\Validator\Constraints\Valid;oben im Formular eine definitoin Datei hinzufügen . Symfony 4 hier.
NullIsNot0
35

Gemäß der Dokumentation zum Formulartyp können Sie Validanstelle der cascade_validationOption auch eine Einschränkung verwenden .

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

Beispiel aus der Eigentümerentität:

/**
 * @var Collection
 *
 * @ORM\OneToMany(targetEntity="Child", ...)
 * @Assert\Valid()
 */
private $children
Supernova
quelle
Und dazu gezwungen seit Symfony 3.0 (wie von @cg erwähnt )
Pierre de LESPINAY
4

Ich habe genau das Gleiche gesucht und hier ist, was ich gefunden habe

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

Sie müssen die Hauptentität anweisen, ihre Unterentitäten wie folgt zu validieren:

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

Ich habe dies auf Symfony 2.8 getestet und es funktioniert.

Nacholibre
quelle
3

Verwenden Sie YML oder Anmerkungen?

Ich habe versucht, die cascade_validationOption auf meine übergeordnete Formularklasse anzuwenden , aber die Validierung fand immer noch nicht statt. Nachdem ich eine kleine Dokumentation gelesen hatte, ging ich zu app/config/config.ymlund stellte fest, dass enable_annotationsunter framework->validationauf true gesetzt war . Wenn dies zutrifft, liest der Validierungsdienst anscheinend kein Einzelgänger validation.yml-Dateien. Also habe ich es einfach in false geändert und jetzt wird das Formular gut validiert.

Ziel
quelle
3

Gehört zu Symfony 2.3

Das Arbeiten mit eingebetteten Formularen und Validierungsgruppen kann sehr schmerzhaft sein: Annotation @Assert \ Valid () funktioniert bei mir nicht (ohne Gruppen ist es in Ordnung). Fügen Sie 'cascade_validation' => true in die DefaultOptions ein. Sie müssen dies nicht auf -> add () wiederholen. Achtung: Die HTML 5-Validierung funktioniert nicht mit Validierungsgruppen zusammen.

Beispiel:

Eine Sammlung von 2 Adressen. Beide 1: 1 unidirektional. Jeweils mit einer anderen (!) Validierungsgruppe.

  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;

//(...)
}

Adressentität

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;

Adresstyp - Möglichkeit zum Ändern der Validierungsgruppe

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,
    ));
}
(...)

Und zuletzt den 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
    ));
}

//(...)    

Ich hoffe es hilft..

Hauke
quelle
1
Das hat mir den Tag gerettet !! nein .. es hat mein Jahr gerettet. Gott segne Sie: D Haben Sie mit @Assert \ Valid () eine Lösung gefunden, weil die Option 'cascade_validation' seit Version 2.8
Stasa
2

Sie müssen hinzufügen validation_groupsin Ihr ProfiletTypeauch. Die Validierung erfolgt in jedem Formulartyp separat, basierend auf deren data_classVorhandensein.

Mun Mun Das
quelle
6
Ich fügte hinzu, dass es danach nicht geholfen hat. Aber ich habe das Problem gefunden. Ich musste dem Optionsarray 'cascade_validation' hinzufügen. Verdammt nervig, weil dies in den Symfony2-Dokumenten nirgendwo erwähnt wird.
Herr Pablo
3
@ MrPablo Ich nehme an, dies ist die cascade_validationOption in 2.1?
Richsage
3
@MrPablo Da Sie anscheinend eine Antwort auf Ihre Frage gefunden haben, sollten Sie Ihre eigene Antwort mit einem vollständigen Beispiel für die Verwendung von "cascade_validation" veröffentlichen. Dies ist zu wichtig, um in einem Kommentar abgestiegen zu sein! Vielen Dank
JeanValjean
0

Von meinem Controller:

$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();

Vierter Parameter in :: createNamedBuilder - array('cascade_validation' => true))

d3uter
quelle