Symfony2 Event Listener preUpdate - php

I have configured in my service.yml some event listener
registerproduct.listener:
class: App\AppBundle\Listener\RegisterProductListener
arguments: [ #service_container ]
tags:
- { name: doctrine.event_listener, event: prePersist }
- { name: doctrine.event_listener, event: preUpdate }
My method decrementProductQuantity() works really well when I do a prePersist but not with the preUpdate.
During the update the product quantity decrement well, but there is no changes in the database.
Here is my code for the events:
public function prePersist(LifecycleEventArgs $args)
{
$this->decrementProductQuantity($args);
}
public function preUpdate(LifecycleEventArgs $args)
{
$this->decrementProductQuantity($args);
}
public function decrementProductQuantity(LifecycleEventArgs $args)
{
/** #var RegisterProduct $registerProduct */
$registerProduct = $args->getEntity();
if (!($registerProduct instanceof RegisterProduct))
return;
if ($registerProduct->getStatus() == 'inscription') {
$product = $registerProduct->getProduct();
$product->setQuantityStock($product->getQuantityStock() - $registerProduct->getQuantity());
}
}
Here is my update code in my controler:
/**
* #param Register $register
* #return \Symfony\Component\HttpFoundation\Response
*/
public function confirmAction(Request $request, Register $register)
{
$em = $this->getDoctrine()->getManager();
foreach($register->getRegisterProducts() as $registerProduct){
$registerProduct->setStatus('inscription');
$em->persist($registerProduct);
$em->flush();
}
return $this->redirectWithMode($this->generateUrl('asc_activity_register_show', array('register' => $register->getId())));
}
I have no idea why it works when I add something in the database, but not for the update.
Thanks for your help.

On your preUpdate listener you should to recompute changeset for product entity
public function decrementProductQuantity(LifecycleEventArgs $args)
{
/** #var RegisterProduct $registerProduct */
$registerProduct = $args->getEntity();
if (!($registerProduct instanceof RegisterProduct))
return;
if ($registerProduct->getStatus() == 'inscription') {
$product = $registerProduct->getProduct();
$product->setQuantityStock($product->getQuantityStock() - $registerProduct->getQuantity());
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
$className = \Doctrine\Common\Util\ClassUtils::getClass($product);
$classMetadata = $em->getClassMetadata($className);
$uow->recomputeSingleEntityChangeSet($classMetadata, $product);
}
}
It happens because in your controller (or somewhere else) your code does not update a product entity. Before listener executing doctrine computes change sets for each entity. Hence in your preUpdate listener doctrine does not know anything about product (because it was not changed).

Try to change the signature of your preUpdate method:
use Doctrine\ORM\Event\PreUpdateEventArgs;
/.../
public function preUpdate(PreUpdateEventArgs $args)
{
$this->decrementProductQuantity($args);
}
/.../

Because I didn't find the awnser I finally choose to do it directly in the controller:
/**
* #param Register $register
* #return \Symfony\Component\HttpFoundation\Response
*/
public function confirmAction(Request $request, Register $register)
{
$mode = $this->getMode($request);
$em = $this->getDoctrine()->getManager();
foreach ($register->getRegisterProducts() as $registerProduct) {
if($registerProduct->getStatus() != 'inscription'){
$product = $registerProduct->getProduct();
$product->setQuantityStock($product->getQuantityStock() - $registerProduct->getQuantity());
}
$registerProduct->setStatus('inscription');
}
$em->flush();
return $this->redirectWithMode($this->generateUrl('asc_activity_register_show', array('register' => $register->getId())), $mode);
}
If someone find the anwser, please tell me.

Related

Symfony 5 PreUpdate change value for only one true

Hello and thank you for your concern.
i'am student and it's my first time creating an eventSubriber.
I would like to change all my attribute "display" (Boolean) in my Entity "Menu" when i try to update one of my entity "Menu" for have only one Attribute "display" true in all my entities.
I use EasyAdmin 3 in my project Symfony if you need to know.
My probleme start with $this->entityManager->flush(); in UniqueBoolTureEvent.php i think.
thank you in advance.
Service.yaml
App\EventListener\UniqueBoolTrueEvent:
tags:
- { name: doctrine.event_listener, event: preUpdate, Lazy: true }
UniqueBoolTrueEvent.php
<?php
namespace App\EventListener;
use App\Entity\Menu;
use Doctrine\Common\EventSubscriber;
// Entity to listen
use Doctrine\Persistence\ObjectManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
class UniqueBoolTrueEvent implements EventSubscriber {
private $entityManager;
/**
* #param EntityManagerInterface $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function getSubscribedEvents() {
return array('preUpdate');//Event to listen
}
public function preUpdate( PreUpdateEventArgs $eventArgs ) {
if ($eventArgs->getEntity() instanceof Menu) {
if ($eventArgs->hasChangedField('display') && $eventArgs->getNewValue('display') == 'true') {
//get the id of entity change for true
$entityId = $eventArgs->getEntity()->getId();
// search the entity already on true exept entity change now
$displayTrue = $this->entityManager->getRepository(Menu::class)->findByDisplay($entityId);
//Change the value for entity already on true exept entity change now
foreach ($displayTrue as $display) {
$display->setDisplay(false);
$this->entityManager->persist($display);
}
$this->entityManager->flush();
}
}
}
}
MenuRepository.php
<?php
namespace App\Repository;
use App\Entity\Menu;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* #method Menu|null find($id, $lockMode = null, $lockVersion = null)
* #method Menu|null findOneBy(array $criteria, array $orderBy = null)
* #method Menu[] findAll()
* #method Menu[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class MenuRepository extends ServiceEntityRepository
{
/**
* #param ManagerRegistry $registry
*/
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Menu::class);
}
/**
* #return Menu[] Returns an array of Menu objects
*/
public function findByDisplay($entityId)
{
return $this->createQueryBuilder('m')
->andWhere('m.display = true')
->andWhere('m.id != :entityId')
->setParameter('entityId', $entityId)
->orderBy('m.id', 'ASC')
->getQuery()
->getResult()
;
}
}
after that i have only one error in my ajax request and cannot see where is the probleme.
Thank you in advance.
My Solution No 2 Solutions lol
First solution with EventSubscriberInterface and BeforeEntityUpdatedEvent. With this solution you don't need to add config in service.yaml
class UniqueBoolTrueEvent implements EventSubscriberInterface
{
private $entityManager;
/**
* #param EntityManagerInterface $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public static function getSubscribedEvents()
{
return [
BeforeEntityUpdatedEvent::class => ['setDisplayTrue'],
];
}
public function setDisplayTrue(BeforeEntityUpdatedEvent $event)
{
$entity = $event->getEntityInstance();
$entityId = $entity->getId();
if ($entity instanceof Menu) {
$displayTrue = $this->entityManager->getRepository(Menu::class)->findByDisplay($entityId);
foreach ($displayTrue as $display) {
$display->setDisplay(false);
$this->entityManager->persist($display);
}
$this->entityManager->flush();
}
}
}
And my second solution with onFlush()
service.yaml
App\EventListener\UniqueBoolTrueEvent:
tags:
- { name: doctrine.event_listener, event: onFlush}
UniqueBoolTrueEvent.php
class UniqueBoolTrueEvent
{
private $entityManager;
/**
* #param EntityManagerInterface $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function onFlush(OnFlushEventArgs $eventArgs)
{
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
$entities = $uow->getScheduledEntityUpdates();
foreach ($entities as $entity) {
//continue only if the object to be updated is a Ticket
if ($entity instanceof Menu) {
$entityId = $entity->getId();
$entityDisplay = $entity->getDisplay();
// //get all the changed properties of the Menu object
// $changes_set = $uow->getEntityChangeSet($entity);
//Check if the changed value of "display" is True
if ($entityDisplay == true) {
//find the entity with "display" value true exept the entity update
$displayTrue = $this->entityManager->getRepository(Menu::class)->findByDisplay($entityId);
// Change the value of "display" to false for each entity on true exept the entity update
foreach ($displayTrue as $display) {
$display->setDisplay(false);
$em->persist($display);
// for flush
$classMetadata = $em->getClassMetadata(get_class($entity));
// dd($classMetadata);
$uow->computeChangeSet($classMetadata, $display);
}
}
}
}
}
}

CollectionType add action for database flush

I've created CollectionType form, which can add unlimited amount of TicketTime entities into one Ticket entity. It is connected via database by a OneToMany relation.
What I am trying to do is to add one Ticket and multiple TicketTimes to the database at one time. For example, one Ticket and 3 TicketTimes.
I would like to know how to make this method, so I can take one Ticket & multiple TicketTimes and flush them to the database.
My Controller action:
public function createTicket(Request $request)
{
if ($this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
if ($this->getUser()->getrole() == 3) {
$em = $this->getDoctrine()->getManager();
$ticket = new Ticket();
$timeArray = new ArrayCollection();
$form = $this->createForm(TicketType::class, $ticket);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
foreach ($timeArray as $time) {
$time->setTicket($ticket);
$em->persist($time);
}
$ticket->setTaken(0);
$em->persist($ticket);
$em->flush();
return $this->redirectToRoute('main');
}
return $this->render(
'create_ticket.html.twig',
array('form' => $form->createView())
);
}
}
return $this->redirectToRoute('main');
}
Ticket entity:
/**
* #Assert\NotBlank()
* #ORM\OneToMany(targetEntity="TicketTime", mappedBy="ticket", fetch="EXTRA_LAZY", orphanRemoval=true, cascade={"persist"})
*/
private $ticketTimes;
public function setTicketTimes($time): void
{
$this->ticketTimes = $time;
}
/**
* #return ArrayCollection|TicketTime[]
*/
public function getTicketTimes()
{
return $this->ticketTimes;
}
TicketTime entity:
/**
* #ORM\ManyToOne(targetEntity="Ticket", inversedBy="time")
* #ORM\JoinColumn(nullable=false)
*/
private $ticket;
public function getTicket()
{
return $this->ticket;
}
public function setTicket($ticket): void
{
$this->ticket = $ticket;
}
I managed to fix it by myself :)
What was wrong:
I needed to create add method in Ticket entity, so I will be able to add as many TicketTime's as I want, here's the code which I added into Ticket entity:
public function addTicketTime(TicketTime $time)
{
$this->ticketTimes[] = $time;
$time->setTicket($this);
return $this;
}
I've also added some lines into Controller
........
if ($form->isSubmitted() && $form->isValid()) {
$ticketTimes = $form->get('ticketTimes')->getData();
foreach ($ticketTimes as $time) {
$ticket->addTicketTime($time);
}
.......

Symfony onFlush Doctrine Listener

Hi I have an onFlush listener:
<?php
namespace FM\AppBundle\EventListener;
use FM\AdminBundle\Entity\Address\DeliveryAddress;
use Doctrine\ORM\Event\OnFlushEventArgs;
class DeliveryAddressListener
{
/**
* #param OnFlushEventArgs $args
*/
public function onFlush(OnFlushEventArgs $args)
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityUpdates() as $entity) {
if ($entity instanceof DeliveryAddress) {
$this->addPostalToUser($entity, $args);
}
}
}
/**
* #param DeliveryAddress $deliveryAddress
* #param OnFlushEventArgs $args
*/
public function addPostalToUser(DeliveryAddress $deliveryAddress, OnFlushEventArgs $args)
{
$em = $args->getEntityManager();
$user = $deliveryAddress->getOwner();
$user->setPostalCode($deliveryAddress->getZipCode());
}
}
service.yml:
delivery_address.listener:
class: FM\AppBundle\EventListener\DeliveryAddressListener
tags:
- { name: doctrine.event_listener, event: onFlush }
I'm trying to set the new zipCode to the User. But it does not seem to work.
Even when I'm adding a $em->persist($user).
I'm looking throught this doc: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#onflush
But I don't understand how I can make it works with this explanation:
If you create and persist a new entity in onFlush, then calling EntityManager#persist() is not enough. You have to execute an additional call to $unitOfWork->computeChangeSet($classMetadata, $entity).
When manipulating fields, they should be done in the preUpdaet/prePersist.
AppBundle/EventSubscriber/EntitySubscriber.php
namespace AppBundle\EventSubscriber;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\OnFlushEventArgs;
class EntitySubscriber implements EventSubscriber
{
private $now;
public function __construct()
{
$this->now = \DateTime::createFromFormat('Y-m-d h:i:s', date('Y-m-d h:i:s'));
}
public function getSubscribedEvents()
{
return [
'prePersist',
'preUpdate'
];
}
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
if (method_exists($entity, 'setCreatedAt')) {
$entity->setUpdatedAt($this->now);
}
if (method_exists($entity, 'setUpdatedAt')) {
$entity->setUpdatedAt($this->now);
}
}
public function preUpdate(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
if (method_exists($entity, 'setUpdatedAt')) {
$entity->setUpdatedAt($this->now);
}
}
}
services.yml
app.entity_subscriber:
class: AppBundle\EventSubscriber\EntitySubscriber
tags:
- { name: doctrine.event_subscriber, connection: default }
If you need to create an object, persist it and flush it in your listener, then tlorens' answer won't work, as Doctrine docs mention that this must be done with an onFlush Event.
Initial question was how to make it work following docs advice:
If you create and persist a new entity in onFlush, then calling EntityManager#persist() is not enough. You have to execute an additional call to $unitOfWork->computeChangeSet($classMetadata, $entity).
And this is a way to achieve this:
/**
* #param OnFlushEventArgs $eventArgs
*/
public function onFlush(OnFlushEventArgs $eventArgs)
{
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityUpdates() as $entity) {
if ($entity instanceof User) {
$uow->computeChangeSets();
$changeSet = $uow->getEntityChangeSet($entity);
// In this exemple, User has a boolean property 'enabled' and a log will be created if it is passed to 'false'
if ($changeSet && isset($changeSet['enabled']) && $changeSet['enabled'][1] === false) {
$log = new Log();
$em->persist($log);
$uow->computeChangeSet($em->getClassMetadata(get_class($log)), $log);
}
}
}
Well it works when I use that:
// Remove event, if we call $this->em->flush() now there is no infinite recursion loop!
$eventManager->removeEventListener('onFlush', $this);
My Listener
namespace FM\AppBundle\EventListener;
use FM\AdminBundle\Entity\Address\DeliveryAddress;
use Doctrine\ORM\Event\OnFlushEventArgs;
class DeliveryAddressListener
{
/**
* #param OnFlushEventArgs $args
*/
public function onFlush(OnFlushEventArgs $args)
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
$eventManager = $em->getEventManager();
// Remove event, if we call $this->em->flush() now there is no infinite recursion loop!
$eventManager->removeEventListener('onFlush', $this);
foreach ($uow->getScheduledEntityUpdates() as $entity) {
if ($entity instanceof DeliveryAddress) {
$this->addPostalToUser($entity, $args);
}
}
}
/**
* #param DeliveryAddress $deliveryAddress
* #param OnFlushEventArgs $args
*/
public function addPostalToUser(DeliveryAddress $deliveryAddress, OnFlushEventArgs $args)
{
$em = $args->getEntityManager();
$user = $deliveryAddress->getOwner();
$user->setPostalCode($deliveryAddress->getZipCode());
$em->flush();
}
}

Symfony Event Listener

Hi I'm tring to do a Symfony event listener following this documentation:
http://symfony.com/doc/2.8/cookbook/doctrine/event_listeners_subscribers.html
<?php
namespace FM\AppBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use FM\AdminBundle\Entity\Address\BillingAddress;
class BillingAdressListener
{
/**
* #param LifecycleEventArgs $args
*/
public function listenBillingAdress(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if(!$entity instanceof BillingAddress){
return;
}
$this->postPersist($args);
}
/**
* #param LifecycleEventArgs $args
*/
public function postPersist(LifecycleEventArgs $args)
{
$em = $args->getEntityManager();
$billingAdress = $args->getEntity();
dump($billingAdress); die();
}
}
service.yml
billing_adress.listener:
class: FM\AppBundle\EventListener\BillingAdressListener
tags:
- { name: doctrine.event_listener, event: listenBillingAdress }
But nothing is happening when I'm submitting a form with the BillingAddress object.
Did I do something wrong?
It's called Entity Listeners in Doctrine. Here is documentation.
Don't forget to add your entity listener in mapping. For example xml mapping:
<entity name="AppBundle\Entity\AssignmentAttempt" table="app_assignment_attempt" >
<entity-listeners>
<entity-listener class="AppBundle\EntityListener\AssignmentAttemptListener">
<lifecycle-callback type="prePersist" method="prePersist"/>
<lifecycle-callback type="preUpdate" method="preUpdate"/>
</entity-listener>
</entity-listeners>
<!-- Rest mapping -->
</entity>
In config you can simply add
app.entity_listener.assignment_attempt:
class: AppBundle\EntityListener\AssignmentAttemptListener
tags:
- { name: doctrine.orm.entity_listener }
My bad, I was not doing a Persist but an Update.
It works with an Persist.
Here is my code now:
<?php
namespace FM\AppBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use FM\AdminBundle\Entity\Address\BillingAddress;
class BillingAddressListener
{
/**
* #param LifecycleEventArgs $args
*/
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if(!$entity instanceof BillingAddress){
return;
}
$this->addNameToUser($args);
}
/**
* #param LifecycleEventArgs $args
*/
public function addNameToUser(LifecycleEventArgs $args)
{
/** #var BillingAddress $billingAdress */
$billingAdress = $args->getEntity();
$user = $billingAdress->getOwner();
if(empty($user->getFirstName())) $user->setFirstName($billingAdress->getFirstName());
if(empty($user->getLastName())) $user->setLastName($billingAdress->getLastName());
}
}
And I have to call the prePersist method.
I was doing the contrary.
billing_address.listener:
class: FM\AppBundle\EventListener\BillingAddressListener
tags:
- { name: doctrine.event_listener, event: prePersist }

Symfony 2 Doctrine MongoDB entity listener

I'm trying to get Entity Listeners work with ODM in Symfony 2.7 but to no avail.
a51.document.listener.store:
class: A51\FilesystemBundle\EventListener\StoreEntityListener
tags:
- { name: doctrine.odm.mongodb.document_manager, event: postLoad, method: onPostLoad }
arguments: [#a51.repo.file]
and:
<?php
namespace A51\FilesystemBundle\EventListener;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use A51\FilesystemBundle\Document\Store;
use A51\FilesystemBundle\Repository\FileRepositoryInterface;
class StoreEntityListener
{
/**
* #var FileRepositoryInterface
*/
private $fileRepository;
public function __construct(FileRepositoryInterface $fileRepository)
{
$this->fileRepository = $fileRepository;
}
public function onPostLoad(LifecycleEventArgs $args)
{
$this->index($args);
}
public function index(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
if ($entity instanceof Store)
{
$entity->setTotalByteSize($this->fileRepository->findSumFilesSizeByStore($entity));
}
}
}
I've tried pretty much everything I could find in docs but for some reason onPostLoad method does not get called.
Store document gets loaded with ParamConverter:
* #ParamConverter("store", class="A51FilesystemBundle:Store")
Any help would be welcome.
I have a MongoDB listener in my project, but my code it's so different of yours. There's a simpler way, all you must to do it's import DocumentManager and then you can call it from construct to use it on all you listener. I'm gonna show you my code and tell me if this help you ;)
namespace AppBundle\OdmListener;
use Doctrine\ORM\Event\PostPersistEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ODM\MongoDB\DocumentManager;
class RedundancyListener
{
/**
* #var DocumentManager
*/
private $dm;
/**
* #param DocumentManager $odm
*/
function __construct(DocumentManager $dm)
{
$this->dm = $dm;
}
Then inside you can do any queries or updates as you do it in you controller. Also you can use ORM or ODM CycleEvents if you import them, like I do in the example.
/**
* #param LifecycleEventArgs $eventArgs
*/
public function preUpdate(LifecycleEventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
if ($entity instanceof \AppBundle\Entity\Example) {
$subscriptionHash = $this->getSubscription($entity);
$this->dm->createQueryBuilder('AppBundle\Document\Example')
->update()
//->multiple(true)
->field('name')->set($entity->getName())
->field('last_name')->set($entity->getLastName())
->field('mail')->set($entity->getMail())
->getQuery()
->execute();
}
}
}

Categories