Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
TRACK

Symfony 3 >

Customizing the Forms

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

What if we wanted to add our own fields to the registration form? Like, what if we needed to add a "First Name" field? No problem!

Adding a firstName field to User

Start in our User class. This is a normal entity... so we can add whatever fields we want, like private $firstName. I'll go to Code->Generate, or Command+N on a Mac, then select "ORM Annoations" to annotate firstName. Then I'll go back to Code->Generate to add the getter and setter methods.

<?php
... lines 2 - 11
class User extends BaseUser
{
... lines 14 - 20
/**
* @ORM\Column(type="string")
*/
private $firstName;
... lines 25 - 30
public function getFirstName()
{
return $this->firstName;
}
public function setFirstName($firstName)
{
$this->firstName = $firstName;
}
}

Notice, right now, firstName is not nullable... meaning it's required in the database... and that's fine! If firstName is optional in your app, you can of course add nullable=true. I'm mentioning this for one reason: if you add any required fields to your User, the fos:user:create command will no longer work... because it will create a new User but leave those fields blank. I never use that command in production anyways, but, you've been warned!

Move over to your terminal to generate the migration:

php bin/console doctrine:migrations:diff

That looks right! Run it:

php bin/console doctrine:migrations:migrate

New field added! Now, how can we add it to the form? Simple! We can create our own new form class and tell FOSUserBundle to use it instead.

Creating the Override Form

In src/AppBundle, create a new Form directory and a new class called RegistrationFormType. Extend the normal AbstractType. Then, I'll use Code->Generate Menu or Command+N to override the buildForm() method. Inside, just say $builder->add('firstName').

<?php
... line 2
namespace AppBundle\Form;
... lines 4 - 8
class RegistrationFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstName');
}
... lines 16 - 20
}

In a minute, we'll tell FOSUserBundle to use this form instead of its normal registration form. But... instead of completely replacing the default form, what I really want to do is just add one field to it. Is there a way to extend the existing form?

Extending the Core Form

Totally! And once again, the web debug toolbar can help us out. Mouse over the form icon and click that. This tells us what form is used on this page: it's called RegistrationFormType - the same as our form class!

To build on top of that form, you don't actually extend it. Instead, override a method called getParent(). Inside, we'll return the class that we want to extend. At the top, add use, autocomplete RegistrationFormType from the bundle and put as BaseRegistrationFormType to avoid conflicting.

Now in getParent(), we can say return BaseRegistrationFormType::class.

<?php
... lines 2 - 6
use FOS\UserBundle\Form\Type\RegistrationFormType as BaseRegistrationFormType;
... line 8
class RegistrationFormType extends AbstractType
{
... lines 11 - 16
public function getParent()
{
return BaseRegistrationFormType::class;
}
}

And that is it! This form will have the existing fields plus firstName.

Registering the Form with FOSUserBundle

To tell FOSUserBundle about our form, we need to do two things. First, register this as a service. In my app/config/services.yml, add app.form.registration with class set to the RegistrationFormType. It also needs to be tagged with name: form.type.

... lines 1 - 5
services:
... lines 7 - 17
app.form.registration:
class: AppBundle\Form\RegistrationFormType
tags:
- { name: form.type }

Finally, copy the class name and go into app/config/config.yml. This bundle has a lot of configuration. And at the bottom of the documentation page, you can find a reference called FOSUserBundle Configuration Reference. I'll open it in a new tab.

This is pretty awesome: a full dump of all of the configuration options. Some of these are explained in more detail in other places in the docs, but I love seeing everything right in front of me. And we can see what we're looking for under registration.form.type.

Go back to your editor and add those: registration, form and type. Paste our class name!

... lines 1 - 74
fos_user:
... lines 76 - 81
registration:
form:
type: AppBundle\Form\RegistrationFormType

And... we're done! Go back to registration and refresh. We got it! And that will save when we submit.

Customizing the Form Order

Why is firstName at the bottom? Well, remember inside of register_content.html.twig, we're using form_widget(form)... which just dumps out the fields in whatever order they were added. Need more control? Cool: remove that and instead use form_row(form.email), form_row(form.username), form_row(form.firstName) and form_row(form.plainPassword).

... lines 1 - 2
<h1>Register Aquanaut!</h1>
... line 4
{{ form_start(form, {'method': 'post', 'action': path('fos_user_registration_register'), 'attr': {'class': 'fos_user_registration_register'}}) }}
{{ form_row(form.email) }}
{{ form_row(form.username) }}
{{ form_row(form.firstName) }}
{{ form_row(form.plainPassword) }}
... lines 10 - 12
{{ form_end(form) }}

If you're not sure what the field names are called, again, use your web debug toolbar for the form: it shows you everything.

Refresh that page! Yes! First Name is exactly where we want it to be. Next, what about the username? Could we remove that if our app only needs an email?

Leave a comment!

17
Login or Register to join the conversation
Default user avatar

Hello I have one question here. Can I add and how can I add custom function to form in customType class.

My goal: I would like to be able to do something like

$myForm->myCustomFunction()

I have tried to add it directly in customType class but when I try to use it in controller like I did above I get not existing method name error. Is that possible?

Regards,
Rob

1 Reply

Yo Robert!

Yea... as you discovered, you cannot do this. The problem is that your "type" class is kind of a "recipe". And when you call createForm(), the form component reads your recipe and creates a Form object from it. At that point, your *Type is discarded. The correct solution is to put this method somewhere else... and the right spot depends on what you want to do - common options are a private method in your controller, method in your entity or a service class. If you want a suggestion, let me know what the method does and I'll at least tell you where I would put it :).

Cheers!

1 Reply
Default user avatar

Hi Ryan,

Thanks for such quick feedback. I though that might not be the right place.

So I am trying to create convinient error wrapper. When you call $myForm->getErrors() you have to make couple of loops and a little bit of additional work to get errors. I would like to be able to call $myForm->getJsonErrors() and couple simillar methods. This would be my custom functionality shared accros all the forms.

I would like to know if I can do this "From within the form" or I have to use it as separate service and then pass data to that service? I have this second solution working but I am really keen to make first one work.

Can I or should I override main form class?

Cheers!

1 Reply

Yo Robert!

Yea, the form errors are a huge pain when you're working with an API :/. So, I feel you on that one :). But yea, the proper solution is what you have: to isolate this out to a service. But, to make it even simpler, I usually create my own base controller and add some shortcut method there, so that I can do something like $errors = $this->getJsonErrors($form). A service is really the best way to go, and together with that shortcut, it's got the "ease" of putting it on the form class (hopefully).

Cheers!

Reply
Default user avatar

Hi Ryan,

Thank you for confirming that. I will definitely go with that solution. Thanks for you time and effort!

Regards,
Rob

5 Reply
Default user avatar

Hi,
How to do in symfony 4.4?
Its not same structure. (services, etc)
Thank's

Reply

Hey @Medah

I believe it works the same, the only thing that changed is the location of FOSUserBundle configuration. If you installed the bundle using Symfony Flex, then you should have a fos_user.yaml file inside config/packages, just add the config logic there, and that should be it.

Cheers!

Reply
Default user avatar

Hi Ryan! thanks a lot for this recipy. I need to do two form types, can i define them with fosuserbundle? I already have one, but what if i want to add another formtype?

Reply

Hey Vadino,

You don't need FOSUserBundle to define your form types - you should do it in your project. Actually, we do exactly the same here - created the form type in this project, but just "based" it on the FOSUserBundle's form type and that's it. But probably it depends on what exactly form type you're talking about.

Cheers!

Reply

Hi Ryan, how can I add a non-mapped field to the login form of FOSUserBundle? The bundle doesn't seems to have a "LoginFormType" class?

Reply

Hey Elvism,

FOSUserBundle does not use Symfony Form for login form, see https://github.com/FriendsO... . So you can override this template to modify it.

Cheers!

Reply
Default user avatar
Default user avatar Luke Oliver | posted 5 years ago

When I run the doctrine:migration:migrate command it comes back with an error: 'User table or view already exists'

Reply

Hey Luke

Hm, that's interesting, probably something went wrong. If we're talking about your project during development (NOT in production!), I'd recommend you to drop your DB and create it again with:
bin/console doctrine:database:drop
bin/console doctrine:database:create

And then, if you use migrations from scratch, run them again with:
bin/console doctrine:migration:migrate

This command will create a new table in your DB named "migration_versions" (if you haven't changed its name in config) which will start tracking your migrations. Also, please make sure you don't have an entity which conflicts with this table name.

Cheers!

Reply
Default user avatar
Default user avatar Gunnar Polte | posted 5 years ago

Hey Ryan,

great tutorial.
I have one question. I like to remove the 'username' field in the form. How can I do this?
I thought just to remove getParent() function in RegistrationFormType, but that gives an error:

Cannot read index "firstName" from object of type "AppBundle\Entity\User" because it doesn't implement \ArrayAccess.

Any ideas?

Also I did your sucurity tutorial with guard. But I need a password forgotten link and a user admin area. So I thought may be FOSUserBundle would be better for that.
What do you think?

Reply
Default user avatar

I just saw, the next chapter is just about that.

Reply

Haha, glad you found it! Sorry to confuse you temporarily! :D

Reply

Btw, in a different thread, I just replied that you might want to NOT use FOSUserBundle. Obviously, if it's working well for you, use it! But, as I said in that other comment, if it's a pain, then it may not be worth using it, just to get free "reset password" functionality.

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.3.*", // v3.3.18
        "doctrine/orm": "^2.5", // v2.7.0
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.5
        "symfony/swiftmailer-bundle": "^2.3", // v2.5.4
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.3.0
        "sensio/distribution-bundle": "^5.0", // v5.0.18
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.25
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "knplabs/knp-markdown-bundle": "^1.4", // 1.5.1
        "doctrine/doctrine-migrations-bundle": "^1.1", // v1.3.2
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "friendsofsymfony/user-bundle": "^2.0" // v2.0.0
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.4
        "symfony/phpunit-bridge": "^3.0", // v3.2.7
        "nelmio/alice": "^2.1", // v2.3.1
        "doctrine/doctrine-fixtures-bundle": "^2.3", // v2.4.1
        "symfony/web-server-bundle": "^3.3"
    }
}
userVoice