If you saw my previous question, this is kind of linked to it but a new question. So I have an Entity and I have a listener linked up to this. In my createAction I create my Object and then persist-flush it to my database. In my listener, I have set up a postFlush function
public function postFlush(PostFlushEventArgs $args)
{
$em = $args->getEntityManager();
foreach ($em->getUnitOfWork()->getScheduledEntityDeletions() as $entity) {
if ($entity instanceof AvailabilityAlert) {
var_dump("TEST");
$this->api_service->addFlightsAction($entity);
}
}
}
What I am trying to do in this function is get the entity that was just flushed. I have tried all the different actions of getUnitsOfWork e.g. getScheduledEntityDeletions but for none of them I can get into where the var_dump occurs.
How would I go about getting the flushed entity within this postFlush function?
According to Doctrine2 documentation here : http://doctrine-orm.readthedocs.org/en/latest/reference/events.html you can't call the flushed entities on PostFlush event.
However you can split your logic : use the OnFlush event to get these entities and then pass it to the PostFlush if the flush succeeded.
To get just persisted to database entity you need not postFlush but postPersist:
public function postPersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
}
And don't forget to add next tag to your service:
{ name: "doctrine.event_listener", event: "postPersist" }
Related
I've create the event listener
class ProcessPostLoadListener
{
public function postLoad(LifecycleEventArgs $args)
{
$em = $args->getEntityManager();
$entity = $args->getEntity();
if ($entity instanceof Process) {
.
.
.
}
}
}
When I fetched data with getRepository(Process::class)->find($id), the method postLoad is called, and I can modify data.
But when the data are fetched with the query builder
$qb = $this->createQueryBuilder('p')
->select('PARTIAL p.{id,answersInRelatedQuestionnaires}')
->where('p.id = :processId')
->setParameter('processId', $processId);
postLoad is not called. What can I do? Is there an another event to listen?
Thanks
Quoting from Doctrine documentation:
PostLoad event occurs for an entity after the entity has been loaded into the current EntityManager from the database or after the refresh operation has been applied to it
That means that PostLoad event is only dispatched when an entity object is hydrated, and that is true only when hydration mode is set to HYDRATE_OBJECT. Since you are using HYDRATION_ARRAY, an associative array is being returned and no entity object is being hydrated, that is why PostLoad event doesn't occur.
I'm trying to link a coffee profile to a user after the user is in the database.
This coffee profile data is in a session and I'm using this session in the postFlush.
However This code is creating an infinite loop and I don't know why:
UserListener.php:
<?php
namespace AppBundle\EventListener;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use AppBundle\Entity\Consumption;
use AppBundle\Entity\CoffeeOption;
use AppBundle\Entity\Consumeable;
use AppBundle\Entity\MomentPreference;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\ORM\Event\PostFlushEventArgs;
class UserListener
{
private $container;
private $user;
public function __construct(ContainerInterface $container = null)
{
$this->container = $container;
}
public function postPersist(LifecycleEventArgs $args)
{
$user = $args->getEntity();
$this->user = $user;
}
public function postFlush(PostFlushEventArgs $args)
{
$session = new Session();
if($session) {
$em = $args->getEntityManager();
$us = $em->getRepository('AppBundle:User')->findOneById($this->user->getId());
$consumption = $session->get('consumption');
$coffee = $session->get('coffee');
$moment = $session->get('moment');
$consumption->setUser($us);
//dummy data for the day, later this needs to be turned into datetime
$moment->setDay('monday');
$moment->setConsumption($consumption);
$em->persist($consumption);
$em->persist($coffee);
$em->persist($moment);
$em->flush();
} else {
return $this->redirectToRoute('fos_user_registration_register');
}
}
}
Services.yml:
zpadmin.listener.user:
class: AppBundle\EventListener\UserListener
arguments: ['#service_container']
tags:
- { name: doctrine.event_listener, event: postPersist }
- { name: doctrine.event_listener, event: postFlush }
What is causing this loop and how can I fix it?
In your postFlush event, you're flushing again. That's what causing the infinite loop because the postFlush event is triggered every time you're calling the flush method.
I'm not sure what you're trying to achieve but your goal is to create a coffee consumption everytime you're saving a user, you can add a test like this one at the start of your method:
$entity = $args->getObject();
if (!$entity instanceof User) {
return;
}
This will prevent the infinite loop.
And a few more things:
Your postPersist method seems useless. It'll be called every time an object is persisted so your $this->user propety will not necessarily be a User object.
If you need the user, you don't have to fetch it in your database. Simply use $args->getObject() to get the flushed entity. In addition with the test above, you'll be sure the method will return you a User object.
This is not a very good practice to check if the user is logged in in your Doctrine listener. This is not what the class is supposed to do.
Don't inject the container in your constructor. Inject only what you need (in this case... nothing ?)
You are calling $em->flush() inside your postPersist event, in the docs it is stated that:
postFlush is called at the end of EntityManager#flush().
EntityManager#flush() can NOT be called safely inside its listeners.
You should use other events like prePersist or postPersist.
If possible try to avoid multiple flush() on a single request.
by the way, there is no need to do this, because your user object is already contained inside your $user variable.
$us =
$em->getRepository('AppBundle:User')->findOneById($this->user->getId());
Event Listener example below works fine for prePersist() and postPersist() however browser times out for preUpdate() and postUpdate(). Anyone knows why this is happening?
Note: Event listener is the one which cause problem because controller works fine when used on its own. I checked the database.
Error:
Maximum execution time of 30 seconds exceeded in /var/www/html/local/listener/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php line 685
Event listener
class UserListener
{
//public function postUpdate(LifecycleEventArgs $args)
public function preUpdate(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof User) {
$userLog = new UserLog();
$userLog->setDescription('User Update.');
$em = $args->getEntityManager();
$em->persist($userLog);
$em->flush();
}
}
}
Controller:
public function updateUser()
{
$repo = $this->getDoctrine()->getRepository('SiteFrontBundle:User');
$user = $repo->findOneBy(array('id' => 1));
$user->setLock(true);
$em = $this->getDoctrine()->getManager();
$em->flush();
}
Service.yml
services:
entity.event_listener.user:
class: Site\FrontBundle\EventListener\Entity\UserListener
tags:
- { name: doctrine.event_listener, event: preUpdate }
preUpdate is very limited.
From: http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#preupdate
Any calls to EntityManager#persist() or EntityManager#remove(),
even in combination with the UnitOfWork API are strongly discouraged and
don’t work as expected outside the flush operation.
Probably need to use onFlush, http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#onflush
Might also want to take look at how these guys do it: https://github.com/Atlantic18/DoctrineExtensions/tree/master/lib/Gedmo/Loggable
Or just setup a logging entity manager.
I have an preUpdate Eventlistener on an Entitychild in my embedded Form.
I can change the attribute related to my entity:
public function preUpdate(LifecycleEventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
$em = $eventArgs->getEntityManager();
if ($entity instanceof AOSupplierReference) {
if ($eventArgs->hasChangedField('amount')) {
$entity->setConfirmed(false);
}
}
}
But now I have to change an attribute of the parent Entity, this don't work in my preUpdate event:
$entity->getPurchaseOrder()->setStatus(4);
only the $entity->setConfirmed(false) changes.
You can't update a related entity in a preUpdate listener:
PreUpdate is the most restrictive to use event, since it is called
right before an update statement is called for an entity inside the
EntityManager#flush() method.
Changes to associations of the updated entity are never allowed in
this event, since Doctrine cannot guarantee to correctly handle
referential integrity at this point of the flush operation.
See the documentation.
How can I achieve this:
For example, I have an entity called Issue. I need to log changes of a field of this entity.
If a user changes the field "status" on the Issue entity I need to create a database record about it with the user, who changed the field, the previous status and the new status.
Using: Symfony2 + doctrine2.
You can use an event subscriber for that, and attach it to the ORM event listener (in symfony 2, there's docs about that):
namespace YourApp\Subscriber;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Events;
use YourApp\Entity\Issue;
use YourApp\Entity\IssueLog;
class IssueUpdateSubscriber implements EventSubscriber
{
public function onFlush(OnFlushEventArgs $args)
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityUpdates() as $updated) {
if ($updated instanceof Issue) {
$em->persist(new IssueLog($updated));
}
}
$uow->computeChangeSets();
}
public function getSubscribedEvents()
{
return array(Events::onFlush);
}
}
You can eventually check the changeset as I've explained at Is there a built-in way to get all of the changed/updated fields in a Doctrine 2 entity.
I left the implementation of IssueLog out of the example, since that is up to your own requirements.