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.
Related
When i use the following annotation within an entity, the listener for it is called successfully. The problem with using the annotation rather than configuring it within the service config is that i cannot pass services into the listener through the constructor.
/**
#ORM\EntityListeners({CustomerLoadListener::class})
*/
I've tried setting the listener up in services.yaml using the following snippet:
services:
App\EventListener\CustomerLoadListener:
tags:
-
name: 'doctrine.orm.entity_listener'
event: 'postLoad'
entity: 'App\Entity\Customer'
Here is my event listener class:
<?php declare(strict_types = 1);
namespace App\EventListener;
use App\Entity\Customer;
class CustomerLoadListener
{
private CustomerService $customerService;
public function __construct(CustomerService $customerService)
{
$this->customerService = $customerService;
}
public function postLoad(Customer $customer): void
dd($customer);
// Do something with customer entity...
}
}
When i use the YAML config above, the listener is never called. yet the standard doctrine annotation works fine.
I want to use call the listener using the yaml variant so i can add custom arguments that are passed via its constructor.
The code works fine if i was to change the name of the event to 'doctrine.event_listener' instead of 'doctrine.orm.entity_listener'. The problem with that approach though is that the method would be called for every entity that is loaded which seems inefficient. I want it to only be called when a specific entity is loaded, which in my case is the Customer entity.
I just can't seem to figure out why when i use an entity_listener, it never seems to get called when i set it up in the yaml config.
I am working on a project based on Symfony 2. I have registered a listener like so:
services:
app.video_release_listener:
class: CmsBundle\EventListener\Video\VideoReleaseListener
tags:
- { name: doctrine.event_listener, event: postPersist }
... and my listener class looks like this:
namespace CmsBundle\EventListener\Video;
use Doctrine\ORM\Event\LifecycleEventArgs;
use AppBundle\Entity\Video;
class VideoReleaseListener
{
public function postPersist(LifecycleEventArgs $args){
$entity = $args->getEntity();
die(get_class($entity));
}
}
... which I had hoped would spit out the class name of my video entity when I saved a video. Instead, it spits out Gedmo\Loggable\Entity\LogEntry -- not what I want.
How do I get access to my video entity from within the listener?
====
Edit: I also tried putting a conditional check of the class around my die() statement in order to see whether there was perhaps another iteration with a different object. No second iteration occurs.
I didn't exactly fix the issue as described, but I got around it by adding an #ORM\EntityListeners tag on my video class like so:
/**
* #ORM\EntityListeners({"CmsBundle\EventListener\Video\VideoReleaseListener"})
*/
... and then switching my VideoReleaseListener method name to postUpdate(). (It refused to work as postPersist() for some reason.)
Anyhow, it's very flaky behavior, but maybe this helps someone.
I have a Symfony 3.4 app and a Composer package with an EntityChangeListener to log entity property changes. The package also contains a EntityListenerPass (compiler pass) which iterates over a list of class names defined in app config.yml while building the service container. It programmatically tags the entity classes like this to notify the listener on preUpdate events:
$listener = $container->getDefinition('entity_history.listener.entity_change');
$entities = $container->getExtensionConfig('entity_history')[0]['entities'];
foreach ($entities as $className) {
$listener->addTag('doctrine.orm.entity_listener', ['entity' => $className, 'event' => 'preUpdate']);
}
Adding those tags causes a lot of errors which appear unrelated. In example undefined index errors inside the Doctrine UnitOfWork for the entity states. Also related entities which are loaded from database suddenly are recognised as new by Doctrine. Even object comparison inside a switch statement started to fail with:
Fatal error: Nesting level too deep - recursive dependency?
But without those listeners, everything works fine and all tests pass. Is there an alternative/better way to programmatically set up Doctrine entity listeners?
Yes, you can attach entity listeners by acting directly on the class metadata. In my application (Symfony 2.8), I am doing this for some entities that are marked in my config by adding a listener that reacts to the loadClassMetadata event.
With this approach, you can hook your entity listeners when Doctrine loads for the first time the classmetada (by using addEntityListener). Thus, you only hook entity listeners that are needed for the current context, nothing more.
Here is a modified version of the listener I use to mirror how it could look in your particular case:
namespace AppBundle\Listener;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
class MappingListener
{
private $listenerClassname;
private $entities;
public function __construct($listenerClassname, array $entities)
{
$this->entities = $entities;
$this->listenerClassname = $listenerClassname;
}
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
if(!in_array($classMetadata->name, $this->entities))
{
return;
}
// Hook the entity listener in the class metadata
// $classMetadata->addEntityListener( string $eventName, string $class, string $method )
$classMetadata->addEntityListener('preUpdate', $this->listenerClassName, 'preUpdate');
}
}
And then in your services.yml, something like this:
mapping.listener:
class: AppBundle\Listener\MappingListener
arguments: [ "%your_listener_classname%", "%your_entities_array%" ]
tags:
- { name: doctrine.event_listener, event: loadClassMetadata, lazy: true }
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 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 }