Rock some FOSUserBundle!

The most popular bundle in all of Symfony is... GifExceptionBundle! Wait... that's not right... but it should be. The actual most popular bundle is, of course, FOSUserBundle. And it's easy to know why: it gives you a lot of crazy-cool stuff, out-of-the-box: registration, reset password, change password, and edit profile pages. Honestly, it feels like stealing.

But guess what? A lot of smart devs don't like FOSUserBundle. How could that be? The bundle does give you a lot of stuff. But, it's not a magician: it doesn't know what your design looks like, what fields your registration form should have, the clever text you want in your emails or where to redirect the user after registration.

See, there's a lot of stuff that makes your site special. And that means that if you're going to use FOSUserBundle - which is awesome - you're going to need to customize a lot of things. And you've come to the right place: once we're done, you're going to be embarrissingly good extending this bundle. I mean, your co-workers will gaze at you in amazement as you hook into events, customize text and override templates. It's going to be beautiful thing.

Let's do it!

Code along with me yo!

As always, you should totally code along with me... it's probably what the cool kids are doing. Just click the download button on this page and unzip that guy. Inside, you'll find a start/ directory that has the same code you see here. Open up README.md for hilarious text... and setup details.

The last step will be to find your favorite terminal and run:

php bin/console server:run

to start the built-in PHP web server. Ok, load this up in your browser: http://localhost:8000.

Welcome to AquaNote! This is the same project we've been building in our main Symfony tutorials, but without any security logic. Gasp! See that Login link? It's a lie! It goes nowhere! The login link is a lie!

Google for the FOSUserBundle documentation: it lives right on Symfony.com. And make sure you're on the 2.0 version.

Installing & Enabling the Bundle

We'll go through the install details... but in our own order. Of course, first, copy the composer require line... but don't worry about the version number. Head over to your terminal and run that:

composer require friendsofsymfony/user-bundle

While we're waiting for Jordi to prepare out delicious FOSUserBundle package, go back and copy the new FOSUserBundle() line from the docs, open our app/AppKernel.php file and paste it to enable the bundle. Oh, and FOSUserBundle uses SwiftmailerBundle to send the password reset and registration confirmation emails. So, uncomment that. You can also create your own custom mailer or tell FOSUserBundle to not send emails.

58 lines app/AppKernel.php
<?php
... lines 2 - 5
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
... lines 11 - 14
new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
... lines 16 - 21
new FOS\UserBundle\FOSUserBundle(),
... lines 23 - 24
);
... lines 26 - 35
}
... lines 37 - 56
}

Ok, flip back to your terminal. Bah! It exploded! Ok, it did install FOSUserBundle. So, let's not panic people. It just went crazy while trying to clear the cache:

The child node db_driver at path fos_user must be configured

Ah, so this bundle has some required configuration.

Create your User Entity

Before we fill that in, I have a question: what does FOSUserBundle actually give us? In reality, just 2 things: a User entity and a bunch of routes and controllers for things like registration, edit password, reset password, profile and a login page.

To use the User class from the bundle, we need to create our own small User class that extends their's.

Inside src/AppBundle/Entity, create a new PHP class called User. To extend the base class, add a use statement for their User class with as BaseUser to avoid a lame conflict. Then add, extends BaseUser.

26 lines src/AppBundle/Entity/User.php
<?php
... line 2
namespace AppBundle\Entity;
... lines 4 - 5
use FOS\UserBundle\Model\User as BaseUser;
... lines 7 - 11
class User extends BaseUser
{
... lines 14 - 24
}

There's just one thing we must do in this class: add a protected $id property. Beyond that, this is just a normal entity class. So I'll go to the Code->Generate menu - or Command+N on a Mac - and choose ORM Class to get my fancy @ORM\Entity stuff on top. Add ticks around the user table name - that's a keyword in some database engines.

26 lines src/AppBundle/Entity/User.php
<?php
... lines 2 - 4
use Doctrine\ORM\Mapping as ORM;
... lines 6 - 7
/**
* @ORM\Entity
* @ORM\Table(name="`user`")
*/
class User extends BaseUser
{
... lines 14 - 18
protected $id;
... lines 20 - 24
}

Now, go back to Code->Generate, choose ORM Annotation and select the id column. Boom! We are annotated! Finally, go back to Code->Generate one last time... until we do it more later - and generate the getId() method.

26 lines src/AppBundle/Entity/User.php
<?php
... lines 2 - 11
class User extends BaseUser
{
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Column(type="integer")
*/
protected $id;
public function getId()
{
return $this->id;
}
}

This class is done!

Tip

Actually, this is unnecessary - the base User class already has a getId() method.

Adding the Required Configuration

And now we have everything we need to add the required configuration. In the docs, scroll down a little: under the Configure section, copy their example config. Then, back in your editor, open app/config/config.yml, and paste this down at the bottom.

Ok, The db_driver is orm and the firewall_name - main - is also correct. You can see that key in security.yml.

And yea, the user_class is also correct. We're crushing it! For the email stuff, it doesn't matter, use hello@aquanote.com and AquaNote Postman.

82 lines app/config/config.yml
... lines 1 - 74
fos_user:
db_driver: orm
firewall_name: main
user_class: AppBundle\Entity\User
from_email:
address: "hello@aquanote.com"
sender_name: "AquaNote Postman"

Generate the Migraiton

Finally, our app should be un-broken! Try the console:

php bin/console

It's alive! Now, we can generate the migration for our User class:

php bin/console doctrine:migrations:diff

Tip

If you get a "Command not Found" error, just install the DoctrineMigrationsBundle.

Yep, that looks about right. Run it:

php bin/console doctrine:migrations:migrate

Perfect!

Importing the Routing

At this point, the only thing this bundle has given us is the base User class... which is nice, but nothing too special, it has a bunch of properties like username, email, password, lastLogin, etc.

The second thing this bundle gives us is a bunch of free routes and controllers. But to get those, we need to import the routes. Back in the documentation, scroll down a bit until you see step 6: Import FOSUserBundle routing files. Ah ha! Copy that routing import.

Find your app/config/routing.yml file and paste that on top.

12 lines app/config/routing.yml
... lines 1 - 9
fos_user:
resource: "@FOSUserBundle/Resources/config/routing/all.xml"

As soon as you do that, we have new routes! At your terminal, check them out:

php bin/console debug:router

Awesome! We have /login, /profile/edit, /register and others for resetting and changing your password. If we manually go to /register in the browser... yea! A functional registration form. I know, it's horribly, embarrassingly ugly: we'll fix that.

Oh, and back on the docs, all the way at the bottom, there's a page about Advanced routing configuration. Open that in a new tab. In your app, you may not need all of the pages that FOSUserBundle gives you. Maybe you need registration and reset password, but you don't need a profile page. No problem! Instead of importing this all.xml file, just import the specific routes you want. Seriously feel free to do this: if some route or controller isn't helping you, kill it.

Enabling the Translator

Oh, and if your registration page doesn't look mine - if it has some weird keys instead of real text, don't worry. In app/config/config.yml, just make sure to uncomment the translator key under framework.

FOSUserBundle uses the translator to translate internal "keys" into English or whatever other language.

A little bit of Security

And basically... at this point... we're done installing the bundle! But how is that possible? We haven't touched anything related to security!

Here's the truth: this bundle has almost nothing to do with security: it just gives you a User class and some routes & controllers! We could already register, reset our password or edit our profile without doing any more setup.

Well, that's almost true: we do need a tiny bit of security to make registration work. In security.yml, add an encoders key with AppBundle\Entity\User set to bcrypt. When we register, FOSUserBundle needs to encode the plain-text password before saving it. This tells it what algorithm to use.

32 lines app/config/security.yml
... lines 1 - 2
security:
encoders:
AppBundle\Entity\User: bcrypt
... lines 8 - 32

There's one other small bit of security we need right now. In the documentation, under step 4, copy the providers key. Paste that over the old providers key in security.yml.

32 lines app/config/security.yml
... lines 1 - 2
security:
... lines 5 - 7
# http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
providers:
fos_userbundle:
id: fos_user.user_provider.username
... lines 12 - 32

I'll talk about this more in a few minutes, but it's needed for registration only because FOSUserBundle logs us in after registering... which is really nice!

In the next chapter, we'll do more security setup. But for right now, that's all we need!

So try it out! Refresh /register. Let's use aquanaut1@gmail.com, aquanaut1, and password turtles. And boom! We are registered and logged in! We even have a role: ROLE_USER. More on that later.

With a User class, a route import and a tiny bit of security, everything in the bundle works: registration, reset password, edit password and edit profile.

The only thing you can't do is log out... or login. But that's not FOSUserBundle's fault: setting up security is our job. Let's do it next.

Leave a comment!

  • 2017-09-14 Алексей Хромец

    Ok, thank you a lot!

  • 2017-09-14 Victor Bocharsky

    Hey Alexey,

    Unfortunately, we do not have exactly this screencast, but I have a few link where you can find useful for you information:
    - https://knpuniversity.com/s... - here's how you can send forms via AJAX.
    - https://knpuniversity.com/s... - here's how you can handle an AJAX response.

    So along the next screencast https://knpuniversity.com/s... you can create and render your login/register forms, but then intercept their sending with JS and send data with AJAX to the controller where you handle the request and return a JSON response for example which will say was the login/register successful or form has some errors. You can also do it along FOSUserBundle https://knpuniversity.com/s... , but probably you'll need to customize some third-party code by overriding it because FOSUserBundle does not work via AJAX out-of-the-box, so it could be more complex because you'll need to customize their actions, or write completely new ones.

    Cheers!

  • 2017-09-14 Алексей Хромец

    Where can I learn more about ajax login/register (in modal windows)?

  • 2017-06-23 Diego Aguiar

    Exactly! Let's focus in what's more relevant to the project, if you can fill any gap with third party bundles, just do it :)

  • 2017-06-23 Robert Went

    Thank Diego,
    I think that's probably the way I'm going to go.
    From the Symfony 3 track, it seemed pretty easy to roll your own, but when I think about all the extra bits like email notifications and forgotten passwords I think it's probably going to take more time than I initially thought. Time that could go into the actual main point of the app :)

  • 2017-06-23 Diego Aguiar

    Hey Robert

    I went through the same case and I ended using FOSUserBundle, because it gives you a lot of free functionalities, and if anything you don't like it, you can override it without too much problems, but, if what you want is to learn things, you should build your own

    Cheers!

  • 2017-06-23 Robert Went

    Hey Ryan,

    I've been looking into FOSUserBundle regarding the different opinions about using it. There's some intense views out there!

    In some conversations the KNPuGuard was mentioned (Which i assume was you guys) and that it is now part of Symfony 3 and that FOSUserBundle isn't really needed anymore.

    Sooo, my question really is, should I stick with the Symfony 3 track to build my own, or go with the bundle and learn how to override and extend? (As a noob, wanting to get going, but also taking the right routes at the start if possible!)

  • 2017-06-15 Robert Went

    Hi Ryan,
    I did install the FOSUserBundle and go through all the steps, it just errored out when posting the form.
    I posted a reply above about it working fine with the blank project. It seems to be something specific with the sample data and my server.
    I had another go with the start folder since then and tried various things like updating different packages one by one to match the symfony installer versions, changing session save path to match and other diferences I could see, but it always gves the same errors so I'm totally stumped.
    Need to move on now though.

  • 2017-06-15 weaverryan

    Let us know what you find out! It's weird because - if the bundle is not installed in composer or in AppKernel, then you won't even be able to get to /register at all. The fact that you could get there... but it failed on submit... that's the strange part ;).

    Cheers!

  • 2017-06-15 Robert Went

    I installed a blank project (3.3) with the symfony installer (and added doctrine/doctrine-cache-bundle) and then went through the exact same steps and registration works as expected, the user is added to the database with an encrypted password.

    I just started again from scratch with the start folder of the project files taking the same steps and got the same errors again when trying to register.

    I'm not really sure where the problem is, but I'm going to try and continue the course using the blank project.

  • 2017-06-15 Robert Went

    Hi,

    Those are the exact steps I took with the finish folder. Just installed, not updated (Although I did try updating later on but the error wwas the same).
    I'm running php 7.1.3, same error in prod
    Just about to try the same steps with a blank symfony project and see if that's the same.
    I had no issues with the security chapter of the symfony 3 course.

  • 2017-06-15 Victor Bocharsky

    Hey Robert,

    Hm, most likely this error is due to unfired event listener from FOSUserBundle which encodes plain password and sets the encoded password with User::setPassword() (IIRC the password is encoded and set in event listener in FOSUserBundle). Do you get this error in the prod environment? Because to update configuration you need to clear the prod cache after you install and configure the FOSUserBundle. You said you had got the same error with our downloaded Course Code. Did you update our code from the finish/ directory to Symfony 3.3 ? Because I can't reproduce it by myself: I downloaded the Course Code, went to the finish/ dir, installed composer dependencies, created DB, ran migrations, loaded fixtures, ran the Symfony web server, then went to /register page in dev environment and registered new user without any errors. Im I missing some steps? And one more question, what PHP version do you use? I tested it on PHP 7.1.5.

    Cheers!

  • 2017-06-14 Robert Went

    I'm getting an error when trying to register a user. It looks like the password fiedls aren't getting registered:

    An exception occurred while executing 'INSERT INTO `user` (username, username_canonical, email, email_canonical, enabled, salt, password, last_login, confirmation_token, password_requested_at, roles) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' with params ["aquanaut1", "aquanaut1", "aquanaut1@gmail.com", "aquanaut1@gmail.com", 1, null, null, null, null, null, "a:0:{}"]:

    SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'password' cannot be null

    I noticed in composer.json that the user bundle wasn't registered, but after running the install command again I got green lights in the console and it was in the required section of composer.json.

    Google's not being too helpful. Has anyone else seen this error?

    Update: I'm getting the same with the finish folder in the sample code.
    I'm assuming it's something to do with php7.1/zendserver but I can't test anywhere else at the moment :(

  • 2017-05-09 Diego Aguiar

    Hey Islam!

    When does that happens ? Could you show me the stack trace or a bit more about the error you are getting ?
    I think you are just missing a library, try running "composer install" again

    Cheers!

  • 2017-05-09 Islam Elshobokshy

    Can't find ORM Class in the generate menu :(

  • 2017-04-28 Victor Bocharsky

    Hey Kim,

    You're right, the command is valid. But did you install the DoctrineMigrationsBundle properly? :) There're a few steps you need to do to install it by following that link:
    1. Require this bundle with "$ composer require doctrine/doctrine-migrations-bundle"
    2. Add "new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle()," line to the app/Kernel.php file to register the bundle.

    And then "./bin/console doctrine:migrations:diff" should work well.

    Cheers!

  • 2017-04-28 Kim Morse

    Following along with the new fosuserbundle tutorial. The command .'/bin/console doctrine:migrations:diff' throws a command not found exception. The note says to install the DoctrineMigrationsBundle. Following this link the said mentioned command is again listed and results in the same error. Help I'm stuck in an endless loop LOL.