web-dev-qa-db-fra.com

Symfony3: champ de type choix rempli d'un tableau d'objets

J'ai une entité Product. Mon produit peut avoir plusieurs noms dans différentes langues. Un nom en français, un nom en anglais, etc. Je ne souhaite pas utiliser une traduction automatique.

L'utilisateur devra écrire les noms dans le formulaire Produit et sélectionner la langue correspondante. Il peut ajouter autant de noms qu'il le souhaite grâce à un bouton Ajouter.

Toutes les langues sont créées par l'utilisateur admin (sous une autre forme). Donc, Language est aussi une entité qui a un nom (ex: anglais) et un code (ex: EN).

J'ai créé l'entité ProductName qui porte un nom et une langue (conforme à ce que l'utilisateur écrit dans le formulaire Product).

Dans ce cas, je n'ai pas besoin d'associer l'entité ProductName à l'entité Language. Je veux juste le code de langue. Donc, dans mon entité ProductName, j'ai cette propriété: 

/**
 * @ORM\Column(name="Language_Code", type="string", length=2)
 */
private $language;

Le formulaire Mon produit (ProductType) comporte un champ CollectionType afin d'ajouter plusieurs noms.

// Form/ProductType.php

    ->add('infos',      CollectionType::class, array(
        'entry_type'    => ProductInfosType::class,
        'allow_add'     => true,
        'allow_delete'  => true,
        'prototype'     => true,
        'label'         => false,
        'mapped'        => false
    ))

Et le formulaire ProductInfosType a 2 champs:

// Form/ProductInfosType.php

        ->add('name',           TextType::class, array(
            'attr'              => array('size' => 40)
        ))
        ->add('language',       EntityType::class, array(
            'placeholder'       => '',
            'class'             => 'AppBundle:Language',
            'choice_label'      => 'code',
            'attr'              => array('class' => 'lang'),
            'query_builder'     => function (EntityRepository $er) {
                return $er->createQueryBuilder('l')->orderBy('l.code', 'ASC');
            }
        ))

Ainsi, lorsque je vais sur ma page de formulaire, j'ai un bloc qui contient un champ de texte de saisie (Nom) et un champ de sélection (langue). Le champ de sélection est comme ceci:

<select id="product_infos_0_language" required="required" name="product[infos][0][language]">
    <option value=""></option>
    <option value="DE">DE</option>
    <option value="EN">EN</option>
    <option value="ES">ES</option>
    <option selected="selected" value="FR">FR</option>
</select> 

À ce stade, tout fonctionne bien. J'ai créé un bouton d'ajout pour que l'utilisateur puisse ajouter d'autres noms, etc.

Mais, lorsque je soumets le formulaire, lorsque je vérifie les données de formulaire dans mon ProductController, j'ai remarqué que cela ne correspond pas à ce que je veux stocker dans la base de données.

print_r($form->get('infos')->getData());

// returns :
Array
(
    [0] => AppBundle\Entity\ProductName Object
        ( 
            [language:AppBundle\Entity\ProductName:private] => AppBundle\Entity\Language Object
                (
                    [code:AppBundle\Entity\Language:private] => FR
                    [name:AppBundle\Entity\Language:private] => Français
                )

            [name:AppBundle\Entity\ProductName:private] => Ceinture lombaire LombaSkin
        )
)

Ce que j'aimerais, c'est:

Array
(
    [0] => AppBundle\Entity\ProductName Object
        ( 
            [language:AppBundle\Entity\ProductName:private] => FR    
            [name:AppBundle\Entity\ProductName:private] => Ceinture lombaire LombaSkin
        )
)

Je ne veux pas l'objet langage mais directement le code de langue !

C'est pourquoi je pense que je ne devrais pas utiliser EntityField dans le formulaire ProductNameType mais dans le champ ChoiceType.

Comment puis-je charger toutes les langues stockées dans la base de données dans le champ de choix? J'espère que cette explication est plus compréhensible ;-)

6
Felurian

J'ai trouvé la solution grâce à cet article: Passing data à buildForm () dans Symfony 2.8/3.0

ProductController.php : transmettre des données personnalisées en tant qu'option dans la méthode createForm().

// ...

// build the form
$em = $this->getDoctrine()->getManager();
$product = new Product();
$languages = $em->getRepository('AppBundle:Language')->findAllOrderedByCode();

$form = $this->createForm(ProductType::class, $product, array(
    'languages' => $languages
));

Formulaire ProductType : transmettre des données personnalisées dans le résolveur d'options

public function configureOptions(OptionsResolver $resolver) {
    $resolver->setDefaults(array(
        'data_class' => 'AppBundle\Entity\Product',
        'languages'  => null
    ));
}

Ensuite, dans la fonction buildForm(), ajoutez une option entry_options dans le champ CollectionType:

$builder->add('infos',  CollectionType::class, array(
    'entry_type'    => ProductInfosType::class,
    'entry_options' => array('languages' => $options['languages']),
    'allow_add'     => true,
    'allow_delete'  => true,
    'prototype'     => true,
    'label'         => false,
    'by_reference'  => false
));

Formulaire ProductInfosType : transmettre des données personnalisées dans le résolveur d'options (exactement comme dans le ProductForm)

public function configureOptions(OptionsResolver $resolver) {
    $resolver->setDefaults(array(
        'data_class' => 'AppBundle\Entity\ProductName',
        'languages'  => null
    ));
}

Vous avez maintenant deux possibilités: soit vous voulez que votre formulaire renvoie des entités ou de simples chaînes.

Dans mon exemple, je veux juste le code de langue (comme FR, EN, etc.).

Cas 1: renvoie uniquement le code de langue lorsque le formulaire est posté:

// Form/ProductInfosType.php

// ...

// Convert array of objects in an array of strings
$choices = array();
foreach ($options['languages'] as $lang) {
    $code = $lang->getCode();
    $choices[$code] = $code;
}

$builder->add('language', ChoiceType::class, array(
    'placeholder'       => '',
    'choices'           => $choices
));

// returns :
Array
(
    [0] => AppBundle\Entity\ProductName Object
        ( 
            [name:AppBundle\Entity\ProductName:private] => Ceinture lombaire LombaSkin
            [language:AppBundle\Entity\ProductName:private] => FR    
        )
)

Cas 2: Renvoie l'entité linguistique lors de l'envoi du formulaire:

// Form/ProductInfosType.php

// ...

$builder->add('language', ChoiceType::class, array(
    'placeholder'       => '',
    'choices'           => $options['languages'],
    'choice_label'      => 'code',
    'choice_value'      => 'code'
));

// returns :
Array
(
    [0] => AppBundle\Entity\ProductName Object
        ( 
            [name:AppBundle\Entity\ProductName:private] => Ceinture lombaire LombaSkin
            [language:AppBundle\Entity\ProductName:private] => AppBundle\Entity\Language Object
                (
                    [code:AppBundle\Entity\Language:private] => FR
                    [name:AppBundle\Entity\Language:private] => Français
                )    
        )
)

Avec cette solution, il n'est pas nécessaire de créer notre formulaire en tant que service pour pouvoir passer le gestionnaire d'entités en argument. Tout est géré dans les options du contrôleur et du formulaire.

7
Felurian