Buy

I know, I know, everyone loves it when the form system changes and breaks everything. Well, get ready: there are more changes in Symfony 2.8. And at first, they're a little shocking. But I think you'll like them: it's a debateable step towards simplification.

Let's create a registration form using this mysterious new stuff. Add a Form directory and create a RegistrationForm class inside. PhpStorm gives me a nice skeleton for the class.... but now it has too much. Remove the getName() method at the bottom:

21 lines src/AppBundle/Form/RegistrationForm.php
... lines 1 - 2
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class RegistrationForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
}
public function configureOptions(OptionsResolver $resolver)
{
}
}

This was always a useless, but required function. But no more!

For the most part, forms look the same. In configureOptions() call $resolver->setDefaults() and pass it data_class set to AppBundle\Entity\User:

24 lines src/AppBundle/Form/RegistrationForm.php
... lines 1 - 16
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'AppBundle\Entity\User'
]);
}
... lines 23 - 24

My super-simple user has just one field on it: username, which makes this one of the most ridiculously useless registration forms in history:

56 lines src/AppBundle/Entity/User.php
... lines 1 - 11
class User implements UserInterface
{
... lines 14 - 20
/**
* @ORM\Column(type="string")
*/
private $username;
... lines 25 - 30
public function getUsername()
{
return $this->username;
}
... lines 35 - 50
public function setUsername($username)
{
$this->username = $username;
}
}

Form types as Class Names!

Add our one field with $builder->add('username'). But stop! Here is where things are different. Before I would have passed text as the second argument. But now, I'm going pass the full class name to the class that's behind the text field type.

To make things easier, use a shortcut: TextType::class:

24 lines src/AppBundle/Form/RegistrationForm.php
... lines 1 - 5
use Symfony\Component\Form\Extension\Core\Type\TextType;
... lines 7 - 9
class RegistrationForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('username', TextType::class);
}
... lines 16 - 22
}

PhpStorm doesn't auto-complete classes when you do this... yet. But if you focus on the class, a light-bulb will allow you to import the class. Or hit option+enter on a Mac to bring it up immediately. This adds the use statement. Using ::class is new in PHP 5.5... so this change won't be much fun unless you can use this. But, you can keep using the old syntax until you switch to 3.0... which requires 5.5 anyways.

Phew! That's big thing number one: instead of weird strings, we have full class names. This sucks because... well, this is a little bit more typing. But this is sweet because a class name is more meaningful than a string like text. If this was your first time using Symfony, you'd have a better chance of understanding how things work. And I really like that.

Create your Form: Still a Class Name

Time to use this! Open DefaultController and create a new registerAction(). Set the URL to /register call it user_register:

60 lines src/AppBundle/Controller/DefaultController.php
... lines 1 - 2
namespace AppBundle\Controller;
... lines 4 - 9
class DefaultController extends Controller
{
... lines 12 - 42
/**
* @Route("/register", name="user_register")
*/
public function registerAction(Request $request)
{
... lines 48 - 57
}
}

Now, start just like normal with: $form = $this->createForm(). But stop! I know you want to say new RegistrationForm(). Instead, type RegistrationForm::class. Then, move your cursor to the class, hit option+enter and import the class:

60 lines src/AppBundle/Controller/DefaultController.php
... lines 1 - 4
use AppBundle\Form\RegistrationForm;
... lines 6 - 9
class DefaultController extends Controller
{
... lines 12 - 45
public function registerAction(Request $request)
{
$form = $this->createForm(RegistrationForm::class);
... lines 49 - 57
}
}

Oh and change type to class, duh! Importing the class added the use statement on top.

So wow, not only do we not use magic strings anymore, we also never pass objects. It's perfectly consistent: we always refer to forms and fields using the full class name. There's an important consequence: the RegistrationForm will be created and passed no constructor arguments.

Form Type Constructor Args?

If you need to pass something to that object, you have two options - neither of which are new. First, you can pass stuff through the $options array:

24 lines src/AppBundle/Form/RegistrationForm.php
... lines 1 - 11
public function buildForm(FormBuilderInterface $builder, array $options)
... lines 13 - 24

The third argument to createForm():

60 lines src/AppBundle/Controller/DefaultController.php
... lines 1 - 47
$form = $this->createForm(RegistrationForm::class);
... lines 49 - 60

Second, you can still - like always - register your form type as a service. If RegistrationForm has constructor args, that's the real answer: register it as a service and tag it with form.type. You'll still refer to it via the class name, but Symfony will use your service instead of creating a new object.

Add $request as an argument and then add the normal $form->handleRequest($request). if ($form->isValid()), for now dump($form->getData()); and put a die statement to celebrate:

60 lines src/AppBundle/Controller/DefaultController.php
... lines 1 - 9
class DefaultController extends Controller
{
... lines 12 - 45
public function registerAction(Request $request)
{
$form = $this->createForm(RegistrationForm::class);
$form->handleRequest($request);
if ($form->isValid()) {
dump($form->getData());die;
}
... lines 54 - 57
}
}

Finally, at the bottom, return $this->render('default/register.html.twig'). Pass it the form with 'form' => $form->createView(),:

60 lines src/AppBundle/Controller/DefaultController.php
... lines 1 - 54
return $this->render('default/register.html.twig', [
'form' => $form->createView()
]);
... lines 58 - 60

Simple enough and the same as always!

In app/Resources/views/default, create that register.html.twig template. I'll copy in some boilerplate code for this form:

11 lines app/Resources/views/default/register.html.twig
{% extends 'base.html.twig' %}
{% block body %}
<h1>You should signup!</h1>
{{ form_start(form) }}
{{ form_widget(form) }}
<button type="submit" class="btn btn-default">Register!</button>
{{ form_end(form) }}
{% endblock %}

None of this has changed. This opens the form tag, dumps out all the fields, adds a submit button and closes the form.

Time to test it! Head to /register, throw in our username: leannapelham and hit register. There's our dump!

Here's the moral of the story: forms work exactly like before. But now, form classes are always referred to by their class name: never objects and never the short, but weird magic string from the past.

This might be the biggest change in Symfony 3 that people will complain about, and rightly so: if you have a lot of forms, this is a lot of changes. For that, check out the Symfony Upgrade Fixer: a library that can automate some of the tasks of upgrading, including this one. Oh, and it's made by my friend Saša, and he's a really cool dude.

Leave a comment!

  • 2016-01-13 SymfonyEnthusiast

    Thank you! It worked :)

  • 2016-01-10 weaverryan

    Absolutely - that hasn't changed in Symfony 3 :). Like before, just make sure that you do *not* set a `data_class` option in your form and that you do *not* pass an object when creating your form.

    Cheers!

  • 2016-01-08 SymfonyEnthusiast

    Is it possible to build forms without entities attached to them in Symfony3?