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 }
Related
api-platform.com's event won't attach to my listener. I tried several combination from their event matrix but it still won't trigger.
# services.yml
user_access_listener:
class: AppBundle\Event\Listener\UserAccessListener
arguments: [ "#security.authorization_checker" ]
tags:
- { name: kernel.event_listener, event: kernel.view, method: onKernelView }
Here is my listener class
namespace AppBundle\Event\Listener;
use UserBundle\Entity\User;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class UserAccessListener
{
/**
* #var AuthorizationCheckerInterface
*/
private $authorizationChecker;
/**
* #param AuthorizationCheckerInterface $authorizationChecker
*/
public function __construct(AuthorizationCheckerInterface $authorizationChecker)
{
$this->authorizationChecker = $authorizationChecker;
}
/**
* #param GetResponseForControllerResultEvent $event
*/
public function onKernelView(GetResponseForControllerResultEvent $event)
{
echo "This should trigger";
exit;
$user = $event->getControllerResult();
if (!$user instanceof User) {
return;
}
if (!$this->authorizationChecker->isGranted(null, $user)) {
throw new AccessDeniedException();
}
}
}
api-platform event reference
I was expecting "This should trigger" would appear when I hit GET /projects/1 and GET /projects, but it is not triggering. Thoughts?
Your should use a higher priority (70 for instance), to be sure that your listener is executed before the builtin view listeners.
Example:
user_access_listener:
class: AppBundle\Event\Listener\UserAccessListener
arguments: [ "#security.authorization_checker" ]
tags:
- { name: kernel.event_listener, event: kernel.view, method: onKernelView, priority: 70 }
I'm studying Symfony and I'm trying with Dispatch Event Component.
I Understand that there are
Dispatcher (component)
Event ( that pass information to Listener )
Listener ( the logic )
I created a simple example in my route
/**
* #Route("/evento", name="evento")
*/
public function eventoAction()
{
$dispatcher = new EventDispatcher();
$listener = new StoreListener($this->get('mailer'));
$dispatcher->addListener(StoreEvents::STORE_ORDER,array($listener,'onFooAction'),25);
$order = new Ordini();
$order->setName('ordine nuovo -'.rand(0,100));
$em = $this->getDoctrine()->getManager();
$em->persist($order);
$em->flush();
$event = new FilterOrderEvent($order);
$dispatcher->dispatch(StoreEvents::STORE_ORDER,$event);
return $this->render('::event.html.twig');
}
StoreEvent with Contain the Const String
namespace AppBundle\Events;
final class StoreEvents
{
/**
* The store.order event is thrown each time an order is created
* in the system.
*
* The event listener receives an
* Acme\StoreBundle\Event\FilterOrderEvent instance.
*
* #var string
*/
const STORE_ORDER = 'store.order';
}
The Event that I pass to Listener
<?php
namespace AppBundle\Events;
use AppBundle\Entity\Ordini;
use Symfony\Component\EventDispatcher\Event;
class FilterOrderEvent extends Event
{
protected $order;
public function __construct(Ordini $order)
{
$this->order = $order;
}
/**
* #return Ordini
*/
public function getOrder()
{
return $this->order;
}
}
And The Listener
<?php
namespace AppBundle\Events;
use Symfony\Component\EventDispatcher\Event;
class StoreListener
{
protected $mail;
/**
* StoreListener constructor.
* #param $mail
*/
public function __construct($mail)
{
$this->mail = $mail;
}
public function onFooAction(Event $event)
{
var_dump($event);
var_dump($event->getOrder());
echo "fooAction";
$message = \Swift_Message::newInstance()
->setSubject($event->getOrder()->getName())
->setFrom('send#example.com')
->setTo('send#example2.com)
->setBody('Test')
;
$this->mail->send($message);
}
}
all these stuff Work but I want to Understand where I have to declare the dispatcher.
I have to declare for each controller ?
$dispatcher = new EventDispatcher();
even the add listener ( or addSubscriber)
$dispatcher->addListener(StoreEvents::STORE_ORDER,array($listener,'onFooAction'),25);
I don't think this is the best practice to Declare every Time the new Dispatcher
or new addListener
UPDATE
I created another Action
/**
* #Route("/evento2", name="evento2")
*/
public function evento2Action()
{
$order = new Ordini();
$order->setName('ordine nuovo -'.rand(0,100));
$em = $this->getDoctrine()->getManager();
$em->persist($order);
$em->flush();
$event = new FilterOrderEvent($order);
$this->get('event_dispatcher')->dispatch(StoreEvents::STORE_ORDER,$event);
return $this->render('::event.html.twig');
}
And create my service:
services:
create_order_listener:
class: AppBundle\Events\StoreListener
arguments: ["#swiftmailer.mailer"]
tags:
- { name: kernel.event_listener, event: store.order, method: onFooAction
all work. But now I'd like to know the best practice or when Create listener by service and when I have to use addListener or addSubscriber
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.
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();
}
}
}
I'm trying to inject my repository service into EventListener but that leads me to following exception, which, with my basic knowledge of Symfony2, I have no idea how to resolve. Exception is:
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 -> person.connect.listener -> tag.repository.service".
And here is how I've declared repository and listener:
tag.repository.service:
class: Application\Bundle\PersonBundle\Entity\TagRepository
factory: ["#doctrine", getRepository]
arguments: [ Application\Bundle\PersonBundle\Entity\Tag ]
person.connect.listener:
class: Application\Bundle\PersonBundle\EventListener\ConnectListener
arguments:
tokenStorage: "#security.token_storage"
tagRepo: "#tag.repository.service"
tags:
- { name: doctrine.event_listener, event: postPersist, connection: default }
Most answers, that I've able to find, suggest injecting service container, but I really don't want do that. Is there any way to resolve this properly?
UPD: Here is the code of the listener. Everything worked fine until I've tried to inject TagRepository
class ConnectListener
{
/**
* #var TokenStorage
*/
private $tokenStorage;
/**
* #var TagRepository
*/
private $tagRepo;
/**
* #param TokenStorage $tokenStorage
* #param TagRepository $tagRepo
*/
public function __construct(TokenStorage $tokenStorage, TagRepository $tagRepo)
{
$this->tokenStorage = $tokenStorage;
}
/**
* #param LifecycleEventArgs $args
* #return void
*/
public function postPersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
if ($entity instanceof Person) {
$user = $this->tokenStorage->getToken()->getUser();
$visibility = new PersonVisibility($entity, $user);
$visibility->setVisibilityType(PersonVisibility::VT_CREATED);
$entityManager->persist($visibility);
$entityManager->flush();
}
}
}
As far as TagRepository is descendant of EntityRepository try obtaining its instance in postPersist event. Like this:
// using full classname:
$tagRepo = $entityManager->getRepository("Application\Bundle\PersonBundle\Entity\TagRepository");
// alternatively:
$tagRepo = $entityManager->getRepository("ApplicationPersonBundle:Tag");
Yo can also change your declaration of your repository, don't use the factory and use one of these 2 methods.
This will avoid the circular reference and will be cleaner than use the EntityManager class.