Matt
Matt

Reputation: 5567

Symfony 2.7 Form Entity type render multiple properties in form

I had this working previously but it stopped working with Symfony 2.7

What I want is to render an expanded/multiple entity choice list such that I display multiple custom properties. The goal is to list the choices as:

{name} - {description} More info

So I created a custom form type with "entity" as parent so I could customize the form rendering

<?php
namespace Study\MainBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ScholarshipEntityType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->setAttribute('dataType', $options['dataType']);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'required' => false,
            'dataType' => 'entity'
        ));
    }

    public function getAllowedOptionValues(array $options)
    {
        return array('required' => array(false));
    }

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

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

}

I render the type as follows (it was just based off of the Twitter Bootstrap bundle template):

{% block scholarship_entity_widget %}
{% spaceless %}
    {% if expanded %}
        {% set label_attr = label_attr|merge({'class': (label_attr.class|default(''))}) %}
        {% set label_attr = label_attr|merge({'class': (label_attr.class ~ ' ' ~ (widget_type != '' ? (multiple ? 'checkbox' : 'radio') ~ '-' ~ widget_type : ''))}) %}
        {% if expanded %}
            {% set attr = attr|merge({'class': attr.class|default(horizontal_input_wrapper_class)}) %}
        {% endif %}
        {% for child in form %}
            {% if widget_type != 'inline' %}
            <div class="{{ multiple ? 'checkbox' : 'radio' }}">
            {% endif %}
                <label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>
                    {{ form_widget(child, {'horizontal_label_class': horizontal_label_class, 'horizontal_input_wrapper_class': horizontal_input_wrapper_class, 'attr': {'class': attr.widget_class|default('')}}) }}
                    {{ child.vars.label.name|trans({}, translation_domain) }}
                    - {{ child.vars.label.description }}
                    <a href="{{ child.vars.label.link }}" target="_blank">More Information</a>
                </label>
            {% if widget_type != 'inline' %}
            </div>
            {% endif %}
        {% endfor %}
        {{ block('form_message') }}
        {% if expanded %}
        {% endif %}
    {% else %}
        {# not being used, just default #}
        {{ block('choice_widget_collapsed') }}
    {% endif %}
{% endspaceless %}
{% endblock %}

Finally, I use my custom type in another form:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        // ...
        ->add('scholarships', new ScholarshipEntityType(), array(
            'class' => 'StudyMainBundle:Scholarship',
            'query_builder' => function(EntityRepository $er) use ($options) {
                return $er->findAllByOfferingQueryBuilder($options['offering']);
            },
            'choice_label' => 'entity',
            'multiple' => true,
            'expanded' => true,
            'label' => 'financial.scholarships'
        ))
    ;
}

The "property" I'm rendering is just the entity itself:

/**
 * Scholarship
 *
 * @ORM\Table(name="scholarship")
 * @ORM\Entity(repositoryClass="Study\MainBundle\Repository\ScholarshipRepository")
 */
class Scholarship
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    // ...

    /**
     * Get the Entity object for form rendering
     * 
     * @return \Study\MainBundle\Entity\Scholarship
     */
    public function getEntity()
    {
        return $this;
    }
}

Unfortunately, it looks like my trick which was passing the entire Entity to Twig and letting me access properties is no longer working. There is some change where the label is rendered as a string (I changed 'property' to 'choice_label' above for 2.7, if that matters).

Error:

Catchable Fatal Error: Object of class Study\MainBundle\Entity\Scholarship could not be converted to string

Stack Trace:

1. in vendor/symfony/symfony/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php at line 251   + 
2. at ErrorHandler ->handleError ('4096', 'Object of class Study\MainBundle\Entity\Scholarship could not be converted to string', '/var/project/vendor/symfony/symfony/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php', '251', array('choice' => object(Scholarship), 'key' => '0', 'label' => object(Closure), 'values' => array('2'), 'index' => array('Symfony\Bridge\Doctrine\Form\Type\DoctrineType', 'createChoiceName'), 'attr' => null, 'isPreferred' => array(), 'preferredViews' => array(), 'otherViews' => array(), 'value' => '2', 'nextIndex' => '2')) 
in vendor/symfony/symfony/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php at line 251   + 
3. at DefaultChoiceListFactory ::addChoiceView (object(Scholarship), '0', object(Closure), array('2'), array('Symfony\Bridge\Doctrine\Form\Type\DoctrineType', 'createChoiceName'), null, array(), array(), array()) 
in vendor/symfony/symfony/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php at line 185

Is there another way to achieve this?

I was thinking about the following (but don't know exactly how to do these or if it's worth looking into any of them):

Upvotes: 2

Views: 2601

Answers (2)

Joshua
Joshua

Reputation: 2982

Try to use the method AbstractType::buildView(FormView, FormInterface, array). There you can access the variables that get passed to the template.

I used it for a DaterangeType to declare separate ids and names for two date fields:

public function buildView(FormView $view, FormInterface $form, array $options)
{
    $view->vars['full_name_0'] = $view->vars['full_name'] . '[0]';
    $view->vars['full_name_1'] = $view->vars['full_name'] . '[1]';

    $view->vars['id_0'] = $view->vars['id'] . '_0';
    $view->vars['id_1'] = $view->vars['id'] . '_1';
}

You can then access these values as standard twig variables.

Upvotes: 0

Dric512
Dric512

Reputation: 3729

If I understood correctly the problem, you should implement the __toString() function in your entity, that will format the string you want to print in the Choice list for you entity.

For example:

function __toString() {
  return sprintf("%s - %s", $this->type, $this->description);
}

Upvotes: 2

Related Questions