Throoze
Throoze

Reputation: 4038

Pass custom options to a symfony2 form

in symfony 1.4 it was possible to parameterize a form class definition, via the options of the form. Is there any way to pass custom options to my custom form type??? i've tried to use the options parameter of the buildForm method, but i'm not very sure what this array is, and apparently it is not for what i want... Thanks!

Upvotes: 27

Views: 66189

Answers (6)

Piotrek Zatorski
Piotrek Zatorski

Reputation: 524

basing on @pulzarraider answer I created code with changes for Symfony 3.

You need to change

OptionsResolverInterface for OptionsResolver

FormBuilder for FormBuilderInterface

In my case:

namespace MediaBundle\Form;

use Symfony\Component\Form\AbstractType as FormAbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;

class ImageType extends FormAbstractType {

    public function configureOptions(OptionsResolver $resolver) {
        $resolver->setDefaults(array(
            'max_images' => ''
        ));
    }

    public function buildView(FormView $view, FormInterface $form, array $options) {

        $view->vars['max_images'] = $options['max_images'];
    }

    public function buildForm(FormBuilderInterface $builder, array $options) {

        $builder
                ->setAttribute('max_images', $options['max_images'])
        ;
    }

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

    public function getParent() {
        return TextareaType::class;
    }
}

Upvotes: 5

pulzarraider
pulzarraider

Reputation: 2317

The solution is simple, if you want your custom option to be available also in Twig template, you must use $builder->setAttribute() in buildForm method and $view->set() method in buildView() method, too.

<?php

namespace Acme\DemoBundle\Form\Type;

use Symfony\Component\Form\AbstractType as FormAbstractType;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;

// For Symfony 2.1 and higher:
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

/**
 * ImagePreviewType
 *
 */
class ImagePreviewType extends FormAbstractType
{

    /**
     * {@inheritDoc}
     * For Symfony 2.0
     */
    //public function getDefaultOptions(array $options)
    //{
    //    $options = parent::getDefaultOptions($options);
    //    $options['base_path'] = 'path/to/default/dir/';
    //
    //    return $options;
    //}

    /**
     * {@inheritDoc}
     * For Symfony 2.1 and higher
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'base_path'         => '',
        ));
    }

    /**
     * {@inheritDoc}
     */
    public function buildView(FormView $view, FormInterface $form, array $options)
    {
        // For Symfony 2.0:
        // $view->set('base_path', $form->getAttribute('base_path'));

        // For Symfony 2.1 and higher:
        $view->vars['base_path'] = $options['base_path'];
    }

    /**
     * {@inheritDoc}
     */
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder
            ->setAttribute('base_path', $options['base_path'])
        ;
    }

    /**
     * {@inheritDoc}
     */
    public function getName()
    {
        return 'image_preview';
    }

    public function getParent(array $options)
    {
        // for Symfony 2.0:
        // return 'field';

        // for Symfony 2.1 and higher:
        return 'form';
    }
}

Template for custom form type (file ...Acme/DemoBundle/Resources/views/Form/fields.html.twig):

{% block image_preview_widget %}
{% spaceless %}
<img src="{{ base_path ~ value }}" alt=""  {{ block('widget_container_attributes') }} />
{% endspaceless %}
{% endblock %}

Register your template for custom form types in app/config/config.yml

twig:
    debug:            %kernel.debug%
    strict_variables: %kernel.debug%
    form:
        resources:
            - 'AcmeDemoAdminBundle:Form:fields.html.twig'

Usage: Display preview of user's image while editing his profile:

// src/Acme/DemoBundle/Form/Type/UserType.php
namespace Acme\DemoBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;

class UserType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('user_profile_image_file_name', new ImagePreviewType(), array(
            'base_path' => 'some/other/dir',
        ));
    }
}

2014-08-18: Updated for Symfony 2.1 or higher

Upvotes: 45

Erik Theoboldt
Erik Theoboldt

Reputation: 2568

Using symfony 2.8, I succeeded by using the proposed solution extending the configureOptions() method.

class ElementType extends AbstractType
{
    // ...

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'my_custom_option_parameter' => null,
        ));
    }
}

I needed to use the ElementType, as a collection and embedded form. I recognized that it was not possible to pass the my_custom_option_parameter to the CollectionType, because I did not customize configureOptions() of CollectionType, but of my ElementType. If you need to pass the my_custom_option_parameter through a CollectionType, you may succeed by defining my_custom_option_parameter in entry_options (see Documentation CollectionType Field) array of CollectionType.

Example passing my_custom_option_parameter through a CollectionType:

class OuterFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    { 
        //...
        $builder->add('elements', CollectionType::class, array(
            'entry_type' => ElementType::class,
            // ...
            'entry_options' => array(
                'my_custom_option_parameter' => 'value is now set!'
            )
        ));
        //...
    }
}

Upvotes: 10

eggmatters
eggmatters

Reputation: 1140

Using Symfony 3, I was able to pass custom options to the form by setting a default option in the OptionsResolver injected into configureOptions method of my form type class:

In the Controller:

//Compile whatever your options are here. Assume an array is returned
$customOptions = $this->getMyCustomOptions($args);
//Build the form:
$form = $this->createForm(MyCustomFormType::class, array('my_custom_options' => $customOptions));

MyCustomFormType.php:

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults([
        'data_class' => DataModel::class,
        'my_custom_options' => []
    ]);
}
//custom options is now set in "$options" array:
public function buildForm(FormBuilderInterface $builder, array $options) {
    $builder->add('my_custom_fields', Type\ChoiceType::class, [
       'choices' => $options['my_custom_options'],
       'mapped' => false //if not part of the data model.
       . . .

So, it looks like you can define a contract with your form and the data provider to set arbitrary data on the form.

I've just successfully implemented this procedure. Note that on the return trip, since you've set 'mapped' => false, in the formBuilder, $form->getData() will not return the selection. To get the selected value:

$mySelectedValue = $form->get('my_custom_options')->getViewData();

in your controller. Why this is is beyond me . . .

Upvotes: 3

eagleoneraptor
eagleoneraptor

Reputation: 1227

UPDATE: Please note that this solution only works in Symfony 2.0.x, which is obsolete, use setDefaultOptions instead of getDefaultOptions.


Justly, Symfony 2 form types accept options that you can use for anything you want inside the form type. You need to override getDefaultOptions method to specify your type options.

For example, I have a type MyCustomType that accept my_option, this option has a default value of false, the implementation of MyCustomType can be something like this.

class MyCustomType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        if($options['my_option']){
            //do something
        } else {
            //do another thing
        }
        ...
    }

    public function getDefaultOptions(array $options)
    {
        return array(
            'my_option' => false
        );
    }

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

Later, you will need to specify the option when you create the form in the controller, using the third parameter of buildForm:

$form = $this->buildForm(new MyCustomType(), null, array(
    'my_option' => true
));

If you not specify the my_option option, it takes the default value (false).

Upvotes: 18

Jovan Perovic
Jovan Perovic

Reputation: 20193

I have tried using that options array with no success as it seemed that it could carry only small, predefined subset of keys. This was, by all means, unacceptable for me...

However, you can pass all the options via forms __construct method and store it in class properties for later use. Then, from buildForm you can access it using $this->"propertyName"...

It's up to you to decide whether you want to pass single array or just few variables to __construct...

This is just a rough example:

class Foobar{
    private $update = false;

    public function __construct($update = false){
        $this->update = $update;
    }


    public function buildForm(FormBuilder builder, array options){
        if ( $update ){
            // something
        }else{
            // well, this is not an update - do something else
        }
    }
}

Upvotes: 2

Related Questions