Buy

Interrupt Symfony with an Event Subscriber

Hey guys! Welcome to a series that we're calling: Journey to the Center of Symfony! In this first part, we'll be talking about the deep, dark core piece called the HttpKernel, a wondrous component that not only sits at the heart of Symfony, but also at the heart of Silex, Drupal 8, PhpBB and a lot of other stuff. How is that possible? We'll find out! And this stuff is really nerdy, so we're going to have some fun.

Getting the Project Running

I already have the starting point of our app ready on my computer. You can download this right on the screencast page. I've already run composer install, I've already created my database, I've already created my schema: I won't show those things here because you guys are a bit more of experts. We do have fixtures, so let's load those.

Now let's use the built-in PHP web server to get our site running.

Perfect!

So in true Journey to the Center of the "Symfony" theme, we're going to talk about dinosaurs. I've already created an app, which has 2 pages. We can list dinosaurs - these are coming out of the database - and if we click on one of them, we go to the show page for that dinosaur.

Big Picture: Request-Route-Controller-Response

No matter what technology or framework we're using, our goal is always to start with a request and use that to create a response. Everything in between those 2 steps will be different based on your tech or framework. In our app, and in almost every framework, two things that are going to be between the request and response are the route and controller. In this case, you can see our homepage has a route, our function is a controller, and our controller returns a Response object::

42 lines src/AppBundle/Controller/DinosaurController.php
... lines 1 - 10
/**
* @Route("/", name="dinosaur_list")
*/
public function indexAction()
{
$dinos = $this->getDoctrine()
->getRepository('AppBundle:Dinosaur')
->findAll();
return $this->render('dinosaurs/index.html.twig', [
'dinos' => $dinos,
]);
}
... lines 24 - 42

And we have the same thing down here with the other page: it has a route, a controller, and that returns a response::

42 lines src/AppBundle/Controller/DinosaurController.php
... lines 1 - 24
/**
* @Route("/dinosaurs/{id}", name="dinosaur_show")
*/
public function showAction($id)
{
$dino = $this->getDoctrine()
->getRepository('AppBundle:Dinosaur')
->find($id);
if (!$dino) {
throw $this->createNotFoundException('That dino is extinct!');
}
return $this->render('dinosaurs/show.html.twig', [
'dino' => $dino,
]);
}

So what we're going to look at is how that all works. Who actually runs the router? Who calls the controller? How do events work in between the request-response flow?

But before we dive into that, what we're going to do first is create an event listener and hook into that process. Then we'll be able to play with that event listener as we dive into the core of things.

The Best Parts of the Web Profiler

I'm going to open up the profiler and go to the timeline. This is going to be our guide to this whole process. This shows everything that happens between the request and the response. Even if you don't understand what's happening yet, after we go through everything, this is going to be a lot more interesting. You can already see where our controller is called, and under the controller you can see the Twig template and even some Doctrine calls being made.

Before and after that, there are a lot of event listeners - you notice a lot of things that end in the word Listener. That's because most of the things that happen between the request and the response in Symfony are events: you have the chance to hook into them with event listeners.

In fact, one other tab I really like on here is the Events tab. You can see there's some event called kernel.request. Maybe you already understand what that means, maybe you don't, but you will soon. There's another event called kernel.controller with listeners and several other events. We're going to see where these events are dispatched and why you would add a hook to one versus another.

Creating an Event Subscriber/Listener

Let's create a listener on that kernel.request event! In my AppBundle, I'll create a new directory called EventListener and a new class. Inside this event listener, we're going to read the User-Agent header off the request and do some things with that. So I'll call this UserAgentSubscriber::

8 lines src/AppBundle/EventListener/UserAgentSubscriber.php
<?php
namespace AppBundle\EventListener;
class UserAgentSubscriber
{
}

If you want to hook into Symfony, there are 2 ways to do it: with a listener or a subscriber. They're actually exactly the same, the only difference is where you configure which events you want to listen to.

I'm going to create a subscriber here because it's a little more flexible. So UserAgentSubscriber needs to implement EventSubscriberInterface::

21 lines src/AppBundle/EventListener/UserAgentSubscriber.php
... lines 1 - 2
namespace AppBundle\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class UserAgentSubscriber implements EventSubscriberInterface
{
... lines 9 - 19
}

Notice that it added the use statement up there. And we're going to need to implement 1 method which is getSubscribedEvents. What this is going to return is a simple array that says: Hey, apparently there's some event whose name is kernel.request - we don't necessary know why it's called or what it does yet - but when that event happens, I want Symfony to call this onKernelRequest function, which we're going to put inside of this class. For now, let's just put a die('it works');::

21 lines src/AppBundle/EventListener/UserAgentSubscriber.php
... lines 1 - 6
class UserAgentSubscriber implements EventSubscriberInterface
{
public function onKernelRequest()
{
die('it works');
}
public static function getSubscribedEvents()
{
return array(
'kernel.request' => 'onKernelRequest'
);
}
}

Cool! The event subscriber is ready to go. No, Symfony doesn't automatically know this class is here or automatically scan the codebase. So to get Symfony to know that there's a new UserEventSubscriber that wants to listen on the kernel.request event, we're going to need to register this as a service.

Registering the Subscriber/Listener

So I'm going to go into app/config/services.yml and clear the comments out. And we'll give it a short, but descriptive name - user_agent_subscriber, the name of the service doesn't really matter in this case. There are no arguments yet, so I'll just put an empty array. Now in order for Symfony to know this is an event subscriber, we'll use something called a tag, and set its name to kernel.event_subscriber:

11 lines app/config/services.yml
... lines 1 - 5
services:
user_agent_subscriber:
class: AppBundle\EventListener\UserAgentSubscriber
tags:
- { name: kernel.event_subscriber }

Now, that tag is called a dependency injection tag, which is really awesome, really advanced and really fun to work with inside of Symfony. And we're going to talk about it in a different part of this series. With just this configuration, Symfony will boot, it'll know about our subscriber, and when that kernel.request event happens, it should call our function.

Sweet!

Logging Something in the Subscriber

Now inside of onKernelRequest, let's do some real work. For now, I want to log a message. I'm going to need the logger so I'll add a constructor and even type hint the argument with the PSR LoggerInterface. And I'll use a little PHPStorm shortcut to create and set that property for me::

29 lines src/AppBundle/EventListener/UserAgentSubscriber.php
... lines 1 - 4
use Psr\Log\LoggerInterface;
... lines 6 - 7
class UserAgentSubscriber implements EventSubscriberInterface
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
... lines 16 - 27
}

Now in our function, we'll log a very important message::

29 lines src/AppBundle/EventListener/UserAgentSubscriber.php
... lines 1 - 16
public function onKernelRequest()
{
$this->logger->info('Yea, it totally works!');
}
... lines 21 - 29

And of course this isn't going to work unless we go back to services.yml and tell Symfony: Hey, we need the @logger service:

12 lines app/config/services.yml
... lines 1 - 5
services:
user_agent_subscriber:
class: AppBundle\EventListener\UserAgentSubscriber
arguments: ["@logger"]
tags:
- { name: kernel.event_subscriber }

Cool!

Let's refresh! It works, and if we click into the profiler, one of the tabs is called "Logs", and under "info" we can see the message. So this is already working, and if we go back to the Timeline and look closely, we should see our UserAgentSubscriber. And it's right there. Also, if we go back to the events tab, we see the kernel.request with all of its listeners. And if you look at the bottom, you see our UserAgentSubscriber on that list too.

So we're hooking into that process already, even if we don't understand what's going on with it.

Every Listener Gets an Event Object

Whenever you listen to any event - whether it's one of Symfony's core events or it's an event from a third-party bundle you installed, your function is passed an $event argument. So, we'll add $event. The only trick is that you don't automatically know what type of object that is, because every event you listen to is going to pass you a different type of event object.

But no worries! I'm going to use the new dump() function from Symfony 2.6::

30 lines src/AppBundle/EventListener/UserAgentSubscriber.php
... lines 1 - 16
public function onKernelRequest($event)
{
dump($event);
$this->logger->info('Yea, it totally works!');
}
... lines 22 - 30

Let's go back a few pages, refresh, and the dump function prints that out right in the web debug toolbar. And we can see it's dumping a GetResponseEvent object. So that's awesome - now we know what type of object is being passed to us. And that's important because every event object will have different methods and different information on it.

Let's type-hint the argument. Notice I'm using PHPStorm, so that added a nice use statement to the top - don't forget that::

33 lines src/AppBundle/EventListener/UserAgentSubscriber.php
... lines 1 - 6
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
... lines 8 - 17
public function onKernelRequest(GetResponseEvent $event)
{
... lines 20 - 23
}
... lines 25 - 33

What I want to do is get the User-Agent header and print that out in a log message. Fortunately, this getResponseEvent object gives us access to the request object. And again, every event you listen to will give you a different event object, and every event object will have different methods and information on it. It just happens to be that this one has a getRequest method, which is really handy for what we want to do. Now I'll just read the User-Agent off of the headers, and log a message::

33 lines src/AppBundle/EventListener/UserAgentSubscriber.php
... lines 1 - 17
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$userAgent = $request->headers->get('User-Agent');
$this->logger->info('Hello there browser: '.$userAgent);
}
... lines 25 - 33

Let's try it! I'll get back into the profiler, then to the Logs... and it's working perfectly.

Even if we don't understand everything that's happening between the request and response, we already know that there are these listeners that happen. But next, we're going to walk through the code that handles all of this.

Leave a comment!

  • 2016-04-15 weaverryan

    Hey Diego!

    Not currently - you're the first to ask! But that would be really (geeky) cool - it's something I'm going to keep thinking about :).

    Cheers!

  • 2016-04-13 Diego Aguiar

    Hello, is there a way to download this course code from console in linux ?

    Cheers

  • 2016-01-27 weaverryan

    Hey Shairyar!

    Ah, I see! So, depending on your configuration, Swiftmailer may send your email immediately (meaning, you can just do an unlink on the line after calling send) *or* will wait until the very end of the request (after the response has been sent) and *then* send the email. The second is the default in the Symfony Standard Edition - it's called "memory spooling". What happens is that Swiftmailer stores the Message object when you send it. Then, it has a listener on the kernel.terminate event, which actually sends any queued (spooled) messages: https://github.com/symfony/swi...

    So, this doesn't help you yet, except that the easiest solution is to turn off memory spooling and allow the message to be sent synchronously (so that you can unlink the file on the next line). The user will receive a slight performance degradation on page loads where you send an email... which is probably not very many.

    Otherwise, the solution is more complicated. Unfortunately, Swiftmailer doesn't dispatch an event (that I know of) after it sends spooled emails. That's *really* what you need. However, check out this bundle: it appears to add that hook: https://github.com/TDMobility/.... If I'm correct, then you would register a listener on the "tdm.swiftmailer.mailer.post_send_cleanup" event and be able to delete the file.

    Let me know if this helps!

  • 2016-01-27 Shairyar Baig

    Hi,

    I am using SwiftMailer to sent email, I did some search on google and came across an option of "unlink(filename)" but using this option leads to an error may be the code deletes the file before the uploading is ever completed.

    In another area of the app I am allowing people to generate files and download them, but there I am using BinaryFileResponse() which gives me an access to deleteFileAfterSend() and that seems to work perfectly fine, it deletes the file after sending it as a response.

    I kind of want to avoid creating the event listener just to delete the file, I was hoping SwiftMail or Symfony had some other way for us to know that the file is attached now and can be deleted safely.

  • 2016-01-26 weaverryan

    Hi Shairyar!

    Cool question. The answer depends on *how* your email is sent:

    1) If you are sending the email in your Symfony code somewhere (maybe via an app/console script or some other Symfony controller - it doesn't matter), then you could of course just delete the file right at that same moment. Or, more elegantly, you could dispatch your own event - e.g. "file.email_sent" and you would send it some new event object (e.g. FileEmailSentEvent) that has information about which email was just sent and which file should be deleted. Then, you could register a listener to this event to delete the file. The pro is that this makes your code very decentralized. The con is that it makes your code a little bit harder to understand: I can't immediately see what is happening when I look at your original code.

    2) If some external service is sending the email (e.g. Mandrill), then you would need to look into webhooks that the service can send you when *it* completes sending. In that case, Symfony events wouldn't be involved.

    Let me know if I somewhat answered your question... or if I misunderstood :).

    Cheers!

  • 2016-01-25 Shairyar Baig

    Hi Ryan,

    I was wondering if it is possible via event listener or some other way in Symfony to know if an email has been sent. Reason I am asking this is because I am creating files for users on server and this file gets sent via email as an attachment. Once the email has been sent this file is no longer required so I want to delete it.

  • 2015-07-02 Juan Luis Garcia

    Thanks so much Ryan!
    You're very friendly.

    Pd. I am eager to begin the tutorial on Continuous Deployment With Symfony2

    Greetings!!! =)

  • 2015-07-02 weaverryan

    Hi Juan!

    Ah, I understand! So first, about sending an email after the response was sent, this is already a built-in feature called "memory spooling": http://symfony.com/doc/current.... It works exactly like you expect: you send an email like normal, but it doesn't send immediately. Instead, a listener on kernel.terminate sends the email.

    But, what if this weren't a feature and you needed to build it yourself? The process would look something like this (it is a little bit more difficult, but I hope you will understand the flow):

    A) Create some class that helps you send emails - e.g. a MailerManager class - and give it two methods: queueEmail($message) and sendAllQueuedEmails(). Register this as a service (named, for example, "app.mailer_manager". The queueEmail() wouldn't actually send the email - it would just set it on some private property in the class (e.g. $queuedMessages). Then, the sendAllQueuedEmails() will eventually loop over this property and use the normal mailer service to send these.

    B) In your controller, instead of getting the "mailer" service and using it to send the email directly, you would instead get your "app.mailer_manager" service and use your queueEmail() method:

    public function someAction()
    {
    // ...
    $this->get('app.mailer_manager')->queueEmail($someMessageVariable);
    }

    C) Finally, create a new event listener class and make it listen on kernel.terminate. You'll inject (via the constructor) the MailerManager service into this, and the code would look something like this:


    class MailSenderListener
    {
    private $mailerManager;


    public function __construct(MailerManager $mailerManager)
    {
    $this->mailerManager = $mailerManager;
    }


    public function onKernelTerminate(PostResponseEvent $event)
    {
    $this->mailerManager->sendAllQueuedEmails();
    }
    }

    The MailerManager::sendAllQueuedEmails() will loop over the queued messages and use the real mailer service to send those (so, the real mailer service would be passed into the constructor of MailerManager).

    How does that sound? So you don't call something in kernel.terminate directly from your controller. Instead, that listener will *always* run. Your job is to make "store" information somewhere so that when your listener executes, it knows what you want it to do.

    Cheers!

  • 2015-07-02 Juan Luis Garcia

    Ryan thank you very much!
    Now I understand.
    Respect to events as kernel.terminate, I intend to send an email or pdf after sending the response to the user.

    greetings and thanks Ryan!

  • 2015-07-01 weaverryan

    Hi Juan!

    Yes, what you say above about listeners and subscribers is true. But also, you *can* specify a priority for a listener. But for a listener, this priority lives with your service definition:


    services:
    my_listener:
    class: AppBundle\EventListener\SomeListener
    arguments: []
    tags:
    - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 100 }

    So again, a listener and a subscriber have the same abilities :).

    Respect to events as kernel.terminate there any way to make the event run by the controller?

    I don't think I understand. The kernel.terminate event happens after your controller. In other words, all listeners to the kernel.terminate event are called after your controller automatically. What do you want to accomplish with this event?

    Cheers!

  • 2015-07-01 Juan Luis Garcia

    Now a little clearer. In the event we put subscriber execution priorities and in no event listener. I think it's not important priorities for most cases ....

    Respect to events as kernel.terminate there any way to make the event run by the controller?

    Thank you very much for your answer.

  • 2015-06-30 weaverryan

    Hi Juan!

    They are really the same thing :). I will say it again: a listener and a subscriber have the *same* purpose. Both have a function that Symfony calls when an event occurs.

    So, what is the difference? Well, somewhere, you need to say "Hey Symfony, please call onKernelRequest when the kernel.request event happens". Listeners and subscribers keep this "configuration" in a different place:

    Listener: This configuration is stored in the service definition, under the "event" and "method" options of the tag

    Subscriber: This configuration is stored right inside the class, inside the getSubscribedEvents function.

    Everything that can be done with a listener can be done with a subscriber, and vice-versa :). So, when you are choosing between them, the choice is entirely up to you. I tend to prefer subscribers, because I like to be able to look inside the class and see all my information in one spot.

    Does that help?

  • 2015-06-30 Juan Luis Garcia

    Hello,
    Do not fail to understand the difference between listener vs subscriber?
    Could you clarify my doubt? Thank you very much Wea Ryan!

  • 2015-06-19 Shairyar Baig

    Just wanted to say thanks. I will now be digging into dependency injection

  • 2015-06-19 weaverryan

    Yea, you probably don't need that part (you're right that the other check should be enough) :)

  • 2015-06-19 Shairyar Baig

    Thank you so much, by using the following piece of code I was able to see the 404 error


    if (!$token = $this->tokenStorage->getToken()) {
    // possible only if the security system didn't run, usually due to an error like a 404
    return;
    }

    just wondering if i still need to use (my code works fine if i dont use it, i dont see any error)



    $user = $token->getUser();
    if (!is_object($user)) {
    // there is no user - the user may not be logged in
    return;
    }

  • 2015-06-18 weaverryan

    Hey!

    Oh yes yes, I know what's happening. Here's how it works:

    A) the RouterListener runs. There is no route, so it throws an exception
    B) The security system (which is also a listener) is never called, so security is never initialized. Your listener is also never called
    C) To render the 404 page, a "sub request" is made. This calls all the listeners again. The security system is programmed to *not* run this time however, so there is still no user. BUT, your listener *does* run again.

    To fix this, be a little more careful when getting the user (especially in a listener). I'd do:


    if (!$token = $this->tokenStorage->getToken()) {
    // possible only if the security system didn't run, usually due to an error like a 404
    return;
    }


    $user = $token->getUser();
    if (!is_object($user)) {
    // there is no user - the user may not be logged in
    return;
    }

    This kind of code is only needed in code that may be run somewhere when an error has happened (listeners, or your exception template, for example). It's kind of an ugly detail.

    And nice work on the service setup - that's exactly what I had in mind! I still have no idea how the ContainerAware things was even working - I see nothing in the core code that would do that...

    Cheers!

  • 2015-06-18 Shairyar Baig

    Many thanks for the feedback, I got the code from searching answers online following different tutorials. I am not using any third party bundles yet just a plain simple Symfony framework.

    1) Reason I asked the route query was I noticed when the route does not exist, instead of seeing the 404 error i see


    FatalErrorException in StudentApplicationSubscriber.php line 36:Error: Call to a member function getUser() on null



    The above error is because of using getUser() in event listener, just to check if i remove the event listener i do see the 404 error, how do i fix this?

    2) Ok so following your instruction about getting rid of ContainerAware, i did that and it is working perfectly fine. this is how my services.yml looks like now


    services:
    student_application_subscriber:
    class: ProjectEverest\CoreBundle\EventListener\StudentApplicationSubscriber
    arguments:
    - @doctrine.orm.entity_manager
    - @security.token_storage
    - @security.authorization_checker
    - @twig
    tags:
    - { name: kernel.event_subscriber



    This is how my event listener looks like now



    namespace ProjectEverest\CoreBundle\EventListener;

    use Doctrine\ORM\EntityManager;
    use Symfony\Component\EventDispatcher\EventSubscriberInterface;

    class StudentApplicationSubscriber implements EventSubscriberInterface
    {
    protected $em;
    protected $twig;
    protected $tokenStorage;
    protected $authChecker;

    function __construct(EntityManager $em, $tokenStorage, $authChecker, $twig)
    {
    $this->em = $em;
    $this->twig = $twig;
    $this->tokenStorage = $tokenStorage;
    $this->authChecker = $authChecker;
    }

    public static function getSubscribedEvents()
    {

    return array(
    'kernel.request' => 'onKernelRequest'
    );

    }

    public function onKernelRequest()
    {

    //get details of logged in user
    $get_user_details = $this->tokenStorage->getToken()->getUser();

    //make sure to pull information when user is logged in
    if ($this->authChecker->isGranted('IS_AUTHENTICATED_FULLY')) {

    //get user id of logged in user
    $userId = $get_user_details->getId();

    //check if the user added a photo
    $get_user_image = $this->em->getRepository('CoreBundle:ProfileImage')->findBy(array('userId' => $userId));

    if ($get_user_image) {
    $profile_image_exist = 'yes';
    } else {
    $profile_image_exist = 'no';
    }
    // assign the value of photo to twig global variable
    $this->twig->addGlobal('profile_image_exist', $profile_image_exist);

    //check if user has filled the application
    $get_user_application = $this->em->getRepository('CoreBundle:Apply')->findBy(array('userid' => $userId));

    if ($get_user_application) {
    $user_application_exist = 'yes';
    } else {
    $user_application_exist = 'no';
    }
    // assign the value of application to twig global variable
    $this->twig->addGlobal('user_application_exist', $user_application_exist);
    }

    }

    }


    Many thanks for all the help

  • 2015-06-18 weaverryan

    Hey!

    Nice work - happy it's working! So:

    1) "how do i get this to stop running on routes that don't exist?". The best answer is: you don't. Basically, why do you care? If the route doesn't exist, then it won't matter anyways. I think that's over-optimizing. But also, I *think* that this won't run anyways - the layer that runs the router is earlier than this listener, and when it fails to find a route, it throws an Exception and stops the rest of the listeners.

    2) I see you're extending ContainerAware and this is giving you a `$this->container` property. Where did you get this code from? I don't think this is a core feature of Symfony itself - do you have a bundle installed? Basically, this is "ok", but there's a better way. You're already "injecting" the EntityManager via the __construct function. You should also add a few more arguments: $tokenStorage for security.token_storage, $authChecker for security.authorization_checker and $twig for twig. You would then set these on properties (just like you do with $em), update the arguments key in your services.yml to pass these (just like you did with the entity manager service), and then reference the properties directly (i.e. $this->twig instead of $this->container->get('twig')). Then, you won't need the ContainerAware class - and I'm not sure what's making that work anyways :). But more importantly, you'll be using "dependency injection" properly: passing your class all the other services it needs.

    Cheers!

  • 2015-06-18 Shairyar Baig

    It sounds great, I was able to query database for the data i needed in my listener and I was able to set the outcome as twig global variable so i can now access that on every page, this is what my listener looks like, this is my first listener ever.....


    namespace ProjectEverest\CoreBundle\EventListener;
    use Doctrine\ORM\EntityManager;
    use Symfony\Component\DependencyInjection\ContainerAware;
    use Symfony\Component\EventDispatcher\EventSubscriberInterface;

    class StudentApplicationSubscriber extends ContainerAware implements EventSubscriberInterface
    {
    protected $em;
    protected $twig;

    function __construct(EntityManager $em)
    {
    $this->em = $em;
    }

    public static function getSubscribedEvents()
    {

    return array(
    'kernel.request' => 'onKernelRequest'
    );

    }

    public function onKernelRequest()
    {

    //get details of logged in user
    $get_user_details = $this->container->get('security.token_storage')->getToken()->getUser();

    //make sure to pull information when user is logged in
    $securityContext = $this->container->get('security.authorization_checker');
    if ($securityContext->isGranted('IS_AUTHENTICATED_FULLY')) {
    // authenticated REMEMBERED, FULLY will imply REMEMBERED (NON anonymous)

    //get user id of logged in user
    $userId = $get_user_details->getId();

    //check if the user added a photo
    $get_user_image = $this->em->getRepository('CoreBundle:ProfileImage')->findBy(array('userId'=>$userId));

    if($get_user_image){
    $profile_image_exist = 'yes';
    }else{
    $profile_image_exist = 'no';
    }
    // assign the value of photo to twig global variable
    $this->container->get('twig')->addGlobal('profile_image_exist', $profile_image_exist);

    //check if user has filled the application
    $get_user_application = $this->em->getRepository('CoreBundle:Apply')->findBy(array('userid' => $userId));

    if($get_user_application){
    $user_application_exist = 'yes';
    }else{
    $user_application_exist = 'no';
    }
    // assign the value of application to twig global variable
    $this->container->get('twig')->addGlobal('user_application_exist', $user_application_exist);
    }
    }
    }


    I did run into a scenario where this event was being dispatched even on route that did not exist or even when a user was not logged in so I added the




    if ($securityContext->isGranted('IS_AUTHENTICATED_FULLY')) {...}


    to stop the query from running for non logged in user, but how do i stop this from running on routes that dont exist?

  • 2015-06-17 weaverryan

    Hey again!

    I see two comments, which I think are both asking nearly the same question - a good question :).

    1) If you're dispatching your own events and want to pass the listeners some data (e.g. the data submitted via a form), you'll create a brand new Event object (that extends Symfony's Event), add any properties you need on it set that data on the custom event object before dispatching it. Check out the FOSUserBundle link from above - they do this exact thing. You're always free just to create a generic Symfony Event object, but if you're passing some extra data, create your own.

    2) It sounds like you're querying for something via a listener in kernel.request and that you want to make this value available to all of your controllers and maybe templates too. There are a few ways of doing this. The most straightforward is to store this value on some service - fortunately I have an example of something like this handy :) https://knpuniversity.com/scre....

    If you want to get *really* fancy, you could make this value available as an argument in any controller in your system by modifying the request attributes. This is easy - but quite advanced, and I only mention it because that advanced stuff is exactly what this tutorial is about :).

    // in the listener
    public function onKernelRequest(GetResponseForEvent $event)
    {
    $someObject = // ... make a query
    $request = $event->getRequest();
    $request->attributes->set('myCoolVar', $someObject);
    }


    // now in ANY controller in the system
    public function showAction($id, $myCoolVar)
    {
    // ...
    }

    How's that sound? :)

  • 2015-06-17 Shairyar Baig

    I was able to set it up fine thanks to this tutorial, what i am stuck at understanding now is what to do with the data being fetched at kernel.request. Using the dump() function I can see the results so the query is working fine in my event listener. How can i access this data now in any controller or twig template?

  • 2015-06-17 Shairyar Baig

    Thanks, makes more sense now. How can I share data submitted via a form from controller to an event so the event method can make use of that to perform its task.

  • 2015-06-16 weaverryan

    Hey!

    So even though they work together in Symfony, think of events and dependency injection as two separate things. Events are no different than in JavaScript. For example, I can register a "listener" to "listen" on the "click" event of a button (via jQuery) with


    $('.some-button').on('click', function() { console.log('this is a listener!'); });

    In Symfony, you register your event listeners as services, but technically, event listeners are flat functions. So, try to keep the ideas separate :).

    To your question - you absolutely could create an event to generate an email after a user registers. In fact, FOSUserBundle does something like this: they dispatch a "registration success" event after a successful registration. You could add a listener to this in order to send the user an email: https://github.com/FriendsOfSy.... That's a perfect use-case.

    And yes, this could be used to keep your controllers shorter. However, be a little careful: if you dispatch a lot of events, it makes your code ver modular and your controllers short. BUT, it also makes your code harder to follow: if I see an event dispatched in a controller, I can't easily see who listens to that or what those listeners do. But if I see the code right in the controller, OR a method call out to another service (which then sends an email and anything else), then the code is less abstract and easier to understand.

    Make sense? Cheers!

  • 2015-06-16 Shairyar Baig

    very nice tutorial. What is the difference between Events and Dependency Injection? Or similarity? Would it make sense to create enents to generate email. Forexampl if a user register on your website an event should be dispatched to send a welcome email. Can events be used or should they be used for a small tasks like these to keep the controller thin?