Customizing FOSUserBundle

Recently I was working on a project with FOSUserBundle. It was best choice instead of implementing the whole user system from scratch. Unfortunately, after a while it became clear, that the bundle doesn’t fulfil all of my requirements and therefore I had to make some modifications. I’ve found the solutions not to be totally obvious, so I decided to write it together in this blog post. Another post on FOSUserBundle is my tutorial how to change the password constraints in FOSUserBundle.

Note: This post is about the 1.3 version of FOSUserBundle. Version 2.0 is not stable yet, but it has much better extensibility.

The first thing to do, when extending the bundle, is to create a new bundle, which extends the original FOSUserBundle. This will enable you to overwrite controllers and configuration files with your own implementation. I’ll call it Acme\UserBundle in the following examples.

Change redirect targets

I wanted to change the redirection after some actions. For example I wanted to redirect back to the “edit profile” page after saving the changes and simply show a flash message on the top. The bundle has a protected method called getRedirectionUrl in the controller, which can be modified by overwriting the controller.

namespace Acme\UserBundle\Controller;

use FOS\UserBundle\Controller\ProfileController as BaseProfileController;
use FOS\UserBundle\Model\UserInterface;

class ProfileController extends BaseProfileController
{
    protected function getRedirectionUrl(UserInterface $user)
    {
        // Change the redirection target after saving the profile
        return $this->container->get('router')->generate('fos_user_profile_edit');
    }
}

Most of the controllers have methods like this, but the RegistrationController doesn’t. When I wanted to change the target URL after successful registration, I couldn’t do it. It wasn’t possible, because the route names are hard-coded in the controller action and they’re not encapsulated in protected methods.

So I had to implement a little hack. My solution was to extend the controller and decorate the registerAction with some extra code, which checks for a RedirectResponse to be returned. This is only the case when registration was successful. Then I simply replaced the response object with a different RedirectResponse instance redirecting to my target URL. This isn’t very pretty, but at least it works.

namespace Acme\UserBundle\Controller;

use FOS\UserBundle\Controller\RegistrationController as BaseRegistrationController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;

class RegistrationController extends BaseRegistrationController
{
    public function registerAction()
    {
        $response = parent::registerAction();

        // Redirect to welcome page instead
        if ($response instanceof RedirectResponse) {
            $url = $this->container->get('router')->generate("_welcome");
            return new RedirectResponse($url);
        }

        return $response;
    }

Overwrite flash messages

I did not want to use the flash message type, which have been pre-defined by the bundle. Instead I wanted to use my own types named “success”, “error” and “notice”. Fortunately the FOS ones are named the same way, but prefixed fos_user_, so I only had to get rid of that prefix. In every controller there is a protected method setFlash($action, $value), which receives the message type (action) and the message itself (value). By overwriting the method I was able to remove the prefix. For me this was enough, but you can use that method to do any message-mapping work required.

namespace Acme\UserBundle\Controller;

use FOS\UserBundle\Controller\ProfileController as BaseProfileController;

class ProfileController extends BaseProfileController
{
    protected function setFlash($action, $value)
    {
        $action = str_replace("fos_user_", "", $action); //Remove "fos_user_" prefix
        parent::setFlash($action, $value);
    }
}

Set additional data, when user is created

There are some additional fields in the user object and some of them had to be set automatically, when the user object is created. Since there is no “on registration” event, I had to hook directly into the code, that creates and persists the user object on registration. This is done by a form handler. The FOSUserBundle has a form handlers for every form type. So I created my own RegistrationFormHandler, which extends the original one. In my case I wanted to save IP, country (received from a geo IP service) and current locale in the user entity. Here’s what I did:

namespace Acme\UserBundle\Form\Handler;

// Use statements ...

class RegistrationFormHandler extends BaseRegistrationFormHandler
{

    // Geo IP service is additionally injected in the constructor
    private $geoIpService;

    // [...]

    protected function createUser()
    {
        $user = parent::createUser();
        $ip = $this->request->getClientIp();
        $user
            ->setIp($ip)
            ->setGeoIpCountry($this->getCountry($ip))
            ->setLanguage($this->request->getLocale());

        return $user;
    }

    protected function getCountry($ip)
    {
        if ($record = $this->geoIpService->getCountry($ip)) {
            return $record->country->isoCode;
        }

        return null;
    }
}

Finally the class has to be registered as a service and the service name has to be set in the configuration:

fos_user:
    registration:
        form:
            handler: acme_user.registration.form.handler

FOSUserBundle 2.0

Just some more information about version 2.0 of FOSUserBundle, which I mentioned in the beginning. I haven’t worked with it yet, since it is not stable, but what you see from the code is, that they implemented events. For example there are events, which enable listeners to set a response object. When a response is set, that one will be used instead of the default response. Great solution to change responses/redirects without the need to modify the controller. Besides this, flash messages have been totally removed from the controller code. They’re set by a FlashListener, which is reacting to events. Altogether it improves extensibility and makes it possible to decouple customizations from the bundle. I’m definitely looking forward to that version!

3 thoughts on “Customizing FOSUserBundle

  • April 21, 2015 at 12:59
    Permalink

    Your article is intresting, i have a problem to custumize profil/edit template the login form it’s ok but the edit form always show the fos template not mine do you think you can help me?

    Reply
  • April 21, 2015 at 18:35
    Permalink

    You can simply overwrite the original template with a file app/Resources/FOSUserBundle/views/Security/login.html.twig. Symfony prefers the templates located in the app/Resources directory over the ones in the bundle.

    Reply
  • November 26, 2015 at 10:14
    Permalink

    Hi,
    Thank you fore these advices.
    I tried to override the getRedirectionUrl, so I have created a ProfileController class, but nothing appends, it still redirects to /profile.

    Any idea ?

    Reply

Leave a Reply to Severin Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.