Buy

Form Type Extension Magic

The only way to make an option valid for a field is to, well, hack the core class and add it! For example, since funFact is a TextType, I could - if I were feeling crazy - open TextType, scroll down, and hack that help option into configureOptions():

69 lines vendor/symfony/symfony/src/Symfony/Component/Form/Extension/Core/Type/TextType.php
... lines 1 - 16
use Symfony\Component\OptionsResolver\OptionsResolver;
class TextType extends AbstractType implements DataTransformerInterface
{
... lines 21 - 35
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'compound' => false,
));
}
... lines 42 - 67
}

With a few other hacks, we could have this feature working!

Form Plugins: Type Extensions

Obviously, this is not the right path to take, and that's ok, but there is another way to add an option to a field. It's called a form type extension, and it's basic a plugin to the Symfony form field system. By leveraging a form type extension, you can modify any field in the system. You could, I don't know, add a new attribute to literally every single field on your entire site.

Let's find out how.

Creating a Form Type Extension

In your Form directory, create a new directory called TypeExtension and then a new class called HelpFormExtension:

14 lines src/AppBundle/Form/TypeExtension/HelpFormExtension.php
... lines 1 - 2
namespace AppBundle\Form\TypeExtension;
... lines 4 - 6
class HelpFormExtension extends AbstractTypeExtension
{
... lines 9 - 12
}

The goal of this class will be to to allow for a help option to be passed to any field, and to turn that help option into a help variable.

First, all form type extensions should extend AbstractTypeExtension:

14 lines src/AppBundle/Form/TypeExtension/HelpFormExtension.php
... lines 1 - 2
namespace AppBundle\Form\TypeExtension;
use Symfony\Component\Form\AbstractTypeExtension;
class HelpFormExtension extends AbstractTypeExtension
{
... lines 9 - 12
}

Next, use the "Code"->"Generate" menu, or Command+N on a Mac, and click "Implement Methods". This abstract class requires us to have one method: getExtendedType():

14 lines src/AppBundle/Form/TypeExtension/HelpFormExtension.php
... lines 1 - 2
namespace AppBundle\Form\TypeExtension;
use Symfony\Component\Form\AbstractTypeExtension;
class HelpFormExtension extends AbstractTypeExtension
{
public function getExtendedType()
{
// TODO: Implement getExtendedType() method.
}
}

You see, when you create a form type extension, you could make it modify every field in your entire system, or just one type, like the FileType. To modify every field, return FormType::class:

22 lines src/AppBundle/Form/TypeExtension/HelpFormExtension.php
... lines 1 - 5
use Symfony\Component\Form\Extension\Core\Type\FormType;
... lines 7 - 9
class HelpFormExtension extends AbstractTypeExtension
{
... lines 12 - 16
public function getExtendedType()
{
return FormType::class;
}
}

Because remember, FormType is the parent for all fields.

Tip

Technically, FormType is the parent type for all fields, except for buttons. But I don't like adding buttons to my form anyways!

Overriding Field Behavior

Here's the cool thing about these classes: they have all the same functions as a normal form class, like buildForm() or configureOptions(). The difference is that whatever modifications we make to this class will literally be applied to every field in the system.

For example, go back to the "Code"->"Generate" menu, click "Override Methods", then select buildView():

22 lines src/AppBundle/Form/TypeExtension/HelpFormExtension.php
... lines 1 - 6
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
class HelpFormExtension extends AbstractTypeExtension
{
public function buildView(FormView $view, FormInterface $form, array $options)
{
... line 14
}
... lines 16 - 20
}

When we're done setting things up, whenever any field is transformed into a FormView object, this method will be called and we will be able to add variables to anything!

Try it: add $view - which represents whatever one field is being setup - $view->vars['help'] set to TURTLES!

22 lines src/AppBundle/Form/TypeExtension/HelpFormExtension.php
... lines 1 - 9
class HelpFormExtension extends AbstractTypeExtension
{
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['help'] = 'TURTLES!';
}
... lines 16 - 20
}

That's a ridiculous, and yet, fully-functional type extension.

Registering a Type Extension

To tell Symfony about this, you guys can probably guess, we need to register this as a service. In app/config/services.yml, add a new service: call it app.form.help_form_extension. Set its class to HelpFormExtension and then I'll set autowire to true... even though the class doesn't have any constructor arguments, at least not yet:

33 lines app/config/services.yml
... lines 1 - 5
services:
... lines 7 - 27
app.form.help_form_extenion:
class: AppBundle\Form\TypeExtension\HelpFormExtension
autowire: true
... lines 31 - 33

Then, to actually tell Symfony: "Hey! This is a form type extension!", add a tag, set to form.type_extension. Also give this an extended_type option. This needs to match whatever you're returning from getExtendedType(). FormType::class returns the long string in the use statement, so copy that, and paste it into your service:

33 lines app/config/services.yml
... lines 1 - 5
services:
... lines 7 - 27
app.form.help_form_extenion:
class: AppBundle\Form\TypeExtension\HelpFormExtension
autowire: true
tags:
- { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\FormType }

That's it team! Temporarily remove the help option from GenusFormType, ya know, so the page doesn't explode. Then, refresh! OMG, everyone is screaming about TURTLES! Well, everyone except for the isPublished field, because we're overriding that help variable at the last possible second: from inside the template.

Using the Option to Fuel the help Variable

Finally, uncomment the help option. So, how can we make this a valid option? Go back to HelpFormExtension, use the "Code"->"Generate" menu one last time, click "Override Methods", and select configureOptions():

30 lines src/AppBundle/Form/TypeExtension/HelpFormExtension.php
... lines 1 - 8
use Symfony\Component\OptionsResolver\OptionsResolver;
class HelpFormExtension extends AbstractTypeExtension
{
... lines 13 - 19
public function configureOptions(OptionsResolver $resolver)
{
... line 22
}
... lines 24 - 28
}

Our job here is so simple: $resolver->setDefault('help', null):

30 lines src/AppBundle/Form/TypeExtension/HelpFormExtension.php
... lines 1 - 10
class HelpFormExtension extends AbstractTypeExtension
{
... lines 13 - 19
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefault('help', null);
}
... lines 24 - 28
}

Just by doing that, you are now allowed to have a help option on any field. It also means that when buildView() is called, the $options array will have a key called help. All we need to say is if $options['help'], then set the help variable to $options['help']:

30 lines src/AppBundle/Form/TypeExtension/HelpFormExtension.php
... lines 1 - 10
class HelpFormExtension extends AbstractTypeExtension
{
public function buildView(FormView $view, FormInterface $form, array $options)
{
if ($options['help']) {
$view->vars['help'] = $options['help'];
}
}
... lines 19 - 28
}

And that takes care of it. Try this puppy out.

And consider yourself very, very dangerous.

Leave a comment!

  • 2017-07-20 Victor Bocharsky

    Hey Josh,

    Ah, those methods do the same but in a different way, very easy to miss "s" at the end :)
    I'm glad you found the problem so quick.

    Cheers!

  • 2017-07-20 Josk

    Solved, I made a stupid typo mistake (setDefaults instead of setDefault) :(

  • 2017-07-20 Josk

    Hi, I get this error at the end:
    Type error: Argument 1 passed to Symfony\Component\OptionsResolver\OptionsResolver::setDefaults() must be of the type array, string given, called in C:\Users\John\Documents\Siti\aqua_note\src\AppBundle\Form\TypeExtension\HelpFormExtension.php on line 22
    I think It shouldn't be an array because it's a value and its option, what am I doing wrong? Below my code:
    namespace AppBundle\Form\TypeExtension;

    use Symfony\Component\Form\AbstractTypeExtension;
    use Symfony\Component\Form\Extension\Core\Type\FormType;
    use Symfony\Component\Form\FormInterface;
    use Symfony\Component\Form\FormView;
    use Symfony\Component\OptionsResolver\OptionsResolver;

    class HelpFormExtension extends AbstractTypeExtension
    {
    public function buildView(FormView $view, FormInterface $form, array $options)
    {
    if ($options['help']) {
    $view->vars['help'] = $options['help'];
    }
    }
    public function configureOptions(OptionsResolver $resolver)
    {
    $resolver->setDefaults('help', null);
    }
    public function getExtendedType()
    {
    return FormType::class;
    }
    }

    Thanks in advance