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 StudyMainBundleFormType;
use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilderInterface;
use SymfonyComponentFormFormView;
use SymfonyComponentFormFormInterface;
use SymfonyComponentOptionsResolverOptionsResolver;
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
*
* @ORMTable(name="scholarship")
* @ORMEntity(repositoryClass="StudyMainBundleRepositoryScholarshipRepository")
*/
class Scholarship
{
/**
* @var integer
*
* @ORMColumn(name="id", type="integer")
* @ORMId
* @ORMGeneratedValue(strategy="AUTO")
*/
private $id;
// ...
/**
* Get the Entity object for form rendering
*
* @return StudyMainBundleEntityScholarship
*/
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 StudyMainBundleEntityScholarship 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 StudyMainBundleEntityScholarship 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('SymfonyBridgeDoctrineFormTypeDoctrineType', '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('SymfonyBridgeDoctrineFormTypeDoctrineType', '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’?)
2
Answers
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:
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:You can then access these values as standard twig variables.