having a bit of trouble understanding Event Listeners. I have the following action in my controller
public function createAction(Request $request)
{
try {
$na_command = strtoupper($request->get('na_command'));
$na_is_connecting = $request->get('na_is_connecting');
// ==== validate (removed) ==== //
$em = $this->getDoctrine()->getManager();
$alert = new AvailabilityAlert();
$alert->setSearchCommand($na_command);
$alert->setIsConnecting($na_is_connecting);
$em->persist($alert);
$em->flush();
return new JsonResponse('Success');
}catch (Exception $e) {
}
}
I have a normal form (not form builder) and I create an Alert from the data and send it to my database. If successful, a Success message is sent back to my ajax.
Here's the problem. If the createAction is successful, I need it to send the alert to another class and do some stuff with it. So it was suggested that an Event Listener could do this for me. So I created a listener to test
<?php
namespace Nick\AlertBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
class AvailabilityAlertListener
{
public function prePersist(LifecycleEventArgs $args)
{
die('Something is being inserted!');
}
}
I then added it to services.yml
services:
doctrine.availability_alert_listener:
class: Nick\AlertBundle\EventListener\AvailabilityAlertListener
arguments: []
tags:
- { name: doctrine.event_listener, event: prePersist }
But then how do I make the createAction listen for this event? I am a bit lost so any advice appreciate. Essentially, at the end of createAction, I want to do something like
include 'uapi_cron_single.php';
AddFlightsAction($alert);
But I dont want to do it like the above because it is messy. But if createAction is successful, I need the alert to be sent to another class, so whats the best way to do this?
Thanks
First of all when you define a service without parameters you don't have to specify arguments: [], you could simply write that
services:
doctrine.availability_alert_listener:
class: Nick\AlertBundle\EventListener\AvailabilityAlertListener
tags:
- { name: doctrine.event_listener, event: prePersist }
Second, do you know that event will be fired before entity is persisted? I suppose that's not what you want (I suppose) as you were writing about trigger an event after object is inserted into DB. So you should modify your service definition as follows
services:
doctrine.availability_alert_listener:
class: Nick\AlertBundle\EventListener\AvailabilityAlertListener
tags:
- { name: doctrine.event_listener, event: postPersist }
and your service class
<?php
namespace Nick\AlertBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
class AvailabilityAlertListener
{
public function postPersist(LifecycleEventArgs $args)
{
die('Something is being inserted!');
}
}
But that's not all ...
Third, this event listener will be raised every time an object, no matter what kind (class) it has, is persisted to db. Of course this is not what you want to.
First solution to check only for "a kind" of object beign persisted is to retrieve entity from LifecycleEventArgs argument and check with instanceof for correctness the class
<?php
namespace Nick\AlertBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
class AvailabilityAlertListener
{
public function postPersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof \Your\Bundle\Path\To\AvailabilityAlert) {
/** do something **/
}
}
}
BUT
If you persist that kind of object only there you could also create a service on your own that not follows the logic behind event listener/subscribers and, once persisted an object of AvailabilityAlert, do some action by recalling one of his methods.
Returning to your question
But then how do I make the createAction listen for this event? I am a
bit lost so any advice appreciate.
now you should have understood that event is triggered "automatically" after object is persisted to DB.
One more advice
I've noticed that you want to do
include 'uapi_cron_single.php';
AddFlightsAction($alert);
that's not correct. In Symfony2 you should use autoloading and namespacing to "include" classes from other files.
I would suggest you dispatch custom event and then create custom listener. In Symfony2 docs there is nice entry about it: http://symfony.com/doc/current/components/event_dispatcher/introduction.html
Correct me if i'm wrong, but this listener which you have created is an DoctrineListener. So it will be launched on ANY entity prePersist event. So you would need to determine if entity that you need is persisted. But i'm not 100% sure.
Related
I have created an event listener class for postPersist, PostUpdate and postRemove methods of doctrine.
I need logged in user id in my class, i have tried injecting #security.context , #security.token_storage and #session
I got circular reference error even i have tried injecting #service_container and use container->get() i got same circular reference error.
ServiceCircularReferenceException: Circular reference detected for > service "doctrine.orm.default_entity_manager"
my code in serviec.yml code is like
my.listener:
class: \projectCreateEventListener
arguments: ["#service_container"]
tags:
- { name: doctrine.event_listener, event: postPersist }
- { name: doctrine.event_listener, event: postUpdate }
- { name: doctrine.event_listener, event: postRemove }
my event listener class is like
class myListener
{
private $container;
public function
__construct(ContainerInterface $container)
{
$this->container = $container;
}
public function prePersist(LifeCycleEventArgs $args)
{
$entity = $args->getEntity();
//Circular reference error
$user = $this->container->get('security.context')-
>getToken()->getUser();
//getToken() is always null
//Circular reference error
$user = $this->container->get('security.token_storage')-
>getToken()->getUser();
//getToken() is always null
//Circular reference error
$userId = $this->container->get('auth.user')-
>getIdentity()['id'];
}
}
Although i am getting logged in user information in my code before $this->persist() in $this->container->get('auth.user')->getIdentity()['id'];
That's a tricky one. When creating the doctrine service the listener is attached while constructing. If the listener you are trying to build uses another service (or some other service down the line) that requires doctrine in any way you get the circular reference.
But you can build around it.
Inject the eventDispatcher into your Listener
Create a custom event (or multiple) http://symfony.com/doc/current/components/event_dispatcher.html#creating-and-dispatching-an-event
Dispatch the event in the prePersist, postPersists methods.
Build another listener that subscribes to your custom event and handle your logic there.
What this achieves is: Your custom event listener will only be initialized when your custom event is actually fired. At that point the crucial services like doctrine are already up and running and you avoid the circular reference problem.
Had a similar issue in my project and solved it this way. Not really sure if it's the most elegant way but it definitely works. (If someone has a better solution i'd welcome it too).
I have a "standard procedure" that I need in EVERY route I call in Symfony.
I basically create a short (relatively) unique number, check the permission and log that the function has been called.
Here is one example:
**
* #Route("/_ajax/_saveNewClient", name="saveNewClient")
*/
public function saveNewClientAction(Request $request)
{
/* Create Unique TrackNumber */
$unique= $this->get('log')->createUnique();
/* Check Permission */
if (!$permission = $this->get('permission')->needsLevel(2, $unique)) {
/* Log Action */
$this->get('log')->writeLog('No Permission, 2 needed', __LINE__, 4);
return new JsonResponse(array(
'result' => 'error',
'message' => 'Insufficient Permission'
)
);
}
/* Log Action */
$this->get('log')->writeLog('called '.__FUNCTION__, __LINE__, 1, $unique);
return $this->render(':admin:index.html.twig', array());
}
Is there a way to put all that in one function somewhere?
The writeLog gets called at other parts in the functions as well, so I don't want to combine it with the permisson check, although that would be possible of course.
When creating an EventListener, do I still have to call it in every function or is it possible to have it automatically called?
Any hint appreciated!
You could try to make a beforefilter.
http://symfony.com/doc/current/cookbook/event_dispatcher/before_after_filters.html
How to Set Up Before and After Filters
It is quite common in web application development to need some logic to be executed just before or just after your controller actions acting as filters or hooks.
Some web frameworks define methods like preExecute() and postExecute(), but there is no such thing in Symfony. The good news is that there is a much better way to interfere with the Request -> Response process using the EventDispatcher component.
Token Validation Example
Imagine that you need to develop an API where some controllers are public but some others are restricted to one or some clients. For these private features, you might provide a token to your clients to identify themselves.
So, before executing your controller action, you need to check if the action is restricted or not. If it is restricted, you need to validate the provided token.
Please note that for simplicity in this recipe, tokens will be defined in config and neither database setup nor authentication via the Security component will be used.
Before Filters with the kernel.controller Event
First, store some basic token configuration using config.yml and the parameters key:
YAML
# app/config/config.yml
parameters:
tokens:
client1: pass1
client2: pass2
Tag Controllers to Be Checked
A kernel.controller listener gets notified on every request, right before the controller is executed. So, first, you need some way to identify if the controller that matches the request needs token validation.
A clean and easy way is to create an empty interface and make the controllers implement it:
namespace AppBundle\Controller;
interface TokenAuthenticatedController
{
// ...
}
A controller that implements this interface simply looks like this:
namespace AppBundle\Controller;
use AppBundle\Controller\TokenAuthenticatedController;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class FooController extends Controller implements TokenAuthenticatedController
{
// An action that needs authentication
public function barAction()
{
// ...
}
}
Creating an Event Listener
Next, you'll need to create an event listener, which will hold the logic that you want executed before your controllers. If you're not familiar with event listeners, you can learn more about them at How to Create Event Listeners and Subscribers:
// src/AppBundle/EventListener/TokenListener.php
namespace AppBundle\EventListener;
use AppBundle\Controller\TokenAuthenticatedController;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class TokenListener
{
private $tokens;
public function __construct($tokens)
{
$this->tokens = $tokens;
}
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
/*
* $controller passed can be either a class or a Closure.
* This is not usual in Symfony but it may happen.
* If it is a class, it comes in array format
*/
if (!is_array($controller)) {
return;
}
if ($controller[0] instanceof TokenAuthenticatedController) {
$token = $event->getRequest()->query->get('token');
if (!in_array($token, $this->tokens)) {
throw new AccessDeniedHttpException('This action needs a valid token!');
}
}
}
}
Registering the Listener
Finally, register your listener as a service and tag it as an event listener. By listening on kernel.controller, you're telling Symfony that you want your listener to be called just before any controller is executed.
YAML
# app/config/services.yml
services:
app.tokens.action_listener:
class: AppBundle\EventListener\TokenListener
arguments: ['%tokens%']
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
With this configuration, your TokenListener onKernelController method will be executed on each request. If the controller that is about to be executed implements TokenAuthenticatedController, token authentication is applied. This lets you have a "before" filter on any controller that you want.
I've been following example code to try and get my doctrine event listener to work. However, even though the class is being instantiated into an object (I know this because I logged the __construct, and the __destructor also gets invoked, the postPersist function never does.
My services.yml file has the following (located in AH/Core/SolutionBundle/Resources/config/services.yml):
solutions_core_reverse_sync:
class: AH\Core\SolutionBundle\Listener\ClientSolutionReverseSyncListener
arguments: [#service_container]
tags:
- { name: doctrine.event_listener, event: postPersist }
(Also, the services.yml file is being loaded in AH/Core/SolutionBundle/DependencyInjection/SolutionExtension.php - confirmed because other services are running just fine)
My Entity is just a standard doctrine entity, nothing special about it, except for using a few extra annotation based integrations, like the JMS Serializer. The only thing that is different to most other entities, is the fact that we use the standard SingleTableInheritence from Doctrine, using an #ORM\DiscriminatorMap annotation and the child-entities.
My listener only has a skeleton right now, to test whether it works without anything interfering:
<?php
namespace AH\Core\SolutionBundle\Listener;
use Symfony\Component\DependencyInjection\Container;
use Doctrine\ORM\Event\LifecycleEventArgs;
class ClientSolutionReverseSyncListener
{
protected $container;
public function __construct(Container $container)
{
$this->container = $container;
echo __CLASS__.' __construct'.PHP_EOL;
}
public function postPersist(LifecycleEventArgs $args)
{
echo __CLASS__.' postPersist fired'.PHP_EOL;
}
public function __destruct()
{
echo __CLASS__.' __destruct'.PHP_EOL;
}
}
When testing it, and running the below code, I only see the __construct and the __destruct run (by way of echoing) but not the postPersist:
$cs = $csm->findClientSolutionById(123); // don't worry where $csm comes from
$cs->setUid('do some update: '.rand(0,10000));
$this->em->persist($cs);
Sample output:
AH\Core\SolutionBundle\Listener\ClientSolutionReverseSyncListener __construct
AH\Core\SolutionBundle\Listener\ClientSolutionReverseSyncListener __destruct
I'm at a loss of where I went wrong here, it follows the docs super close:
Doctrine documentation
I also checked this doc, which is similar to the above:
Symfony documentation around listeners
Here is the explanation, and the implementation So, you need to flush the changes, if you want the event to be fired. Persisting entities without flushing them doesn't generate primary key. Also persisting entities doesn't call the database insert operations.
I'm scratching my head over this one, so maybe someone can help me out. I've done this before without any issue, but I'm new to Symfony so it's likely I'm missing something this time around.
I'm trying to load an event listener to fire some code whenever I save an entity.
In my app/config/config.yml I put this.
services:
fu_bar.listener:
class: Fu\BarBundle\EventListener\AuthCheckListener
tags:
- { name: doctrine.event_listener, event: postPersist, connection: default }
- { name: doctrine.event_listener, event: preUpdate, connection: default }
In my Fu\BarBundle\EventListener\AuthCheckListener I'm just doing this so I can see I'm hitting these methods, which I'm not.
<?php
namespace Fu\BarBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
class AuthCheckListener {
public function preUpdate(LifecycleEventArgs $args) {
file_put_contents('/tmp/yyy', 'ffdf');
}
public function postPersist(LifecycleEventArgs $args) {
file_put_contents('/tmp/xxx', 'fff');
//$this->syncAuth($args);
}
}
When I save an entity, I'm expecting to see some file(s) in /tmp, but I'm not. It doesn't appear that the event listener is being registered.
What am I missing here?
doctrine:event_listener should be doctrine.event_listener
use dot notation for services and parameters
use colon notation for controllers,views, and these kind of "objects"
I have multilingual site that uses xml for diffrent languages. (I know that Symfony Translations exists, but i'm implementing my own system for accessing and taking xml elements values and i wanted to test it).
I'm not going to put the entire code here for readability, the implementation is same as in this link.
I have a service under a name xml_handler. The service I fetched in the controller with $this->get('xml_handler'). But then, I started using Symfony2 best practices and started extending ContainerAware. After that, the code below, which is defined as a Before Listener, failed.
if($controller[0] instanceof LanguageInterface) {
$xmlHandler = $controller[0]->get('xml_handler');
It raises an error that there is no get() method in $controller (IndexController is the name of the controller that extends ContainerAware, but that doesn't really matter, just for clarity)
The problem arose after I stopped extending Controller but started to extend ContainerAware, as i said earlier.
So, how do i get the get() method, which is protected so obviously, trying :
$controller[0]->container->get('xml_handler')
Doesn't work. I've looked at the API but there isn't a getContainer() method.
I guess, the questing is, how do I access ContainerAware outside the controller, in my case, in the Before filter?
Thanks for the answers.
You must pass all the required dependencies to your listener constructor:
In your listener :
<?php
namespace Acme\MyBundle\EventListener;
class MyListener
{
private $xmlHandler;
public function __construct($xmlHandler)
{
$this->xmlHandler = $xmlHandler;
}
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if (!is_array($controller)) {
return;
}
if ($controller[0] instanceof LanguageInterface) {
$this->xmlHandler->doSomething(...);
// ...
In your configuration file you have to ask the dependency injection component to provide the xml_handler service as an argument of your listener constructor (a dependency).
services:
acme_my.listener.action:
class: Acme\MyBundle\EventListener\MyListener
arguments:
- "#xml_handler"
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }