Symfony EventSubscribe on Entity - php

trying to make an subscriber for Entity actions (CRUD) and cannot figure it out.
I know there is a way, where I can make listener and send him 3 different events, but that's not what I want to reach, I dont even think is good solution.
Event Subscriber
<?php
namespace App\EventListener;
use App\Entity\Log;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Events;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
/**
* Part of program created by David Jungman
* #author David Jungman <davidjungman.web#gmail.com>
*/
class EntitySubscriber implements EventSubscriberInterface
{
/**
* #var EntityManagerInterface
*/
private $em;
/**
* #var TokenStorageInterface
*/
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage, EntityManagerInterface $em)
{
$this->em = $em;
$this->tokenStorage = $tokenStorage;
}
public static function getSubscribedEvents()
{
return array(
Events::postPersist,
Events::postUpdate,
Events::postRemove,
);
}
public function postUpdate(LifecycleEventArgs $args)
{
$this->logEvent($args, "remove");
}
public function postRemove(LifecycleEventArgs $args)
{
$this->logEvent($args, "remove");
}
public function postPersist(LifecycleEventArgs $args)
{
$this->logEvent($args, "create");
}
private function logEvent(LifecycleEventArgs $args, string $method)
{
$entity = $args->getEntity();
if($entity->getShortName() != "Log")
{
$user = $this->tokenStorage->getToken()->getUser();
$log = new Log();
$log
->setUser($user)
->setAffectedTable($entity->getShortName())
->setAffectedItem($entity->getId())
->setAction($method)
->setCreatedAt();
$this->em->persist($log);
$this->em->flush();
}
}
}
and my Service.yaml part
App\EventListener\EntitySubscriber:
tags:
- { name: doctrine.event_subscriber, connection: default }
I have tried:
I've looked into these 2 official tutorials:
-https://symfony.com/doc/current/event_dispatcher.html
-https://symfony.com/doc/current/doctrine/event_listeners_subscribers.html
but neither helped.. when I use shown part of config, my computer freeze.
When I try to debug it, I can see these methods active
( php bin/console debug:event-dispatcher )
but they are listening on "event" event

Doctrine has it's own events handler/subscriber system. However, with the class Symfony\Component\EventDispatcher\EventSubscriberInterface; that you are implementing, that is from the Symfony event system.
<?php
use Doctrine\ORM\Events;
use Doctrine\Common\EventSubscriber; // **the Doctrine Event subscriber interface**
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
class MyEventSubscriber implements EventSubscriber
{
public function getSubscribedEvents()
{
return array(
Events::postUpdate,
);
}
public function postUpdate(LifecycleEventArgs $args)
{
$entity = $args->getObject();
$entityManager = $args->getObjectManager();
// perhaps you only want to act on some "Product" entity
if ($entity instanceof Product) {
// do something with the Product
}
}
}

Related

How to autowire an entity into a service?

I have an abstract class called AbstractMediaService and a some specific implementations of this abstract class:
abstract class AbstractMediaService
{
private $em;
private $media;
public function __construct(EntityManagerInterface $em, Media $media)
{
$this->em = $em;
$this->media = $media;
}
public function dosomethingInCommon();
abstract public function dosomethingSpecific();
}
class PhotoMediaService extends AbstractMediaService
{
public function dosomethingSpecific()
{
echo 'i am a photo service';
}
}
class VideoMediaService extends AbstractMedia
{
public function dosomethingSpecific()
{
echo 'i am a video service';
}
}
These objects require a Media entity to work with
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
class Media
{}
Controller
/**
* #Route("/{_locale}/infos/{idMedia}.html", name="info", methods={"GET"}, requirements={
* "idMedia" = "\d+",
* })
*/
public function infosPhotoAction(RequestStack $requestStack, Media $media)
{
$request = $requestStack->getCurrentRequest();
$session = $requestStack->getSession();
$media = new PhotoMedia($media);
// return response
}
Problem is that I need some dependencies like the Security service or the EntityManager.
I would like to know how autowire AbstractMediaService service.
This is wrong. You cannot autowire Media to be injected into a service, because entities are not services.
public function __construct(EntityManagerInterface $em, Media $media)
If VideoMediaService and PhotoMediaService (I renamed them for clarity, since sharing the name with your entity made it look like it were related) need an instance of Media to perform some work, just make that a parameter for the corresponding methods.
public function dosomethingInCommon(Media $media);
abstract public function dosomethingSpecific(Media $media);
Or alternatively, simply have a setMedia(Media $media) method on that class for that:
public function setMedia(Media $media) {
$this->media = $media;
}
Frankly, this latter approach does not seem like a great idea. You would need to make the methods that work on $media aware of the possibility of setMedia() not having been called yet, or subsequent calls to setMedia() would change how the service behaved. Just making it a parameter of the appropriate method is much cleaner, clearer and safer.
Injecting those services is done like any other service. That they extend an abstract class is irrelevant.
/**
* #Route("/{_locale}/infos/{idMedia}.html", name="info", methods= {"GET"}, requirements={
* "idMedia" = "\d+",
* })
*/
public function infosPhotoAction(RequestStack $requestStack, Media $media, PhotoMediaService $photoMediaService): Response
{
$request = $requestStack->getCurrentRequest();
$session = $requestStack->getSession();
$photoMediaService->doSomethingSpecific($media)
return new Response('all done');
}

Doctrine inserting on POST_WRITE event with Symfony 5 and APIplatform

I want to add a new UserTeam(which get a team, a user and a role) each time a User create a Team. I created an event subscriber TeamFirstUserAdminSubscriber.php but it doesn't work and I have no error message.
here is my database model:
and here is the file TeamFirstUserAdminSubscriber.php
<?php
namespace App\Events;
use ApiPlatform\Core\EventListener\EventPriorities;
use App\Entity\Team;
use App\Entity\UserTeam;
use App\Repository\RoleUserTeamRepository;
use App\Repository\TeamRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Security;
class TeamFirstUserAdminSubscriber implements EventSubscriberInterface
{
private $security;
private $repository;
private $repositoryT;
private $manager;
public function __construct(Security $security, RoleUserTeamRepository $repository, TeamRepository $repositoryT, EntityManagerInterface $manager)
{
$this->security = $security;
$this->repository = $repository;
$this->repositoryT = $repositoryT;
$this->manager = $manager;
}
public static function getSubscribedEvents()
{
return[
KernelEvents::VIEW => ['setInstanceUserTeam', EventPriorities::POST_WRITE],
];
}
public function setInstanceUserTeam(ViewEvent $event)
{
$team = $event->getControllerResult();
$method = $event->getRequest()->getMethod();
if($team instanceof Team && $method === 'POST')
{
//get the user connected
$user = $this->security->getUser();
//get the last team created
$lastTeam = $this->repositoryT->findLastTeamCreated();
//get the admin role (NOT the symfony one)
$admin = $this->repository->findOneByRoleAdmin('Admin');
//should create a new UserTeam instance with the User, the Team and the RoleUserTeam wanted
$userTeam = new UserTeam();
$userTeam->setUser($user);
$userTeam->setTeam($lastTeam);
$userTeam->setRoleUserTeam($admin);
$manager = $this->manager;
$manager->persist($userTeam);
$manager->flush();
}
}
The new UserTeam is not created in the databse when I try it out with postman, but the Team is well created.
I think I am missing something but I don't know what.
Could anyone help me ?
I can't see your full code(controller, forms etc), but you can achieve this without an event listener. As you already know, what team to be assigned for a user, why don't you assign that on the entity when creating the user object. For example if you using form in the controller
public function addAdminUser(Request $request, RoleUserTeamRepository $repository, TeamRepository $repositoryT, EntityManagerInterface $manager)
{
$user = new User();
$form = $this->createForm(UserForm::class, $user);
$form->handleRequest($form);
 
if ($form->isSubmitted && $form->isValid()) {
//get the last team created
$lastTeam = $this->repositoryT->findLastTeamCreated();
//get the admin role (NOT the symfony one)
$admin = $this->repository->findOneByRoleAdmin('Admin');

//should create a new UserTeam instance with the User, the Team and the
RoleUserTeam wanted
$userTeam = new UserTeam();
$userTeam->setUser($user);
$userTeam->setTeam($lastTeam);
$userTeam->setRoleUserTeam($admin);
$user->setUserTeam($userTeam);

//persist the user
$manager->persist($user);
$manager->flush();
}
}
If you want to use events I would suggest use Doctrine events. You can use prePersist event on user entity to achieve same result. Doctrine Events
I solved my problem, I needed a cascade persist in my entities. Here, if it can hepl some people :
I had this in the Team entity
public function __construct()
{
$this->id = Team::class;
$this->userTeams = new ArrayCollection();
$this->categories = new ArrayCollection();
}
public function userTeam()
{
$newUserTeam = new UserTeam();
$newUserTeam->setTeam($this);
$this->userTeams->add($newUserTeam);
}
UserTeam entity, I had to add cascade={"persist"} in the relation ManyToOne:
class UserTeam
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity=Team::class, inversedBy="userTeams", cascade={"persist"})
* #ORM\JoinColumn(nullable=false)
* #Groups({"users_read", "userTeam_read"})
*/
private $team;
And the TeamByUserSubscriber that I modified :
<?php
namespace App\Events;
use ApiPlatform\Core\EventListener\EventPriorities;
use App\Entity\Team;
use App\Entity\UserTeam;
use App\Repository\RoleUserTeamRepository;
use App\Repository\TeamRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Security;
class TeamByUserSubscriber implements EventSubscriberInterface
{
private $security;
private $repository;
private $entityManager;
public function __construct(Security $security, RoleUserTeamRepository $repository, EntityManagerInterface $entityManager)
{
$this->security = $security;
$this->repository = $repository;
$this->entityManager = $entityManager;
}
public static function getSubscribedEvents()
{
return[
KernelEvents::VIEW => ['setUserForTeam', EventPriorities::PRE_VALIDATE],
];
}
public function setUserForTeam(ViewEvent $event)
{
$team = $event->getControllerResult();
$method = $event->getRequest()->getMethod();
if($team instanceof Team && $method === 'POST')
{
//get the connected User
$user = $this->security->getUser();
//put the current User to the team we are creating (as the creator of the team)
$team->setUser($user);
//get the Admin role from the RoleUserTeam entity
$admin = $this->repository->findOneByRoleAdmin('Admin');
//create a new instance of UserTeam with the connected User, the Team we are creating and the role
$userTeam = new UserTeam();
$userTeam->setUser($user);
$userTeam->setTeam($team);
$userTeam->setRoleUserTeam($admin);
$manager = $this->entityManager;
$manager->persist($userTeam);
$manager->flush();
}
}

Symfony 4 PHP Exception Doctrine\ORM\ORMException: "Unknown Entity namespace ali as 'User'."

Here is my subscriber class. I want to get the user email to him an email.
I use here the EntityManagerInterface
use Doctrine\ORM\EntityManagerInterface;
final class RegisterMailSubscriber implements EventSubscriberInterface
{
private $mailer;
public function __construct(\Swift_Mailer $mailer, EntityManagerInterface $entityManager)
{
$this->mailer = $mailer;
$this->repository= $entityManager->getRepository('AppEntity:User');
}
public static function getSubscribedEvents()
{
return [
KernelEvents::VIEW => ['sendMail', EventPriorities::POST_WRITE],
];
}
public function sendMail(ViewEvent $event): void
{
$user = $event->getControllerResult();
$method = $event->getRequest()->getMethod();
if (!$user instanceof User || Request::METHOD_POST !== $method) {
return;
}
$userInfo = $this->repository->find($user->getId());
}
}
You need to import all the dependencies used as User, Request, ViewEvent, KernelEvent, etc.
By the way, is a good practice to import the repository (UserRepository) and not the entityManager, but you dont need it because you have the $user already. You dont need to find it again.
I think this should be enough if you have the user classes in those namespaces (locations):
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use ApiPlatform\Core\EventListener\EventPriorities;
use App\Entity\User;
final class RegisterMailSubscriber implements EventSubscriberInterface
{
private $mailer;
public function __construct(\Swift_Mailer $mailer)
{
$this->mailer = $mailer;
}
public static function getSubscribedEvents()
{
return [
KernelEvents::VIEW => ['sendMail', EventPriorities::POST_WRITE],
];
}
public function sendMail(ViewEvent $event): void
{
$user = $event->getControllerResult();
$method = $event->getRequest()->getMethod();
if (!$user instanceof User || Request::METHOD_POST !== $method) {
return;
}
$userEmail = $user->getEmail(); //for example. You got the user 5 lines before.
}
}

Prevent a listener from being activated depending on where it was called in Symfony

I have a Symfony project with a lot of bundles.
In one of them, I have a standard Doctrine listener like this:
class MyListener
{
public function postLoad(LifecycleEventArgs $args)
{
$entity = $args->getObject();
if ($entity instanceof MyEntity) {
//do something
}
...
Now I've created a new Bundle that also loads these Entities in a Controller.
As expected, it also triggers the postLoad in the listener.
I need it not to trigger it, or if it's triggered by this Bundle/Controller, to don't do anything, something like:
class MyListener
{
public function postLoad(LifecycleEventArgs $args)
{
$entity = $args->getObject();
if ($caller = "DontTriggerBundle")
return true;
}
if ($entity instanceof MyEntity) {
//do something
}
...
Is there a way to do this? Thanks in advance
So this is how I solved it:
Added the request stack to the service:
<service id="myservice>
<argument type="service" id="request_stack"/>
</service>
Then got the controller like this:
// src/AppBundle/EventListener/AcmeListener.php
namespace AppBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
class AcmeListener
{
/** #var Request */
protected $request;
/**
* AcmeListener constructor.
*
* #param RequestStack $requestStack
*/
public function __construct(RequestStack $requestStack)
{
$this->request = $requestStack->getCurrentRequest();
}
/**
* #param LifecycleEventArgs $args
*/
public function postLoad(LifecycleEventArgs $args)
{
$controller = $this->request->attributes->get('_controller');
if (strpos($controller, 'DontTriggerController::indexAction') !== false) {
// Do nothing
return;
}
// Do somethings
}
}
hope this helps someone

How can i get entityManager inside Subscriber in Symfony

I use Api Platform. I have subscriber
namespace App\EventSubscriber\Api;
use ApiPlatform\Core\EventListener\EventPriorities;
use App\Entity\Product;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\KernelEvents;
final class ProductCreateSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
KernelEvents::VIEW => ['createHost', EventPriorities::POST_WRITE],
];
}
public function createHost(GetResponseForControllerResultEvent $event)
{
$product = $event->getControllerResult();
$method = $event->getRequest()->getMethod();
if (!$product instanceof Product || Request::METHOD_POST !== $method) {
return;
}
I NEED ENTITY MANAGER HERE
}
}
Is it possible to get a entity manager here?
I need create another entity, after creating Product
Symfony allow (and recommend) to inject dependencies in services.
We add a constructor to the subscriber in order to inject Doctrine and make it accessible though $this->entityManager:
use Doctrine\ORM\EntityManagerInterface;
final class ProductCreateSubscriber implements EventSubscriberInterface
{
/**
* #var EntityManagerInterface
*/
private $entityManager;
public function __construct(
EntityManagerInterface $entityManager
) {
$this->entityManager = $entityManager;
}
public function createHost(GetResponseForControllerResultEvent $event)
{
$product = $event->getControllerResult();
$method = $event->getRequest()->getMethod();
if (!$product instanceof Product || Request::METHOD_POST !== $method) {
return;
}
// You can access to the entity manager
$this->entityManager->persist($myObject);
$this->entityManager->flush();
}
If autowiring is enabled, you'll have nothing else to do, the service will be instantiated automatically.
If not, you'll have to declare the service:
App\EventSubscriber\Api\ProductCreateSubscriber:
arguments:
- '#doctrine.orm.entity_manager'

Categories