Symfony3 : choice type field filled with array of objects
Asked Answered
S

2

7

I have an entity Product. My product can have multiple names in different languages. A name in french, a name in english, etc. I don't want to use an automatic translation.

The user will have to write the names in the Product form and select the corresponding language. He can add so many names as he wants thanks to an Add button.

All the languages are created by the admin user (in another form). So, Language is also an Entity which have a name (ex: English) and a code (ex: EN).

I created the Entity ProductName which have a name and a language (that conform to what the user writes in the Product form).

In that case, I don't need to associate Entity ProductName with Entity Language. I just want the language code. So, in my ProductName entity, I have this property :

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

My Product form (ProductType) has a CollectionType field in order to add several names.

// Form/ProductType.php

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

And ProductInfosType form has 2 fields :

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

So, when I go on my form page, I have a block which contains an input text field (Name) and a select field (language). The select field is like this :

<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> 

At this point, everything works well. I created an add button so that the user can add other names, etc...

But, when I submit the form, when I check form data in my ProductController, I noticed that it does not correspond to what I want to store in database.

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
        )
)

What I would like is :

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

I don't want the language object but directly the language code !

That's why I think that I should not use EntityField in ProductNameType form but ChoiceType field.

How can I load all the languages stored in db in the choice field ? I hope that this explanation is more understandable ;-)

Salaam answered 3/5, 2016 at 14:16 Comment(5)
If I understand well : Have you tried to create a __toString() method which returns your code property of Language entity ?Cassity
No. I don't think it helps me. The code of my language is already a string. Ex : Name = English and Code = EN. Instead of get an object Language when I submit my form, I just want the code that I selected in my list.Salaam
I don't really understand what you mean by "get" after submitCassity
I think EntityType is exactly what you need. Why do you want a property of Language, and not Language itself? Just get $entity->getLanguage()->getCode() after submitting.Mcfall
I modified my post in order to explain better my problem ;-)Salaam
S
7

I found the solution thanks to this post : Passing data to buildForm() in Symfony 2.8/3.0

ProductController.php : pass custom data as an option in the createForm() method.

// ...

// 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
));

ProductType form : pass custom data in the options resolver

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

Then, in the buildForm() function, add an entry_options option in the CollectionType field :

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

ProductInfosType form : pass custom data in the options resolver (exactly the same as in the ProductForm)

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

Now, you have two alternatives : either you want that your form returns entities or simple strings.

In my example, I just want the language code (like FR, EN, etc.).

Case 1 : return only the language code when the form is posted :

// 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    
        )
)

Case 2 : return Language Entity when the form is posted :

// 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
                )    
        )
)

With this solution, we don't need to create our form as a service in order to pass entity manager as an argument. All is managed in the controller and form options.

Salaam answered 9/5, 2016 at 8:39 Comment(0)
H
1

You should use choice_value of EntityType.

'choice_value' => function ($language) {
    return $language->getCode();
},

EDIT: After reading your edit, you're indeed right, do not use EntityType but ChoiceType. To populate choices, I think you should inject LanguageRepository inside your Form using DependencyInjection, and then create a query in your Repository to fetch all Language codes.

Heurlin answered 3/5, 2016 at 16:29 Comment(4)
No, I already have codes as values of my select options. It doesn't change the data of my form.Salaam
I modified my post in order to explain better my problem ;-)Salaam
Ok thank you ! That's what I thought. I know how to create the query in LanguageRepository but can you write me the solution for "inject LanguageRepository inside your Form using DependencyInjection", please ?Salaam
Add LanguageRepository in your Form's constructor, then define your Form as a service in your services.xml with the repository as an argument (cf. symfony.com/doc/current/components/dependency_injection/…). You will need to define your repository as a service as well.Heurlin

© 2022 - 2024 — McMap. All rights reserved.