Ian P. Christian's Personal Blog Random witterings from pookey

13Aug/119

Creating a custom form field type in symfony 2

I am finally starting to dive into symfony 2 properly. Yes - it's taken a while, work has taken me in different directions! I found myself needing to create a custom form field type pretty quickly, but couldn't find much in the way of documentation to do so, so I thought I'ld throw it up here; partly to help others, but mostly to get feedback to make sure I'm not approaching this from the wrong angle.

I have a model called Person, and this Person has a attribute of preferredTranportMethod, which could be either Train, Car or Bike. This is similar to what one might use an enum for with doctrine, but for portability I'm making this an integer. This means my model looks something like this:

 
class Person
{
  const TRANSPORT_CAR = 1;
  const TRANSPORT_TRAIN = 2;
  const TRANSPORT_BIKE = 3;
 
  protected $preferredTransportMethod;
}

In a form I wan this to be a select box, so it was typical for me to do something like this in my form type:

 
class XXX extends BaseType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
      $builder->add('preferredTransportMethod', 'choice', array(
        'choices'  => array(
          Person::TRANSPORT_CAR => 'car',
          Person::TRANSPORT_TRAIN => 'train',
          Person::TRANSPORT_BIKE => 'bike',
        )));
    }
}

The problem with this is that it's not reusable - if I had several forms with this choice, and needed to add PLANE to the options, I'd have to modify them all. So, the solution was to add my own form field type of 'transportmethod'

To start with, I looked at how the Timezone field type worked, looking at the source in

'vendor/symfony/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php'

. It seemed pretty simple, take al ook at the code yourself. You'll notice is separates out the choices into a separate class of TimezoneChoiceList. I copied this structure to create my transportmethod type in my own bundle.

In the file src/Acme/ProfileBundle/Form/Extension/Type/TransportMethodType.php

 
namespace Acme\ProfileBundle\Form\Extension\Type;
 
use Symfony\Component\Form\AbstractType;
 
use Acme\ProfileBundle\Form\Extension\ChoiceList\TransportMethodChoiceList;
 
class TransportMethodType extends AbstractType
{
 
    /**
     * {@inheritdoc}
     */
    public function getDefaultOptions(array $options)
    {
        return array(
            'choice_list' => new TransportMethodChoiceList(),
        );
    }
 
    /**
     * {@inheritdoc}
     */
    public function getParent(array $options)
    {
        return 'choice';
    }
 
    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'timezone';
    }
 
}

As you can see, this references TransportMethodChoiceList, which is in

src/Acme/ProfileBundle/Form/Extension/ChoiceList/TransportMethodChoiceList.php

 
namespace Acme\ProfileBundle\Form\Extension\ChoiceList;
 
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
 
use Acme\ProfileBundle\Entity\Person;
 
class TransportMethodChoiceList implements ChoiceListInterface
{
  public function getChoices()
  {
    return array(
          Person::TRANSPORT_CAR => 'car',
          Person::TRANSPORT_TRAIN => 'train',
          Person::TRANSPORT_BIKE => 'bike',
    );
  }
}

Now I need to register this new field type as a service in my Bundle

src/Acme/ProfileBundle/Resources/config/services.xml
      <service id="form.type.transportmethod" class="Acme\ProfileBundle\Form\Extension\Type\TransportMethodType">
        <tag name="form.type" alias="transportmethod" />
      </service>

Now in my form types, I can simply do:

class PersonFormType extends AbstractType
{
 
  public function buildForm(FormBuilder $builder, array $options)
  {
 
    $builder->add('name');
    $builder->add('dateOfBirth', 'birthday');
 
    $builder->add('preferredTransportMethod', 'transportmethod');
  }

Hope that helps! :)

Comments (9) Trackbacks (2)
  1. Great, thanks for that info, but the getName method should be return a unique id like transportmethod, again thanks.

  2. Nice post! A small improvement: You can skip the TransportMethodChoiceList and directly set the “choices” option in TransportMethodType.

  3. Welcome back Ian! Any chance to see you at some symfony social event soon?

  4. Hey Ian, nice post but do you have an idea how I can use the create() method on the form.factory in order to pass my form html code a custom html attribute?

  5. Thanks, this helped me!
    I worked a little more on this and this is the yaml syntax to register the new type:

    services:
    transportmethod:
    class: Acme\ProfileBundle\Form\Extension\Type\TransportMethodType
    tags:
    – { name: form.type }

  6. Thank you dude ! You helped me so much :)

  7. Thank you!

  8. I was trying this example and throw me this error

    Fatal error: Declaration of Proyecto\ActividadesBundle\Form\Extension\Type\TransportMethodType::getParent() must be compatible with that of Symfony\Component\Form\FormTypeInterface::getParent() in /var/www/Symfony/src/Proyecto/ActividadesBundle/Form/Extension/Type/TransportMethodType.php

    In the form i’m using
    ->add(‘days’, ‘transportmethod’, array(
    ‘expanded’=>true,
    ‘multiple’ => true,
    ‘property_path’ => false,))

    Any ideas?


Leave a comment