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

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...

Related

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 }

Monolog in Symfony from non-controller Class

I have the following class:
namespace AppBundle\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use FOS\UserBundle\Event\UserEvent;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
class UserRegistrationListener implements EventSubscriberInterface
{
protected $logger;
public function __construct($logger)
{
$this->logger = $logger;
}
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_INITIALIZE => 'onRegistrationInit',
);
}
/**
* take action when registration is initialized
* set the username to a unique id
* #param \FOS\UserBundle\Event\FormEvent $event
*/
public function onRegistrationInit(UserEvent $userevent)
{
$this->logger->info("Log Something");
$user = $userevent->getUser();
$user->setUsername(uniqid());
}
}
and I have been trying for hours to log something with monolog from within it but have had no luck.
I have read much of the documentation and I believe I need to somehow 'Inject' monolog as a service. What I have read however does not seem to be clear to me.
Some details:
#config_dev.yml
monolog:
channels: [chris]
handlers:
mylog:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%_chris.log"
channels: chris
formatter: monolog.my_line_formatter
.
#services.yml
services:
monolog.my_line_formatter:
class: Monolog\Formatter\LineFormatter
arguments: [~, ~, true]
app.user_registration:
class: AppBundle\EventListener\UserRegistrationListener
arguments: [#logger] ## changed to [#monolog.logger.chris] to us custom channel
tags:
- { name: kernel.event_subscriber }
What do I have to do to get Monolog working with my formatter inside this class?
UPDATE:
#griotteau I have done what you have posted in your answer but I still get an error:
CRITICAL - Uncaught PHP Exception Symfony\Component\Debug\Exception\ContextErrorException: "Warning: Missing argument 1 for AppBundle\EventListener\UserRegistrationListener::__construct(), called in ...filepath...\app\cache\dev\appDevDebugProjectContainer.php on line 384 and defined" at ...filepath...\src\AppBundle\EventListener\UserRegistrationListener.php line 18
SOLVED ERROR I already had a service with the same class (not shown in ym question). #griotteau 's answer is correct.
You can pass arguments when you declare your service
In services.yml :
app.user_registration:
class: AppBundle\EventListener\UserRegistrationListener
arguments: [#logger]
tags:
- { name: kernel.event_subscriber }
In your class, add a constructor :
protected $logger;
public function __construct($logger)
{
$this->logger = $logger;
}
So when you want to add a log :
$this->logger->info(...);

Argument passed to controller must be an instance of ContainerInterface, instance of appDevDebugProjectContainer given

Why do I have this error?
Catchable Fatal Error: Argument 1 passed to Application\Sonata\ProductBundle\Controller\ProductAdminController::__construct() must be an instance of ContainerInterface, instance of appDevDebugProjectContainer given
Here is my services.yml:
services:
product_admin_controller:
class: Application\Sonata\ProductBundle\Controller\ProductAdminController
arguments: ["#service_container"]
tags:
- { name: doctrine.event_listener, event: postLoad, connection: default }
And my controller:
class ProductAdminController extends Controller
{
protected $container;
public function __construct(\ContainerInterface $container)
{
$this->container = $container;
}
}
You have to inject the container by the "calls" option, not as an argument i think :
services:
product_admin_controller:
class: Application\Sonata\ProductBundle\Controller\ProductAdminController
arguments: ["#another_service_you_need"]
tags:
- { name: doctrine.event_listener, event: postLoad, connection: default }
calls:
- [ setContainer,["#service_container"] ]
Also, don't forget to create the public method "setContainer()" in your listener class.
First of all, why are you trying to use __constract()? Instead you have to use setContainer() method which takes ContainerInterface $container as an argument, it should look like this:
<?php
...
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\DependencyInjection\ContainerInterface;
...
class YourClass extends Controller
{
public function setContainer(ContainerInterface $container = null)
{
// your stuff
}
}
And the second question: what for do you need the container to be injected into controller? You can call any service with $this->get('{service_id}') statement instead of direct call of container.

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.

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