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.
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());
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" }
Consider the following situation. I have an entity that holds some information, let's say a news item. This news item contains comments.
In the news item entity, there is calculateStatistics() function, that returns some statistics derived from the news item entity, plus its comments. I used to have this calculate function inside a NewsService, but then found out a service wasn't needed because I only use information that is inside the entity.
Nowadays, the calculate function also does some sanity checking. I want to log negative results as a warning in my Monolog service. I still believe at this point it is valid to have this calculate function inside the entity, since no external information/service is needed. Is there an elegant way to support logging inside an entity?
I don't think that handling logging inside Entity is a good idea, as entity should be as independent as possible and have no business logic inside. I would suggest doing it by event listener. Consider such configuration (I assume you're using Doctrine and want to perform logging while some doctrine event - but if not, you will only have to modify name of event which you listen to):
Entity:
class YourEntity implements StatisticInterface
{
(...)
public function calculateStatistics()
{
(...)
}
}
config.yml
your_service.statistics_listener:
class: Acme\DemoBundle\EventListener\Entity\StatisticsEntityListener
arguments: [#logger]
tags:
- { name: doctrine.event_listener, event: prePersist }
prePersist is one of many possible events, just pick one that fits most
StatisticsEntityListener
class StatisticsEntityListener
{
public function __construct(Logger $logger)
{
$this->logger = $logger;
}
/**
* #param LifecycleEventArgs $args
*/
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof StatisticInterface) {
//do whatever you like with your logger and entity
$logger->log($entity->calculateStatistics());
}
}
}
This way you get nice separation of concerns and you're able to log info using your Monolog
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.