5

In Symfony2, is there a way to map more fields from an entity to the option tag of a select dropdown generated from a form which is based on an entity?

I currently have something like:

    $builder->add('creditcard', 'entity',
        array( 'label' => 'Credit Card',
            'required' => true,
            'expanded' => false,
            'class' => 'Acme\Bundle\Entity\CreditCard',
            'property' => 'display_text',
            'multiple' => false,
            'query_builder' => function(\Acme\Bundle\Repository\CreditCardRepository $er)  {
                return $er->createQueryBuilder('b');
            },
            'mapped' => false,
        ));

This works just fine, but I would like to generate something like:

<option value="id" string_mapped_from_field1="value_of_field1">display_text</option>

Thanks!

5 Answers 5

14

Ok, in case somebody gets here with the same question, this is what I've done in the end:

I've created a custom field type (see http://symfony.com/doc/current/cookbook/form/create_custom_field_type.html)

Since we is going to be an entity field in the end, you want to add:

    public function getParent() {
        return 'entity';
    }

When using it on the Form:

    $builder->add('creditcard', new CreditCardFieldType(),
        array( 'label' => 'Credit Card',
            'required' => true,
            'expanded' => false,
            'class' => 'Acme\Bundle\Entity\CreditCardCharge',
            'property' => 'object',
            'multiple' => false,
            'query_builder' => function(\Acme\Bundle\Repository\CreditCardChargeRepository $er)  {
                return $er->createQueryBuilder('b');
            },
            'mapped' => false,
        ));

object is a new property added to the entity that contains the whole object, so I added to the entity:

public function getObject()
{
    return $this;
}

This way we can access to the object from the template, we just need to create a new template for our own custom field type:

{% block creditcard_widget %}
    {% spaceless %}
        {% if required and empty_value is none and not empty_value_in_choices %}
            {% set required = false %}
        {% endif %}
        <select {{ block('widget_attributes') }}{% if multiple %} multiple="multiple"{% endif %}>
            {% if empty_value is not none %}
                <option value=""{% if required and value is empty %} selected="selected"{% endif %}>{{ empty_value|trans({}, translation_domain) }}</option>
            {% endif %}
            {% if preferred_choices|length > 0 %}
                {% set options = preferred_choices %}
                {{ block('choice_creditcard_widget_options') }}
                {% if choices|length > 0 and separator is not none %}
                    <option disabled="disabled">{{ separator }}</option>
                {% endif %}
            {% endif %}
            {% set options = choices %}
            {{ block('choice_creditcard_widget_options') }}
        </select>
    {% endspaceless %}
{% endblock creditcard_widget %}

{% block choice_creditcard_widget_options %}
    {% spaceless %}
        {% for group_label, choice in options %}
            {% if choice is iterable %}
                <optgroup label="{{ group_label|trans({}, translation_domain) }}">
                    {% set options = choice %}
                    {{ block('choice_creditcard_widget_options') }}
                </optgroup>
            {% else %}
                <option value="{{ choice.data.creditcard }}" charge="{{  choice.data.charge }}" {% if choice is selectedchoice(data.creditcard_charges_id) %} selected="selected"{% endif %}>{{ choice.data.text|trans({}, translation_domain) }}</option>
            {% endif %}
        {% endfor %}
    {% endspaceless %}
{% endblock choice_creditcard_widget_options %}

And register it for twig in your config.yml:

twig:
    form:
        resources:
            - 'AcmeBundle:Form:creditcardfield.html.twig'

Not sure it is the best solution but it does the trick. Hope it helps.

Sign up to request clarification or add additional context in comments.

Comments

3

an other solution is to declare a function __toString() on CreditCardCharge entity

http://symfony.com/doc/current/reference/forms/types/entity.html#property

like :

public function __toString(){
   return $this->data1.'-'.$this->data2;
}

this function must return a string, replace my sample by your logic

and remove the "property" option to use this function by default

$builder->add('creditcard', 'entity',
        array( 'label' => 'Credit Card',
            'required' => true,
            'expanded' => false,
            'class' => 'Acme\Bundle\Entity\CreditCard',
            'multiple' => false,
            'query_builder' => function(\Acme\Bundle\Repository\CreditCardRepository $er)  {
                return $er->createQueryBuilder('b');
            },
            'mapped' => false,
        ));

Comments

3

since Symfony 2.7 you can use choice_attr

$builder->add('creditcard', 'entity',
    array( 'label' => 'Credit Card',

        // ...

       'choice_attr' => function($object, $key, $index) {
           return [
               'string_mapped_from_field1' => $object->getValueOfField1()
           ];
       },

    ));

1 Comment

far quicker and cleaner than the main answer in Symfony 2.8
1

I fixed it by putting this in my empField.php :

{%- block choice_widget_options -%}
    {% for group_label, choice in options %}
        {%- if choice is iterable -%}
            <optgroup label="{{ group_label|trans({}, translation_domain) }}">
                {% set options = choice %}
                {{- block('choice_widget_options') -}}
            </optgroup>
        {%- else -%}
            <option value="{{ choice.value }}" data-position="{{ choice.data.position }}" data-avatar="{% path choice.data.avatar, 'reference' %}"{% if choice is selectedchoice(value) %} selected="selected"{% endif %}>{{ choice.data.fullname }}</option>
        {%- endif -%}
    {% endfor %}
{%- endblock choice_widget_options -%}

(it is from : \vendor\symfony\symfony\src\Symfony\Bridge\Twig\Resources\views\Form\form_div_layout.html.twig) And it works, I'm not sure it was the right thing to do but yeah it works.

Comments

0

I tried to use this exemple with my entity, but i have an issue. It doesn't detect if a value is selected after the page load and it doesn't persist after submit.

I feel it is related to this line :

<option value="{{ choice.data.creditcard }}" charge="{{  choice.data.charge }}" {% if choice is selectedchoice(data.creditcard_charges_id) %} selected="selected"{% endif %}>{{ choice.data.text|trans({}, translation_domain) }}</option>

I am not sure to understand what to put instead of : data.creditcard_charges_id.

This is how looks mine :

{% block emp_widget %}
    {% spaceless %}
        {% if required and empty_value is none and not empty_value_in_choices %}
            {% set required = false %}
        {% endif %}
        <select {{ block('widget_attributes') }}{% if multiple %} multiple="multiple"{% endif %}>
            {% if empty_value is not none %}
                <option value=""{% if required and value is empty %} selected="selected"{% endif %}>{{ empty_value|trans({}, translation_domain) }}</option>
            {% endif %}
            {% if preferred_choices|length > 0 %}
                {% set options = preferred_choices %}
                {{ block('choice_emp_widget_options') }}
                {% if choices|length > 0 and separator is not none %}
                    <option disabled="disabled">{{ separator }}</option>
                {% endif %}
            {% endif %}
            {% set options = choices %}
            {{ block('choice_emp_widget_options') }}
        </select>
    {% endspaceless %}
{% endblock emp_widget %}

{% block choice_emp_widget_options %}
    {% spaceless %}
        {% for group_label, choice in options %}
            {% if choice is iterable %}
                <optgroup label="{{ group_label|trans({}, translation_domain) }}">
                    {% set options = choice %}
                    {{ block('choice_emp_widget_options') }}
                </optgroup>
            {% else %}
                <option value="{{ choice.data.id }}" data-position="{{ choice.data.position }}" data-avatar="{% path choice.data.avatar, 'reference' %}" {% if choice is selectedchoice(choice.data.id) %} selected="selected"{% endif %}>{{ choice.data.firstname }} {{ choice.data.lastname }}</option>
            {% endif %}
        {% endfor %}
    {% endspaceless %}
{% endblock choice_emp_widget_options %}

2 Comments

I faced the same problem but no solution yet. If I find I will let you know.
Well in my case it was missing value="{{ choice.data.id }}" So entire line is: <option value="{{ choice.data.id }}" {% if choice is selectedchoice(choice.data.id) %} selected="selected"{% endif %}>{{ choice.data.location.name[app.request.locale].value }}: {{ choice.data.name[app.request.locale].value }}</option>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.