They are similar but, but CommandBus is something related to Symfony Messenger component and is used to dispatch *messages*, see https://symfony.com/doc/cur... . While EventDispatcher is a standalone Symfony component and is used to dispatch events. It provides tools that allow your application components to communicate with each other by dispatching events and listening to them.
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?
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/FriendsOfSymfony/FOSUserBundle/blob/master/Controller/RegistrationController.php#L59. 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.
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.
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?
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.
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)
{
// ...
}
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?
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.
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
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);
}
}
}
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...
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.
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?
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:
So again, a listener and a subscriber have the same abilities :).
<blockquote>Respect to events as kernel.terminate there any way to make the event run by the controller?</blockquote>
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?
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.
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/cookbook/email/spool.html#spool-using-memory. 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.
Let's try to debug things: write "{{ dump(dino.imageUrl) }}" somewhere in the template and see what is this path. Then, go to your filesystem and find this image. You need to compare the path you dumped and the real path of the image. Probably you need to tweak this path a bit or add some nested directories to the relative web path.
Thank you, Victor for your quick reply! And just to say to anybody alse that will see the same thing from now on, it s because these images have disappeared from the url. If they want they can put inside the databases other Url or path for images!
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.
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 :).
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.
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/...
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/TDMobili.... 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.
I want to send real time data to Twig file. In the Internet research I saw the StreamedResponse class. Is it different from normal ajax request? or is there another way to transfer data without having to render the twig file?
public function streamedResponseAction(Request $request) { $response = new StreamedResponse(); $response->setCallback(function () { echo 'Hello World'; flush(); sleep(3); echo 'Hello World'; flush(); });
Basically, there is no difference between StreamedResponse and some normal Response: you would make a traditional AJAX call to get either of these. A StreamedResponse is just a mechanism for returning a normal response. For real-time data, you need a different solution. Oh, and btw, we use Pusher for this functionality: we publish messages to it and it has an easy JS API where we can subscribe to those messages.
I want to know about Event Dispatcher,Event Listner and Event Subscriber ,Actually i wan to create a Dispatcher exactly same as how symfony has provided in the document i am confused how to implement this document in symfony project .
So, first let me make sure I understand correctly :). Usually, people interact with the event dispatcher in a Symfony project by "listening" (or subscribing) to events. That's what we do in this tutorial. Basically, this is when you want to hook into some functionality (e.g. call my function when Symfony does XX).
However, it sounds like you might want to do something different: it sounds like you want to actually dispatch your own events, so that other parts of your application can listen/subscribe to them. Is that correct? If that's true, read this section: https://symfony.com/doc/current/components/event_dispatcher.html#creating-and-dispatching-an-event. The only difference is that - since you're using Symfony - you won't need to create the $dispatcher object. Instead, dispatching your event from a controller would look like this:
// in some controller
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
// ...
public function someAction(EventDispatcherInterface $dispatcher)
{
$order = new Order();
// ...
// create the OrderPlacedEvent and dispatch it
// obviously, you will create your *own* Event object and event name, and use it here
$event = new OrderPlacedEvent($order);
$dispatcher->dispatch(OrderPlacedEvent::NAME, $event);
}
My requirement is that once a user logged in i want to give a pop up msg like login successfully.But i want to achieve it using Dispatcher.I want to create my own custom events. My doubt is what shall i provide in Order class in docs they have provided something like this:- // the order is somehow created or retrieved $order = new Order();
My Files are as following:-
Event class :- class LoginEvent extends Event { const LOGIN = 'login.successfull';
protected $login;
public function __construct(Login $login) { $this->login = $login; }
public function getLogin() { return $this->login; } } YAML File:-
Default Contoller :- (When i will redirect to home page)
public function loginAction(Request $request) { $login = new Login(); $event = new LoginEvent($login); $dispatcher = new EventDispatcher(); $dispatcher->dispatch(LoginEvent::LOGIN, $event); $dispatcher->addListener('login.action', array($listener, 'onLoginAction'));
return $this->render('base.html.twig'); }
My ListnerClass :-
namespace AppBundle\EventListener;
class EventListener { public function onLoginAction(LoginEvent $event) { dump($event); } }
Can you pleae figure out where i'm making mistake.
Error i got after executing the code:- Attempted to load class "Login" from namespace "AppBundle\Controller". Did you forget a "use" statement for another namespace?
If you are using Symfony 3.3+ you can inject your dependencies as arguments in a controller's method, just as Ryan did:` public function someAction(EventDispatcherInterface $dispatcher)`
So, you don't have to create a dispatcher object. What you have to do in your loginAction() is to inject the dispatcher, and dispatch your custom event only if the login was successful. Probably you will only need to pass the user object to your event, so you can print his name/nickname
Hi folks - I seem to be falling at the first hurdle here. When I run php app/console doctrine:schema:create I get the following error:
[Doctrine\Common\Annotations\AnnotationException] You have to enable opcache.load_comments=1 or zend_optimizerplus.load_comments=1.
I've read around and it seems like the issue might be with PHP7, which is what I've got installed locally - but I couldn't find a satisfactory solution. So I put everything inside a Vagrant box with PHP5.6, and the doctrine commands work just fine - but I can't see how to load the site itself. I've made the start dir from your code tar into the Vagrant docroot, but when I go to the corresponding URL I just get a forbidden. I can't see an index file or anything like that in your code.
You have to enable opcache.load_comments=1 or zend_optimizerplus.load_comments=1.
Basically, you need to tweak some php.ini settings so that annotations load. I'm a bit surprised to see this, as I believe the default settings in PHP 7 should be ok... but no problem. First, find your php.ini file by running:
php --ini
Now that you know where your php.ini file lives, open it and add/modify:
opcache.load_comments=1
Then, restart your web server if you're using one. If you're using bin/console server:run command, just stop and restart that.
but I can't see how to load the site itself
Ah, so here's the trick! First, because we're going to use the built-in PHP web server (just for development), you don't actually need to put your code in the Vagrant docroot - but that certainly doesn't hurt anything! When you start the built-in web server, you need to bind to all IP addresses (if this is the first time you've done this, I admit, it looks a bit technical:
php bin/console server:run 0.0.0.0
Now, find what the IP address is to your Vagrant box - e.g 192.168.33.10. Then, put that in your browser: http://192.168.33.10:8000.
That should load the site! In a nut-shell, you first start the built-in PHP web server inside Vagrant. But to access it on your host machine, you won't use "localhost" like normal - as Vagrant is like having a remote machine. Instead, you'll use the IP address to your Vagrant box. The reason we need to do the 0.0.0.0 thing is that - by default - the PHP web server only accepts requests coming from the local (in this case Vagrant) machine. By saying 0.0.0.0, it says "Respond to requests that come from ANY IP address". This allows us to access from our outside (host) machine.
And if you want to use Nginx or Apache, you can! But Symfony's "document root" is actually the "web" directory. The easiest way to make this work is to do some symbolic link magic. For example, suppose /var/www is the document root in Vagrant. I would do this:
# start in your home directory... or really ANY directory - it doesn't matter
cd ~
# put the start code here - in a "symfony_project" directory
# ... commands putting the start code here
# delete the document root
cd /var
sudo rm -rf www
# replace the document root with a symbolic link to your project's web directory
sudo ln -s ~/symfony_project/web www
That's it! Suddenly, your document root - /var/www - points to the "web" directory of your project.
Let me know if any of this helped! Hopefully we can get your past the first hurdle - setup problems are no fun ;).
I ended up figuring it out like this (subtly different to your answer, so I thought I'd post here in case it helps anyone else):
The opcache settings are actually in /usr/local/php5/php.d/20-extension-opcache.ini, rather than in the core php.ini file (I found it by using php --ini, as you suggested).
By default opcache is switched off, so you can't use opcache.load_comments. In order to switch it on, I commented out the path to opache.so (which doesn't seem to be there), and I enabled opcache and the comments extension, like this:
FYI I'm using PHP7 and Apache 2 on a Mac running El Capitan.
Thanks for getting back to me. You guys are always really really helpful on the forums - it's one of the reasons I think this site is totally worth paying for.
I was wondering if I can get some help on this as I am trying to understand EventSubscriber in forms. I have a form and in that i have ->addEventSubscriber(new SkillsSubscriber()) but inside the the subscriber class SkillsSubscriber() none of my events are firing
namespace AppBundle\Form\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class SkillsSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_SUBMIT => 'onPreSubmit',
);
}
public function onPreSubmit(FormEvent $event)
{
$user = $event->getData();
$form = $event->getForm();
dump($user, $form);
die;
}
}
All I am trying to do is when the form is submitted, I want to get hold of the data and change few things before its inserted in the database. What am i missing here? I am not sure if I should be using subscriber or listener here.
I believe you forgot to register your listener as a service (You can watch how in the video, around 4:30). Subscribers and listeners are almost the same, the only thing that changes is the way that they are hooked up into the system.
FYI: there are "FormEvents" that you can attach directly to your "FormTypes", I think they are perfect for your current task, you can read more about it here: http://symfony.com/doc/curr...
Thanks for getting back. It was a stupid mistake at my end the subscriber was implemented just fine I was looking at the wrong route :) everything is working as expected. thanks
Please, log in to vote for this comment
|
Share Comment
"Houston: no signs of life" Start the conversation!
50 Comments
Hi, like always, nice video; what are the differences between a CommandBus and an EventDispatcher in Symfony?
Hey Gustavo,
They are similar but, but CommandBus is something related to Symfony Messenger component and is used to dispatch *messages*, see https://symfony.com/doc/cur... . While EventDispatcher is a standalone Symfony component and is used to dispatch events. It provides tools that allow your application components to communicate with each other by dispatching events and listening to them.
Cheers!
Hello, is there a way to download this course code from console in linux ?
Cheers
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!
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?
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
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/FriendsOfSymfony/FOSUserBundle/blob/master/Controller/RegistrationController.php#L59. 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!
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.
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?
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/screencast/question-answer-day/symfony2-dynamic-subdomains#determining-the-site-automatically-with-an-event-listener.
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 :).
How's that sound? :)
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.....
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
to stop the query from running for non logged in user, but how do i stop this from running on routes that dont exist?
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!
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
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
This is how my event listener looks like now
Many thanks for all the help
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:
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!
Thank you so much, by using the following piece of code I was able to see the 404 error
just wondering if i still need to use (my code works fine if i dont use it, i dont see any error)
Yea, you probably don't need that part (you're right that the other check should be enough) :)
Just wanted to say thanks. I will now be digging into dependency injection
Hello,
Do not fail to understand the difference between listener vs subscriber?
Could you clarify my doubt? Thank you very much Wea Ryan!
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?
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.
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:
So again, a listener and a subscriber have the same abilities :).
<blockquote>Respect to events as kernel.terminate there any way to make the event run by the controller?</blockquote>
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!
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!
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/cookbook/email/spool.html#spool-using-memory. 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:
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:
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!
Thanks so much Ryan!
You're very friendly.
Pd. I am eager to begin the tutorial on Continuous Deployment With Symfony2
Greetings!!! =)
Hello guys,
i have a problem with loading the dinosaurs images! Probably the
src="{{ dino.imageUrl }}"
in dinosaurs\show.html.twig
doesn t work. What can i do for this?
Thanks in advance!
Hey argy_13 ,
Let's try to debug things: write "{{ dump(dino.imageUrl) }}" somewhere in the template and see what is this path. Then, go to your filesystem and find this image. You need to compare the path you dumped and the real path of the image. Probably you need to tweak this path a bit or add some nested directories to the relative web path.
Cheers!
Thank you, Victor for your quick reply! And just to say to anybody alse that will see the same thing from now on, it s because these images have disappeared from the url. If they want they can put inside the databases other Url or path for images!
Hey argy_13 ,
Thanks for sharing the reason with others!
Cheers!
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.
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!
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.
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/...
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/TDMobili.... 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!
Hi,
I want to send real time data to Twig file. In the Internet research I saw the StreamedResponse class. Is it different from normal ajax request? or is there another way to transfer data without having to render the twig file?
public function streamedResponseAction(Request $request)
{
$response = new StreamedResponse();
$response->setCallback(function () {
echo 'Hello World';
flush();
sleep(3);
echo 'Hello World';
flush();
});
return $response;
}
Yo Mert Simsek!
I just replied to your other thread here: https://knpuniversity.com/s...
Basically, there is no difference between StreamedResponse and some normal Response: you would make a traditional AJAX call to get either of these. A StreamedResponse is just a mechanism for returning a normal response. For real-time data, you need a different solution. Oh, and btw, we use Pusher for this functionality: we publish messages to it and it has an easy JS API where we can subscribe to those messages.
Cheers!
I want to know about Event Dispatcher,Event Listner and Event Subscriber ,Actually i wan to create a Dispatcher exactly same as how symfony has provided in the document i am confused how to implement this document in symfony project .
Please give me some idea.
Symfony Docs Reference:- https://symfony.com/doc/cur...
Yo Vivek Sharma!
So, first let me make sure I understand correctly :). Usually, people interact with the event dispatcher in a Symfony project by "listening" (or subscribing) to events. That's what we do in this tutorial. Basically, this is when you want to hook into some functionality (e.g. call my function when Symfony does XX).
However, it sounds like you might want to do something different: it sounds like you want to actually dispatch your own events, so that other parts of your application can listen/subscribe to them. Is that correct? If that's true, read this section: https://symfony.com/doc/current/components/event_dispatcher.html#creating-and-dispatching-an-event. The only difference is that - since you're using Symfony - you won't need to create the
$dispatcherobject. Instead, dispatching your event from a controller would look like this:Let me know if that helps!
My requirement is that once a user logged in i want to give a pop up msg like login successfully.But i want to achieve it using Dispatcher.I want to create my own custom events.
My doubt is what shall i provide in Order class in docs they have provided something like this:-
// the order is somehow created or retrieved
$order = new Order();
My Files are as following:-
Event class :-
class LoginEvent extends Event
{
const LOGIN = 'login.successfull';
protected $login;
public function __construct(Login $login)
{
$this->login = $login;
}
public function getLogin()
{
return $this->login;
}
}
YAML File:-
crv.event.subscriber.fun_event_subscriber:
class: AppBundle\EventListener\EventListener
tags:
- { name: kernel.event_listener, event: login.successfull, method: onLoginAction }
Default Contoller :- (When i will redirect to home page)
public function loginAction(Request $request)
{
$login = new Login();
$event = new LoginEvent($login);
$dispatcher = new EventDispatcher();
$dispatcher->dispatch(LoginEvent::LOGIN, $event);
$dispatcher->addListener('login.action', array($listener, 'onLoginAction'));
return $this->render('base.html.twig');
}
My ListnerClass :-
namespace AppBundle\EventListener;
class EventListener
{
public function onLoginAction(LoginEvent $event)
{
dump($event);
}
}
Can you pleae figure out where i'm making mistake.
Error i got after executing the code:-
Attempted to load class "Login" from namespace "AppBundle\Controller".
Did you forget a "use" statement for another namespace?
Hey Vivek Sharma
If you are using Symfony 3.3+ you can inject your dependencies as arguments in a controller's method, just as Ryan did:
`public function someAction(EventDispatcherInterface $dispatcher)
`So, you don't have to create a dispatcher object. What you have to do in your
loginAction()is to inject the dispatcher, and dispatch your custom event only if the login was successful. Probably you will only need to pass the user object to your event, so you can print his name/nicknameI hope it helps you :)
Hi folks - I seem to be falling at the first hurdle here. When I run php app/console doctrine:schema:create I get the following error:
[Doctrine\Common\Annotations\AnnotationException]
You have to enable opcache.load_comments=1 or zend_optimizerplus.load_comments=1.
I've read around and it seems like the issue might be with PHP7, which is what I've got installed locally - but I couldn't find a satisfactory solution. So I put everything inside a Vagrant box with PHP5.6, and the doctrine commands work just fine - but I can't see how to load the site itself. I've made the start dir from your code tar into the Vagrant docroot, but when I go to the corresponding URL I just get a forbidden. I can't see an index file or anything like that in your code.
Am I missing something?
Thanks
Yo James D!
Let's see if I can help!
Basically, you need to tweak some php.ini settings so that annotations load. I'm a bit surprised to see this, as I believe the default settings in PHP 7 should be ok... but no problem. First, find your php.ini file by running:
Now that you know where your php.ini file lives, open it and add/modify:
Then, restart your web server if you're using one. If you're using
bin/console server:runcommand, just stop and restart that.Ah, so here's the trick! First, because we're going to use the built-in PHP web server (just for development), you don't actually need to put your code in the Vagrant docroot - but that certainly doesn't hurt anything! When you start the built-in web server, you need to bind to all IP addresses (if this is the first time you've done this, I admit, it looks a bit technical:
Now, find what the IP address is to your Vagrant box - e.g 192.168.33.10. Then, put that in your browser:
http://192.168.33.10:8000.That should load the site! In a nut-shell, you first start the built-in PHP web server inside Vagrant. But to access it on your host machine, you won't use "localhost" like normal - as Vagrant is like having a remote machine. Instead, you'll use the IP address to your Vagrant box. The reason we need to do the
0.0.0.0thing is that - by default - the PHP web server only accepts requests coming from the local (in this case Vagrant) machine. By saying0.0.0.0, it says "Respond to requests that come from ANY IP address". This allows us to access from our outside (host) machine.And if you want to use Nginx or Apache, you can! But Symfony's "document root" is actually the "web" directory. The easiest way to make this work is to do some symbolic link magic. For example, suppose /var/www is the document root in Vagrant. I would do this:
That's it! Suddenly, your document root - /var/www - points to the "web" directory of your project.
Let me know if any of this helped! Hopefully we can get your past the first hurdle - setup problems are no fun ;).
Cheers!
Amazing - thanks for your help, Ryan.
I ended up figuring it out like this (subtly different to your answer, so I thought I'd post here in case it helps anyone else):
The opcache settings are actually in /usr/local/php5/php.d/20-extension-opcache.ini, rather than in the core php.ini file (I found it by using php --ini, as you suggested).
By default opcache is switched off, so you can't use opcache.load_comments. In order to switch it on, I commented out the path to opache.so (which doesn't seem to be there), and I enabled opcache and the comments extension, like this:
;zend_extension=/usr/local/php5/lib/php/extensions/no-debug-non-zts-20151012/opcache.so
[opcache]
opcache.enable=1
opcache.load_comments=1
Then I reloaded Apache and Bob's your uncle.
FYI I'm using PHP7 and Apache 2 on a Mac running El Capitan.
Thanks for getting back to me. You guys are always really really helpful on the forums - it's one of the reasons I think this site is totally worth paying for.
You're awesome for sharing your fix details :). Cheers!
The url to the dino images are not working for example http://0.tqn.com/y/dinosaur... does not exist. I think this needs to be updated
Hey Hank
Look like the fixtures links are broken, thanks for informing us. We will fix it as soon as possible.
Cheers!
When I use dump($event) I get the error:
ContextErrorException in VarCloner.php line 144:
Notice: A non well formed numeric value encountered
in VarCloner.php line 144
at ErrorHandler->handleError('8', 'A non well formed numeric value encountered', '/var/www/aqua_note/vendor/symfony/symfony/src/Symfony/Component/VarDumper/Cloner/VarCloner.php', '144', array('var' => object(GetResponseEvent), 'useExt' => false, 'i' => '0', 'len' => '1', 'pos' => '0', 'refs' => '0', 'queue' => array(array(object(GetResponseEvent))), 'arrayRefs' => array(), 'hardRefs' => array(), 'objRefs' => array(), 'resRefs' => array(), 'values' => array(), 'maxItems' => '2500', 'maxString' => '-1', 'cookie' => object(stdClass), 'gid' => '33780576259ce40784a5ff7.42781480', 'a' => null, 'stub' => null, 'zval' => array('type' => 'object', 'zval_isref' => false, 'zval_hash' => null, 'array_count' => null, 'object_class' => null, 'object_handle' => null, 'resource_type' => null), 'hashMask' => '65 (0) refcount(2){ } ', 'hashOffset' => '8', 'indexed' => true, 'j' => '0', 'step' => array(object(stdClass)), 'v' => object(GetResponseEvent), 'k' => '0'))
What could be wrong?
Hey Hank
This is a Symfony (old version) incompatibility with PHP 7, you need to upgrade your Symfony's version to at least 2.7 or higher
Cheers!
Thanks @Diego Aguiar:disqus I'll do that!
Hello,
I was wondering if I can get some help on this as I am trying to understand EventSubscriber in forms. I have a form and in that i have
->addEventSubscriber(new SkillsSubscriber())but inside the the subscriber classSkillsSubscriber()none of my events are firingAll I am trying to do is when the form is submitted, I want to get hold of the data and change few things before its inserted in the database. What am i missing here? I am not sure if I should be using subscriber or listener here.
Hey Shairyar!
I believe you forgot to register your listener as a service (You can watch how in the video, around 4:30). Subscribers and listeners are almost the same, the only thing that changes is the way that they are hooked up into the system.
FYI: there are "FormEvents" that you can attach directly to your "FormTypes", I think they are perfect for your current task, you can read more about it here: http://symfony.com/doc/curr...
Have a nice day!
Thanks for getting back. It was a stupid mistake at my end the subscriber was implemented just fine I was looking at the wrong route :) everything is working as expected. thanks
"Houston: no signs of life"
Start the conversation!