TRACK

Symfony 3 >

Customize everything with Events

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.

We can override templates. We can override translations. We can override forms. But there's more than that. For example, after we finish registration, we're redirected to this registration confirmation page. You know what? I'd rather do something else: I'd rather redirect to the homepage and skip this page entirely. How can we do that?

Well, let's do a little bit of digging. If you hover over the route name in the web debug toolbar, you can see that this is page rendered by RegistrationController. Back in my editor I'll press Shift+Shift and look for RegistrationController in the bundle. Specifically, registerAction() is responsible for both rendering the registration page and processing the form submit.

And check this out: after the form is valid, it redirects to the confirmation page.

Events to the Rescue!

So at first, it seems like we need to override the controller itself. But not so fast! The controller - in fact every controller in FOSUserBundle is littered with events: REGISTRATION_INITIALIZE, REGISTRATION_SUCCESS, REGISTRATION_COMPLETED and REGISTRATION_FAILURE. Each of these represents a hook point where we can add custom logic.

In this case, if you look closely, you can see that after it dispatches an event called REGISTRATION_SUCCESS, below, it checks to see if the $event has a response set on it. If it does not, it redirects to the confirmation page. But if it does, it uses that response.

That's the key! If we can add a listener to REGISTRATION_SUCCESS, we can create our own RedirectResponse and set that on the event so that this controller uses it. Let's go!

Creating the Event Subscriber

Inside of AppBundle, create a new directory called EventListener. And in there, a new PHP class: how about RedirectAfterRegistrationSubscriber. Make this implement EventSubscriberInterface: the interface that all event subscribers must have. I'll use our favorite Code->Generate menu, or Command+N on a Mac, go to "Implement Methods" and select getSubscribedEvents.

... lines 1 - 2
namespace AppBundle\EventListener;
... lines 4 - 6
class RedirectAfterRegistrationSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
}
}

We want to attach a listener to FOSUserEvents::REGISTRATION_SUCCESS, which, by the way, is just a constant that equals some string event name.

In getSubscribedEvents(), add FOSUserEvents::REGISTRATION_SUCCESS assigned to onRegistrationSuccess. This means that when the REGISTRATION_SUCCESS event is fired, the onRegistrationSuccess method should be called. Create that above: public function onRegistrationSuccess().

<?php
... lines 2 - 8
class RedirectAfterRegistrationSubscriber implements EventSubscriberInterface
{
public function onRegistrationSuccess(FormEvent $event)
{
}
public static function getSubscribedEvents()
{
return [
FOSUserEvents::REGISTRATION_SUCCESS => 'onRegistrationSuccess'
];
}
}

Oh, and notice that when this event is dispatched, the bundle passes a FormEvent object. That will be the first argument to our listener method: FormEvent $event. That's what we need to set the response onto.

Investigating all the Events

Before we go any further, I'll hold command and click into the FOSUserEvents class... cause it's awesome! It holds a list of every event dispatched by FOSUserBundle, what its purpose is, and what event object you will receive. This is gold.

Creating the RedirectResponse

Back in onRegistrationSuccess, we need to create a RedirectResponse and set it on the event. But to redirect to the homepage, we'll need the router. At the top of the class, create public function __construct() with a RouterInterface $router argument. Next, I'll hit Option+Enter, select "Initialize Fields" and choose router.

<?php
... lines 2 - 10
class RedirectAfterRegistrationSubscriber implements EventSubscriberInterface
{
private $router;
public function __construct(RouterInterface $router)
{
$this->router = $router;
}
... lines 19 - 32
}

That was just a shortcut to create the private $router property and set it in the constructor: nothing fancy.

Now, in onRegistrationSuccess() we can say $url = $this->router->generate('homepage'), and $response = new RedirectResponse($url). You may or may not be familiar with RedirectResponse. In a controller, to redirect, you use $this->redirectToRoute(). In reality, that's just a shortcut for these two lines!

Finally, add $event->setResponse($response).

<?php
... lines 2 - 10
class RedirectAfterRegistrationSubscriber implements EventSubscriberInterface
{
... lines 13 - 19
public function onRegistrationSuccess(FormEvent $event)
{
$url = $this->router->generate('homepage');
$response = new RedirectResponse($url);
$event->setResponse($response);
}
... lines 26 - 32
}

Ok, this class is perfect! To tell Symfony about the event subscriber, head to app/config/services.yml. At the bottom, add app.redirect_after_registration_subscriber, set the class, and add autowire: true. By doing that, thanks to the RouterInterface type-hint, Symfony will automatically know to pass us the router.

Finally, add a tag on the bottom: name: kernel.event_subscriber. And we are done!

... lines 1 - 5
services:
... lines 7 - 22
app.redirect_after_registration_subscriber:
class: AppBundle\EventListener\RedirectAfterRegistrationSubscriber
autowire: true
tags:
- { name: kernel.event_subscriber }

Try it out! Go back to /register and signup as aquanaut5@gmail.com. Fill out the rest of the fields and submit!

Boom! Back to our homepage! You can customize just about anything with events. So don't override the controller. Instead, hook into an event!

Leave a comment!

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
        "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"
    }
}