Do not use FOSUserBundle

Update March 2017: Great news everyone, FOSUserBundle 2.0 is finally released. This does not fix the issues mentioned in this article though, like the possibility to use email as User identity.

Update December 2016: I invite you to checkout my 2016 SymfonyCon presentation about FOSUserBundle for more up to date informations.


Or at least, do it knowing what it really costs you.

Under this trolling title is something I wanted to talk about for a while. It’s about PHP dependencies and especially bundles in the Symfony world.

TL;DR: This snippet tells a lot about why I don’t use “quick win / do everything” bundles systematically anymore:

composer require technical-debt-bundle

And this does not apply only to FOSUserBundle. You add technical debt on every composer require. It’s completely normal but you have to know there is debt before being able to do something about it [FR].

Installing FOSUserBundle

When starting a Symfony project for a common web application, one of the first thing you need is a way to store users and manage authentication.

Reading the documentation and online tutorial, you are definitely going to install the most popular Symfony bundle of all: FOSUserBundle. And it’s not only a beginner thing, we are all doing it.

This is a big and quick win, because you get:

  • an User entity with sensible default fields, compatible with Doctrine / Propel / ODM;
  • a register and profile form;
  • an email validation for registration and a forgot password form;
  • some commands to add / edit users…

Everything else is already in Symfony:

  • remember me authentication;
  • login form and user provider;
  • impersonate users;
  • permission based access list…

It looks like you are going to save some time if you have to manage users. And that may be true.

The downsides

Let’s speak about the real issue of using a generic user management tool – again, this article uses FOSUserBundle as an example but, really, it’s about common issues with generic bundles trying to solve everything for everyone.

Identity of users

The first one is really about identifying an User. The Bundle use by default a username and password method. Most of the website I make are not using an username at all. I do websites for e-commerce, b2b, intranet… where people are not anonymous but instead referred to by first name and last name – and use an email as login – this is really common.

So the first thing I need to do is to switch the authentication method to use email instead of username. This can’t really be done in fact, all you can do here is using the fos_user.user_provider.username_email provider. Which is going to search for both the username and the email. Generally, I write my own provider.

This is something people are asking for since 2012, and they came up with a lot of solutions… Like storing uniqid() in the username field, or copying the email in the username field… just because we need to fill it and there is no way to just remove it.

Entities created by the bundle are going to have fields you will never use:

  • salt: not needed with the bcrypt encoder, which is standard now;
  • some booleans and dates like locked, expired, credentials_expired…;
  • username_canonical;
  • username.

You can’t remove the username fields, you can’t leave them empty (NOT NULL) and you can’t put garbage in it (Unique constraint on username_canonical). Here is the full list of what’s needed to deal with this username feature: 2 new classes, a hack in a setter, a new validation file and some configuration. Ouch.

Templates, template everywhere

By default, all the views provided by the bundle extends layout.html.twig. As you certainly have some kind of navigation and rich layout on your website, you must create a app/Resources/FOSUserBundle/views/layout.html.twig file with a fos_user_content block.

What I do most of the time is make this file extend my own layout, with something like that:

{% block content %}
    {% block fos_user_content %}{% endblock fos_user_content %}
    {{ parent() }}
{% endblock content %}  

It’s not very difficult, but again, I’m creating files doing nothing relevant, it’s just glue. Very fast, you will be copying and pasting templates from the bundle in your Resources folder, sometimes for nothing but a new paragraph, or a new class on a button.

Want to add a field? You have to write FormTypes yourself

You will need to add some fields on your User class, obviously (like a firstName / lastName). This is easy as the entity is yours.

But if those fields must also be added in your Profile and Registration forms, then you have to create some more classes:

  • a new FormType with fos_user_registration as parent;
  • a new service to tag this FormType as the app_user_registration form.
  • and the same goes for the ProfileFormType.

Again, I feel my time is getting robbed, as soon as you expect more than the defaults, you have to write the code you were supposed to enjoy not writing by using the bundle.

Split login form for Admin and Clients

This is one of the most hacky stuff you could do. The need is simple, you just want a way for admins to have their own login form (maybe with a captcha & some browser additional checks), at the gates of the Admin area, while customer use another one, with a different skin.

As you can guess, this is not supported, so we will need:

  • custom routes for everything;
  • a new firewall and new access_control entries (that’s normal);
  • a new SecurityController extending the bundle one, with some if to know what template to render;
  • and of course the new template.

You can learn more on those tweaks by reading this SO answer.

Admin and customers entities not always the same

Let’s go a step further: we have two types of users in our application, Customers and Sellers. Each one with a specific registration workflow, login form, and permissions.

With Symfony it’s easy, you just setup two user providers; with the bundle, you are stuck. A lot of things are hard-coded, and only done for applications with one type of user. Hopefully there is another bundle which extends FOSUserBundle to do exactly that.

It’s just ~10 more classes, listeners and services to trust and add in your project.

The cost of “big win” Bundles

So far, to have a typical customized experience with FOSUserBundle, we have written:

  • a custom user provider for email only login;
  • new templates for every views;
  • new translation file;
  • new Registration and Profile FormType to add and remove fields;
  • new services…

Our app/Resources folder is messy with specific code for the Bundle:

app/Resources
├── FOSUserBundle
│   └── views
│       ├── Registration
│       │   ├── checkEmail.html.twig
│       │   ├── confirmed.html.twig
│       │   └── register.html.twig
│       ├── Resetting
│       │   ├── checkEmail.html.twig
│       │   ├── request.html.twig
│       │   └── reset.html.twig
│       └── Security
│           └── login.html.twig
├── translations
│   ├── messages.fr.yml
│   ├── FOSUserBundle.fr.yml
│   └── validators.fr.yml

We spent time writing those customization, and we are going to keep those files for the whole project lifetime. Updating the bundle becomes a tedious task as compatibility with our changes has to be checked. And all the time we won with not having to re-invent the wheel is really not that much.

As soon as requested features are a bit different from what a bundle exposes, you are going to have a hard time: extending bundles is not always easy, and bundle implementation just can’t make everything an option, it would lead to more madness!

This is not something to blame on the bundle itself, FOSUserBundle is not a framework to manage users, it’s an implementation, a good one, but maybe not the one you have to implement for your projects.

Some feature commonly known or wrongly attributed to FOSUserBundle are really just one cookbook away.

The art of choosing a Bundle

Back in 2013, I talked about how to choose a bundle at Symfony Live Paris [FR], of course FOSUserBundle was recommended, and I still stand behind this claim. Then, I talked about maintenance, number of issues, popularity… I missed something big: technical debt and functional compatibility with your needs!

Next time you start a project, please think twice before using any bundle:

  • is it maintained? Is it up to date? Can I contribute?
  • what does it do and how easily can it be changed?
  • what’s the real added value? Sometimes bundles are just a library exposed as a service, avoid those!
  • how long it will take to implement the needed feature myself versus how long it will take to customize?

There is something like 5000 lines of code in FOSUserBundle, and I really don’t want to rewrite the wheel on every project. But I do not want a tweaked 5000 lines technical debt I didn’t write to build my projects on neither.

I wish bundles like FOSUser act more as a User Management Framework rather than trying to implement everything. There is some really great code in it, and it can be used as an example to build your own system.

So use bundles wisely and with great care, people. With great bundles come great responsibilities.

Nos formations sur le sujet

  • Symfony

    Formez-vous à Symfony, l’un des frameworks web PHP les plus connus au monde

  • Symfony avancée

    Décou­vrez les fonc­tion­na­li­tés et concepts avan­cés de Symfo­ny

blog comments powered by Disqus