gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
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!
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.
... 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.
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')
.
... 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?
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
.
... 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
.
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.
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?
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!
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!
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!
Hi Ryan,
Thank you for confirming that. I will definitely go with that solution. Thanks for you time and effort!
Regards,
Rob
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!
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?
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!
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?
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!
When I run the doctrine:migration:migrate command it comes back with an error: 'User table or view already exists'
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!
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?
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!
// 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"
}
}
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