Symfony monolog after doctrine persist - symfony

I need to log all my users actions with monolog. But only if the actions persist data with doctrine, insert, update or delete.
What should I do ? Could I define a generic method like "afterPersist" to log every action ?
Thx !
EDIT :
The Listener :
use Doctrine\ODM\MongoDB\Event\OnFlushEventArgs;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\SecurityContextInterface;
class DatabaseLogger
{
protected $logger;
protected $security_context;
protected $request;
public function __construct(LoggerInterface $logger, ContainerInterface $service_container)
{
$this->logger = $logger;
$this->setSecurityContext($service_container->get('security.context'));
}
public function setRequest(RequestStack $request_stack)
{
$this->request = $request_stack->getCurrentRequest();
}
public function setSecurityContext(SecurityContextInterface $security_context)
{
$this->security_context = $security_context;
}
public function onFlush(OnFlushEventArgs $args)
{
// configure this however you want
}
}
and in service.yml
cc.listener.database_logger:
class: Cc\HitoBundle\Listener\DatabaseLogger
tags:
- { name: doctrine_mongodb.odm.event_listener, event: onFlush }
- { name: monolog.logger, channel: database_access }
calls:
- [ setRequest, [#request_stack] ]
arguments: [ #logger, #service_container ]
I got an error when I add the security context :
ServiceCircularReferenceException: Circular reference detected for service "doctrine_mongodb.odm.default_document_manager", path: "doctrine_mongodb.odm.default_document_manager -> doctrine_mongodb.odm.default_connection -> doctrine_mongodb.odm.event_manager -> cc.listener.post_persist -> security.context -> security.authentication.manager -> security.user.provider.concrete.user_db".

Register a listener with something like:
Build a listener:
namespace Acme\MyBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
class PersistLogger
{
public $logger;
public function __construct($logger)
{
$this->logger = $logger;
}
public function postPersist(LifecycleEventArgs $args)
{
// configure this however you want
$this->logger->addDebug('whatever');
}
}
Register the listener in config.yml
acme_mybundle.eventlistener.persistlogger:
class: Acme\MyBundle\EventListener\PersistLogger
tags:
- { name: doctrine.event_listener, event: postPersist }
argument: [ #logger ]
EDIT:
Injecting the security context into a doctrine listener causes a circular reference exception if you are storing your users in the database (e.g. with FOSUserBundle). This is because the security context needs to inject the entity manager so it can get users from the database, but because of the listener, the entity manager depends on the security context.
The workaround is to inject the whole service container (one of the only times doing this is justified), and get the security context from there:
namespace Acme\MyBundle\EventListener;
use Psr\Log\LoggerInterface,
Symfony\Component\DependencyInjection\ContainerInterface,
Symfony\Component\Security\Core\SecurityContextInterface;
protected $service_container;
protected $logger;
public function __construct(LoggerInterface $logger, ContainerInterface $service_container)
{
$this->service_container = $service_container;
$this->logger = $logger;
}
public function getSecurityContext()
{
return $this->service_container->get('security.context');
}
and
acme_mybundle.eventlistener.persistlogger:
class: Acme\MyBundle\EventListener\PersistLogger
tags:
- { name: doctrine.event_listener, event: postPersist }
argument: [ #logger, #service_container ]

I think that you may have a look to the cookbook, there is a very nice entry that talk about Doctrine's events.
In addition, you may have a look to the method to create custom monolog chanels.

Related

Inject EventDispatcher in service doesn't work in production environment - symfony 3.4

let's say I have a listener as a service like this
class MyListener
{
/**
* #var Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher
*/
private $dispatcher;
function __construct(ContainerAwareEventDispatcher $dispatcher)
{
$this->dispatcher = $dispatcher;
}
public function onKernelRequest(GetResponseEvent $event)
{
//in my logic here I use $this->dispatcher->dispatch(my-own-event)
}
}
service.yml
ap.my_listener:
class: my-name-space\MyListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
arguments: ["#debug.event_dispatcher.inner"]
It's work fine in development environment but when I switch to production environment after clearing cache I get this exception:
The service "ap.my_listener" has a dependency on a non-existent service "debug.event_dispatcher.inner"
As I pressed by time I just inject all container, so what happened exactly and there is another way to inject Event Dispatcher without retrieve it from container
you should just inject the event dispatcher and use an interface like
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class MyListener
{
/**
* #var Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
private $dispatcher;
function __construct(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
}
public function onKernelRequest(GetResponseEvent $event)
{
//in my logic here I use $this->dispatcher->dispatch(my-own-event)
}
}
//service.yml
ap.my_listener:
class: my-name-space\MyListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
arguments: ["#event_dispatcher"]

Injecting EntityManager-dependend service into Listener

I am trying to inject one of my services into an EntityListener in order to call some application specific behaviour when an entity gets updated.
My Logger service, used to store events in a LogEntry entity in my database:
class Logger
{
/**
* #var EntityManager $manager The doctrine2 manager
*/
protected $manager;
//...
}
The listener:
class EntityListener
{
public function __construct(Logger $logger)
{
$this->logger = $logger;
// ...
}
}
And the service definitions in my service.yml:
listener:
class: Namespace\EntityListener
arguments: [#logger]
tags:
- { name: doctrine.event_listener, event: preUpdate }
logger:
class: Namespace\Logger
arguments: [#doctrine.orm.entity_manager]
Unfortunately it results in a ServiceCircularReferenceException:
Circular reference detected for service "doctrine.orm.default_entity_manager", path: "doctrine.orm.default_entity_manager -> doctrine.dbal.default_connection -> listener -> logger".
The problem obviously is that I inject the doctrine into the my service while it is also automatically injected into my listener. How do I proceed? I found a very similar question but the accepted answer is to inject the container which is obviously not favourable.
Any suggestions on how to solve my issue would be appreciated.
Small side note: I would like to avoid a solution depending on lazy services if possible.
First of all I switched from an EventListener to an EventSubscriber. From the docs:
Doctrine defines two types of objects that can listen to Doctrine events: listeners and subscribers. Both are very similar, but listeners are a bit more straightforward.
It turns out one can access the ObjectManager via the passed $args-parameter like so:
/** #var Doctrine\Common\Persistence\ObjectManager $manager */
$manager = $args->getObjectManager();
So either use it directly in the callback:
public function postUpdate(LifecycleEventArgs $args)
{
$manager = $args->getObjectManager();
// ...
...or set it to an object field:
/** #var ObjectManager $manager */
private $manager;
public function postUpdate(LifecycleEventArgs $args)
{
$this->manager = $args->getObjectManager();
// ...
After struggling with the same problem, I found out that using lazy loading solved my issue.
listener:
class: AppBundle\EventListener\OrderDoctrineListener
tags:
- { name: doctrine.event_listener, event: postPersist, lazy: true }

#mailer and #twig in argument of a service error ServiceCircularReferenceException

I'm trying to put twig like argument of my service but i have always the same error :
ServiceCircularReferenceException in bootstrap.php.cache line 2129
Circular reference detected for service "doctrine.orm.default_entity_manager",path: "doctrine.orm.default_entity_manager -> doctrine.dbal.default_connection -> wh.participant_listener -> wh.participant_notification -> twig -> security.authorization_checker -> security.authentication.manager -> fos_user.user_provider.username -> fos_user.user_manager".`
This is my service.yml file
wh.participant_notification:
class: WH\TrainingBundle\Notification\Notification
arguments: [#mailer, #twig]
wh.participant_listener:
class: WH\TrainingBundle\EventListener\ParticipantListener
arguments: [#wh.participant_notification]
tags:
- { name: doctrine.event_listener, event: postUpdate }
- { name: doctrine.event_listener, event: postPersist }
My PartcicipantListenerFile
namespace WH\TrainingBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use WH\TrainingBundle\Notification\Notification;
class ParticipantListener
{
protected $notification;
public function __construct(Notification $notification)
{
$this->notification = $notification;
}
}
This probleme exist only when i pass #wh.participant_notificationin arguments of my second service
Any body has an idea ?
Thank's a lot
I've find a solution, not pretty, but it works :
First i pass the service container in argument of my service
services:
wh.participant_notification:
class: WH\TrainingBundle\Notification\Notification
arguments: ['#service_container']
wh.participant_listener:
class: WH\TrainingBundle\EventListener\ParticipantListener
arguments: ['#wh.participant_notification']
tags:
- { name: doctrine.event_listener, event: postPersist }
then in my Notification.php class :
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
private $container;
public function __construct(Container $container) {
$this->container = $container;
}
public function subscribValidation ($participant) {
$templating = $this->container->get('templating');
$mailer = $this->container->get('mailer');
...
I can't create protected var $twig because the probleme persiste.
I repeat, its only with twig service (or template).
Maybe another one find a better solution ...
The circular message, while unclear, should guide you.
Doctrine entity manager loads its listeners,wh.participant_notification among them. Your service requires twig which in turns requires a chain of other things, doctrine entity manager among them. This causes the exception above.
One solution to this issue could be to use setter injection
So you can just define your service as:
wh.participant_notification:
class: WH\TrainingBundle\Notification\Notification
calls:
- [setMailer, ["#mailer"]]
- [setTemplating, ["#templating"]]
and add to your Notification class the setter methods
class Notification
{
private $mailer;
private $templating;
public function setMailer(\Mailer $mailer)
{
$this->mailer = $mailer;
}
public function setTemplating(\Symfony\Bundle\TwigBundle\TwigEngine $templating)
{
$this->templating= $templating;
}
...your code...

Login EventListener not firing on production side

I have an event listener that should fire when a user logs in in my Symfony setup.
I have the following in my services.yml
...
d_user.login_listener:
class: D\UserBundle\EventListener\LoginListener
arguments: []
tags:
- { name: 'kernel.event_subscriber', event: 'security.interactive_login' }
and in my login listener I simply have this:
<?php
namespace D\UserBundle\EventListener;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class LoginListener implements EventSubscriberInterface
{
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
echo 'user logged in';
}
public static function getSubscribedEvents()
{
return array(
// must be registered before the default Locale listener
'security.interactive_login' => array(array('onSecurityInteractiveLogin', 18))
);
}
}
On my dev server I correctly see the text "user logged in" before redirection, but on my production server it just logs in without the event firing. I'm modifying this later to set a session var on user login. I just need to get the method called.
Any advice? I've tried clearing the cache for prod and dev on my production environment, but that didn't help.
Debugging with echo is discouraged. If you want an output, use the logger!
namespace D\UserBundle\EventListener;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class LoginListener implements EventSubscriberInterface
{
private $logger;
private $router;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
$this->logger->info('user logged in');
}
public static function getSubscribedEvents()
{
return array(
// must be registered before the default Locale listener
'security.interactive_login' => array(array('onSecurityInteractiveLogin', 18))
);
}
}
and inject the logger in your service defintion, preferrable with a nice channel name:
d_user.login_listener:
class: D\UserBundle\EventListener\LoginListener
arguments: [#logger]
tags:
- { name: 'kernel.event_subscriber', event: 'security.interactive_login' }
- { name: monolog.logger, channel: d_user }

Set parameter for Doctrine filter on every request in Symfony2

I have a Doctrine filter in Symfony2 project. I am trying to set filter's parameter to some value (taken from session) on every request.
The problem is that filter object is created after Symfony's onKernelRequest event, so I can't set it from there. If I try to set it in Doctrine's postConnect event circular dependency is detected:
ServiceCircularReferenceException: Circular reference detected for service "doctrine.orm.private_entity_manager", path: "routing.loader -> assetic.asset_manager -> twig -> translator.default -> doctrine.orm.private_entity_manager -> doctrine.dbal.private_connection -> year_visibility.parameter_setter".
The question is, where (or rather how) should I set filter's parameter?
You can try to define filters manually and pass required parameters at the same time.
services:
app.filter_manager:
class: App\Bundle\AppBundle\Filter\FilterManager
arguments: [#doctrine.orm.entity_manager, #session]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
And in the filter manager class:
// ...
public function __construct(EntityManager $em, SessionInterface $session)
{
$this->em = $em;
$this->session = $session;
}
// ...
public function onKernelRequest()
{
$this->em->getConfiguration()->addFilter('filter_name', 'Filter/Class/Name/With/Ns');
$filter = $this->em->getFilters()->enable('filter_name');
$filter->setParameter('param_name', $this->session->get('param_name'));
}
As seen here: https://stackoverflow.com/a/14650403/244058 ,
you can have an instance of your Filter class at kernel boot.
So, your instance would be available very early.
<?php
class MyBundle extends Bundle
{
public function boot()
{
$em = $this->container->get('doctrine.orm.default_entity_manager');
$conf = $em->getConfiguration();
$conf->addFilter(
'filter_name',
'Doctrine\Filter\TestFilter'
);
// either enable it here, or later in the event listener
$em->getFilters()->enable('filter_name');
}
}
After that, just add a kernel.event_listener that listens on kernel.request and set a filter parameter (something like this):
<?php
class DoctrineSqlFilterConfigurator
{
private $em; // inject the entity manager somehow (ctor is a good idea)
public function onKernelRequest(GetResponseEvent $event)
{
$filter = $this->em->getFilters()->enable('filter_name');
$filter->setParameter('param_name', $event->getRequest()->getSession()->get('param_name'));
}
}

Resources