Symfony2 Entity type form with name and image

I have an object with a name (string) and a file (also a string representing the file name). This is the Icon object. I have another object called "Category" that has a name (string) and an icon (OneToMany) related to it. I want the form to allow the user to select an icon for the category.

Therefore, I could display it in the form:

$builder->add('icon', 'entity', array( 'class' => 'CroltsMainBundle:Icon', 'expanded' => true, 'multiple' => false )); 

But I really want to show something like this in twig for each switch:

 <div> <label for="something"><img src="/icons/{{icon.file }}" />{{icon.name}}</label> <input type="radio" name="something" value="{{ icon.id }}" /> </div> 

Is there a good way to make this type of radio format with Symfony forms? How would a user type be what I want? I really haven’t done too much with custom types to know as much as possible.

+4
source share
4 answers

Not sure if this is the best way, but here is how I can deal with this situation:

  • create a new type of form, which will be on behalf of entityType , IconCheckType for example: ( http://symfony.com/doc/master/cookbook/form/create_custom_field_type.html )

     namespace .....\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormInterface; class IconCheckType extends AbstractType { /** * {@inheritdoc} */ public function buildForm(FormBuilder $builder, array $options) { $builder -> setAttribute('dataType', $options['dataType']); } /** * {@inheritdoc} */ public function buildView(FormView $view, FormInterface $form) { $view -> set('dataType', $form -> getAttribute('dataType')); } /** * {@inheritdoc} */ public function getDefaultOptions(array $options) { return array('required' => false,'dataType'=>'entity'); } /** * Returns the allowed option values for each option (if any). * * @param array $options * * @return array The allowed option values */ public function getAllowedOptionValues(array $options) { return array('required' => array(false)); } /** * {@inheritdoc} */ public function getParent(array $options) { return 'entity'; } /** * {@inheritdoc} */ public function getName() { return 'iconcheck'; } } 
  • in your form

     ... ->add('icon', 'iconcheck', array( 'class' => 'CroltsMainBundle:Icon', 'property'=>'formField', 'multiple'=>false, 'expanded'=>true )) ... 

    Pay attention to property=>'formField' , which means that instead of returning the __toString as label, it returns everything you want from the getFormField function from the entity class

  • So, in the entity class:

     class Icon { .... public function getFormField() { return $this; /* or an array with only the needed attributes */ } .... } 
  • you can display your custom field

     {% block iconcheck_widget %} {% for child in form %} {% set obj=child.vars.label %} <div> <label for="something"><img src="/icons/{{obj.file }}" />{{obj.name}}</label> {{ form_widget(child) }} {# the radio/checkbox #} </div> {{ form_widget(child) }}#} {% endfor %} {% endblock %} 
+6
source

Can you potentially make your __toString() method:

 <?php // Icon entity public function __toString() { return '<img src="/icons/'. $this->file .'" />' . $this->name'; } 

If not, you will have to create a custom type. However it is really easy

 <?php namespace Your\NameSpace; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormViewInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class MyCustomType extends AbstractType { public function getParent() { // By calling get parent here your custom type will // automatically inherit all the properties/functionality // of the type you extend return 'radio'; } } 

Then you can create your own widget for your type. I would read the entry in the cookbook if I were you, because he explains the process very well. You can look at the default Twig widgets for forms to learn how to write your own.

+1
source

I had to add a thumbnail in front of the file selection button for downloading the image today. I have finished it. Sorry, I don’t have time to create a complete example for your case.

  • I only access the parent object to pass the object to the vich_uploadable_asset () helper.

/src/AcmeBundle/Form/Type/AcmeFormType.php

 <?php namespace Acme\AcmeBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; class AcmeFormType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { parent::buildForm($builder, $options); $builder ->add('icon', 'vich_uploadable') ... 

config.yml

 twig: form: resources: - 'AcmeBundle:Form:fields.html.twig' services: acme.type.vich_uploadable: class: Acme\AcmeBundle\Form\Type\VichUploadableFieldType arguments: ["@doctrine.orm.entity_manager"] tags: - { name: form.type, alias: vich_uploadable } 

/src/Acme/AcmeBundle/Form/fields.html.twig

 {% block vich_uploadable_widget %} {% spaceless %} {% if attribute(form.parent.vars.value, form.name) is not empty %} <img src="{{ vich_uploader_asset(form.parent.vars.value, form.name) | imagine_filter('thumb_square') }}" /> {% endif %} {{ form_widget(form) }} {# If you're extending the radio button, it would show here #} {% endspaceless %} {% endblock %} 
+1
source

Here is what I ended up doing. This required a lot of trial and error and searching the EntityType class hierarchy and learning how form types really work. The hardest part is finding the source code and figuring out how to get from the PHP classes to Twig templates (which variables are available).

Here is what I did. This is not an ideal solution (it feels a bit hacked), but it works for my purposes. The idea is to expose the underlying Entity for my view so that I can get its properties.

The biggest problem is that the file property, which contains the file path, is hard-coded in the view. Anyway, I am posting the whole solution, as this may be useful to others. I am also open to criticism if anyone can find a better solution.

(spaces omitted)

Extended Entity Type

 <?php class ExtendedEntityType extends EntityType { public function getParent() { return 'extended_choice'; } public function getName() { return 'extended_entity'; } } 

Extended Choice Type (just needed to change addSubForms, but it's closed)

 <?php class ExtendedChoiceType extends ChoiceType { /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { if (!$options['choice_list'] && !is_array($options['choices']) && !$options['choices'] instanceof \Traversable) { throw new FormException('Either the option "choices" or "choice_list" must be set.'); } if ($options['expanded']) { $this->addSubForms($builder, $options['choice_list']->getPreferredViews(), $options); $this->addSubForms($builder, $options['choice_list']->getRemainingViews(), $options); if ($options['multiple']) { $builder ->addViewTransformer(new ChoicesToBooleanArrayTransformer($options['choice_list'])) ->addEventSubscriber(new FixCheckboxInputListener($options['choice_list']), 10) ; } else { $builder ->addViewTransformer(new ChoiceToBooleanArrayTransformer($options['choice_list'])) ->addEventSubscriber(new FixRadioInputListener($options['choice_list']), 10) ; } } else { if ($options['multiple']) { $builder->addViewTransformer(new ChoicesToValuesTransformer($options['choice_list'])); } else { $builder->addViewTransformer(new ChoiceToValueTransformer($options['choice_list'])); } } if ($options['multiple'] && $options['by_reference']) { // Make sure the collection created during the client->norm // transformation is merged back into the original collection $builder->addEventSubscriber(new MergeCollectionListener(true, true)); } } /** * {@inheritdoc} */ public function getParent() { return 'choice'; } /** * {@inheritdoc} */ public function getName() { return 'extended_choice'; } /** * Adds the sub fields for an expanded choice field. * * @param FormBuilderInterface $builder The form builder. * @param array $choiceViews The choice view objects. * @param array $options The build options. */ private function addSubForms(FormBuilderInterface $builder, array $choiceViews, array $options) { foreach ($choiceViews as $i => $choiceView) { if (is_array($choiceView)) { // Flatten groups $this->addSubForms($builder, $choiceView, $options); } else { $choiceOpts = array( 'value' => $choiceView->value, // Expose more data 'label' => array( 'data' => $choiceView->data, 'label' => $choiceView->label, ), 'translation_domain' => $options['translation_domain'], ); if ($options['multiple']) { $choiceType = 'checkbox'; // The user can check 0 or more checkboxes. If required // is true, he is required to check all of them. $choiceOpts['required'] = false; } else { $choiceType = 'radio'; } $builder->add((string) $i, $choiceType, $choiceOpts); } } } } 

Services

  <service id="crolts_main.type.extended_choice" class="My\MainBundle\Form\Type\ExtendedChoiceType"> <tag name="form.type" alias="extended_choice" /> </service> <service id="crolts_main.type.extended_entity" class="My\MainBundle\Form\Type\ExtendedEntityType"> <tag name="form.type" alias="extended_entity" /> <argument type="service" id="doctrine" /> </service> 

form_layout.html.twig

(this is based on a MopaBootStrapBundle, but the idea is the same. The difference is that MopaBootstrap wraps a <label> around a <radio> )

 {% block extended_choice_widget %} {% spaceless %} {% if expanded %} {{ block('extended_choice_widget_expanded') }} {% else %} {# not being used, just default #} {{ block('choice_widget_collapsed') }} {% endif %} {% endspaceless %} {% endblock extended_choice_widget %} {% block extended_choice_widget_expanded %} {% spaceless %} <div {{ block('widget_container_attributes') }}> {% for child in form %} <label class="{{ (multiple ? 'checkbox' : 'radio') ~ (widget_type ? ' ' ~ widget_type : '') ~ (inline is defined and inline ? ' inline' : '') }}"> {{ form_widget(child, {'attr': {'class': attr.widget_class|default('')}}) }} {% if child.vars.label.data.file is defined %} <img src="{{ vich_uploader_asset(child.vars.label.data, 'file')}}" alt=""> {% endif %} {{ child.vars.label.label|trans({}, translation_domain) }} </label> {% endfor %} </div> {% endspaceless %} {% endblock extended_choice_widget_expanded %} 

Using

 <?php $builder->add('icon', 'extended_entity', array( 'class' => 'MyMainBundle:MenuIcon', 'property' => 'name', // this is still used in label.label 'expanded' => true, 'multiple' => false )); 
0
source

All Articles