Symfony2 call subscriber doctrine and other - symfony

I create a subscriber and register a new service, what I do :
https://gist.github.com/Draeli/2c591c16409a5664ae58
<?php
namespace My\BlogBundle\Listener;
use Doctrine\ORM\Events;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
class BlogArticleDetailListener implements EventSubscriberInterface
{
/**
* #var ContainerInterface
*/
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
static public function getSubscribedEvents()
{
return array(
'doctrine.event_subscriber' => array(
array(Events::prePersist, 0),
array(Events::preUpdate, 0),
),
);
}
public function prePersist(FilterResponseEvent $event)
{
var_dump('prePersist');die;
}
public function preUpdate(FilterResponseEvent $event)
{
var_dump('preUpdate');die;
}
}
services:
my_blog.listener.blog_article_detail:
class: My\BlogBundle\Listener\BlogArticleDetailListener
arguments: ["#service_container"]
tags:
- { name: kernel.event_subscriber }
But in this case methods prePersist and preUpdate in spite of I persist objects, like if Doctrine didn't dispatch.
Someone know what is strong in what I do ?
(For explanation, now I register only doctrine event but after I will register more from same place)

Service
At present the listener is listening to event being dispatched using the Symfony kernel event dispatcher whereas you should be listening to the Doctrine event dispatcher.
Your service...
services:
my_blog.listener.blog_article_detail:
class: My\BlogBundle\Listener\BlogArticleDetailListener
arguments: ["#service_container"]
tags:
- { name: kernel.event_subscriber }
Should have the tag doctrine.event_subscriber rather than the kernel.
Subscriber
As you are creating a Doctrine subscriber rather than a Symfony kernel subscriber you should implementing \Doctrine\Common\EventSubscriber, meaning the getSubscribedEvents method shouldn't be static.
Currently your subscriber is listening for an event called doctrine.event_subscriber rather than listening to the Doctrine events.
You should actually just be listening to the Doctrine events using...
public function getSubscribedEvents()
{
return array(
Events::prePersist, // I'm not sure about setting
Events::preUpdate, // the priorities to be honest
);
}
With the Doctrine events you won't be getting FilterResponseEvent as Doctrine dispatches with specific argument objects (Doctrine\Common\Persistence\Event\LifecycleEventArgs mostly) on events. For more info on the arguments (and the events) you can view the documentation.

Related

Subscriber call twice symfony

I use FOSUserEvents after submit form but the subscriber call twice.
In this way my captcha is valid the first time and not valid the second
this is my code
<?php
namespace AppBundle\EventListener;
class CaptchaSubscriber implements EventSubscriberInterface
{
private $router;
private $requestStack;
private $templating;
/**
* RedirectAfterRegistrationSubscriber constructor.
*/
public function __construct(RouterInterface $router, RequestStack $requestStack, \Twig_Environment $templating)
{
$this->router = $router;
$this->requestStack = $requestStack;
$this->templating = $templating;
}
public function onRegistrationInit(GetResponseUserEvent $event)
{
if ($this->requestStack->getMasterRequest()->isMethod('post')) {
...handle captcha...
}
}
public static function getSubscribedEvents()
{
return [
FOSUserEvents::REGISTRATION_INITIALIZE => 'onRegistrationInit'
];
}
}
my symfony is 3.3
UPDATE
I added
$event->stopPropagation();
with this snippet the code works, but i don't know if it is the best practice
In my case of symfony 4.2 it depends on the service definition if it occures or not.
My Subscriber gets registered twice if I define the service like this:
# oauth process listener
app.subscriber.oauth:
class: App\EventListenerSubscriber\OauthSubscriber
arguments: ['#session', '#router', '#security.token_storage', '#event_dispatcher', '#app.entity_manager.user', '#app.fos_user.mailer.twig_swift']
tags:
- { name: kernel.event_subscriber }
But it gets registerd only once if I chenge the definition to this:
# oauth process listener
App\EventListenerSubscriber\OauthSubscriber:
arguments: ['#session', '#router', '#security.token_storage', '#event_dispatcher', '#app.entity_manager.user', '#app.fos_user.mailer.twig_swift']
tags:
- { name: kernel.event_subscriber }
I posted a bug report on github and got immediately an answer, that in newer symfony versions event listeners and subscribers get registered automatically with their class name as key (under some default conditions - must read on that topic).
So there is no need to register them explicitely as services.
I we do this anyway, but using an arbitrary key instead of class name, there will be two services.
If you are using Autowiring/Autoconfiguration, it's possible that you've added the subscriber service you show above, twice. I've done it myself when I first added the autowiring, but I also had the subscriber listed explicitly in the configuration as well.
You can see what events are registered (and check if any are registered more than once to perform the same service/action) with:
bin/console debug: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 }

Symfony monolog after doctrine persist

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.

How to assign roles on successful registration?

I'm using fos user bundle and pugx multi user bundle.
I've read all the documentation and I'm new to Symfony.
In the pugx multi user bundle there's a sample on every point but one: sucessful registration.
Samples of overriding controllers for generating forms => ok
Samples of overriding templates for generating forms => ok
Samples of overriding successful registration sample => nothing.
Here's my code:
class RegistrationController extends BaseController
{
public function registerAction(Request $request)
{
$response = parent::registerAction($request);
return $response;
}
public function registerTeacherAction()
{
return $this->container
->get('pugx_multi_user.registration_manager')
->register('MyBundle\Entity\PersonTeacher');
}
public function registerStudentAction()
{
return $this->container
->get('pugx_multi_user.registration_manager')
->register('MyBundle\Entity\PersonStudent');
}
}
The problem is with ->get('pugx_multi_user.registration_manager') which returns a manager. In the fos user overring controllers help, they get either a form or a form.handler. I'm having hard times to "link" those with the pugx_multi_user manager.
What code should I put in the registerTeacherAction() to set roles for teacher, and in registerStudentAction() to set roles for student on a successful registration?
Solution 1 (Doctrine Listener/Subscriber)
You can easily add a doctrine prePersist listener/subscriber that adds the roles/groups to your entities depending on their type before persisting.
The listener
namespace Acme\YourBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Acme\YourBundle\Entity\Student;
class RoleListener
{
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
// check for students, teachers, whatever ...
if ($entity instanceof Student) {
$entity->addRole('ROLE_WHATEVER');
// or
$entity->addGroup('students');
// ...
}
// ...
}
}
The service configuration
# app/config/config.yml or load inside a bundle extension
services:
your.role_listener:
class: Acme\YourBundle\EventListener\RoleListener
tags:
- { name: doctrine.event_listener, event: prePersist }
Solution 2 (Doctrine LifeCycle Callbacks):
Using lifecycle callbacks you can integrate the role-/group-operations directly into your entity.
/**
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks()
*/
class Student
{
/**
* #ORM\PrePersist
*/
public function setCreatedAtValue()
{
$this->addRole('ROLE_WHATEVER');
$this->addGroup('students');
}
Solution 3 (Event Dispatcher):
Register an event listener/subscriber for the "fos_user.registration.success" event.
How to create an event listener / The EventDispatcher component.

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