2

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

  • transformers
  • custom type that derives from Choice and does what I want (maybe from a bundle)
  • using the choice list factory somehow
  • passing the entity as some additional field instead of the label (maybe the new 'choice_attr'?)
3
  • As the choice field got heavily changed in 2.7, try a few other of the new properties. Maybe 'choices_as_values' => true will do the trick. Commented Sep 3, 2015 at 6:18
  • I just finished converting all of my forms to do that. It didn't help in this case (I don't think it's a default change for 2.7 but something that will be default in 3.0) Commented Sep 3, 2015 at 6:23
  • 1
    In 2.5 it was possible to use the buildView method of the field type to pass arbitrary data to the template. Commented Sep 3, 2015 at 6:58

2 Answers 2

2

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);
}
Sign up to request clarification or add additional context in comments.

3 Comments

I also want to display a link inside an a html tag. But can I just do all that and set the label to render without escaping HTML?
That's tricky... In this case, in the __toString() function, you could use htmlentities($this->name) and htmlEntities($this->description), plus the link (Inside <a></a>), then use |raw...
I used htmlspecialchars (that's what Twig does, apparently). I also defined this as a new getter just to keep things clean. Otherwise, this solution worked for me. I'm not happy about moving the code from Twig to the Entity, but it works.
0

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.

1 Comment

This could help. But if I have an 'entity' type, then the choices automatically link to Entities based on the query I run. Is there some way to do this per choice within the entity choice type?

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.