Doctrine inserting on POST_WRITE event with Symfony 5 and APIplatform - php

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();
}
}

Related

Why the sendMailConfirmation method does not send a signedURL to the user?

By using Symfony 5 and after user registration, the system should send an automatic email to the user to activate a signed URL. In EmailVerifer.php, the sendMailConfirmation method does not recognize the getId and getEmail methods. However after registration the confirmation email is anyway sent by the system to the user but without a signed URL to activate it. I think the issue is related to the implementation of serialization and deserialization methods in user class, as it worked before Serialization implementation. I'm trying to implement serializable methods in EmailVerifier class but it doesn't work. Thanks for the help.
<?php
namespace App\Security;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
use SymfonyCasts\Bundle\VerifyEmail\VerifyEmailHelperInterface;
class EmailVerifier
{
private $verifyEmailHelper;
private $mailer;
private $entityManager;
public function __construct(VerifyEmailHelperInterface $helper, MailerInterface $mailer, EntityManagerInterface $manager)
{
$this->verifyEmailHelper = $helper;
$this->mailer = $mailer;
$this->entityManager = $manager;
}
public function sendEmailConfirmation(string $verifyEmailRouteName, UserInterface $user, TemplatedEmail $email): void
{
$signatureComponents = $this->verifyEmailHelper->generateSignature(
$verifyEmailRouteName,
$user->getId(),
$user->getEmail()
);
$context = $email->getContext();
$context['signedUrl'] = $signatureComponents->getSignedUrl();
$context['expiresAtMessageKey'] = $signatureComponents->getExpirationMessageKey();
$context['expiresAtMessageData'] = $signatureComponents->getExpirationMessageData();
$email->context($context);
$this->mailer->send($email);
}
/**
* #throws VerifyEmailExceptionInterface
*/
public function handleEmailConfirmation(Request $request, UserInterface $user): void
{
$this->verifyEmailHelper->validateEmailConfirmation($request->getUri(), $user->getId(), $user->getEmail());
$user->setIsVerified(true);
$this->entityManager->persist($user);
$this->entityManager->flush();
}
}

Deal with 'new' keyword in PHP unit test

In my case I have a class such as:
class Logger
{
/**
* #var EntityManager
*/
protected $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* #param Model $model
*/
public function log(Model $model)
{
$logEntity = new LogEntity();
$logEntity->setOrder($model->getOrder());
$logEntity->setType($model->getType());
$logEntity->setScore($model->getScore());
$logEntity->setCreated(new DateTime());
$logEntity->setModified(new DateTime());
$this->entityManager->persist($logEntity);
$this->entityManager->flush();
return $logEntity;
}
Logger class is not testable because in my code 'new' keyword exists, in other hand Logger class and EntityManager class registered singleton in container and can't inject model as dependency.
How to change class for change to testable class?
LoggerModel is a Doctrine entity and use in Laravel framework.
I solve this problem with a sample solution: Factory Pattern.
I need assertion, so when get a new model from factory assert it as mock.
And how?
I create a class with a method that can be a singleton service:
class LogFactory
{
public function makeLogEntity()
{
return new LogEntity();
}
}
In another service, inject factory class:
class Logger
{
/**
* #var EntityManager
*/
protected $entityManager;
/**
* #var LogFactory
*/
protected $logFactory;
public function __construct(EntityManager $entityManager, LogFactory $logFactory)
{
$this->entityManager = $entityManager;
$this->logFactory = $logFactory
}
/**
* #param Model $model
*/
public function log(Model $model)
{
$logEntity = $this->logFactory->makeLogEntity();
$logEntity->setOrder($model->getOrder());
$logEntity->setType($model->getType());
$logEntity->setScore($model->getScore());
$logEntity->setCreated(new DateTime());
$logEntity->setModified(new DateTime());
$this->entityManager->persist($logEntity);
$this->entityManager->flush();
return $logEntity;
}
Now I have a service that is mock able and call $mock->willReturn() function in test.

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'

Symfony EventSubscribe on Entity

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
}
}
}

Symfony2 $user->setPassword() updates password as plain text [DataFixtures + FOSUserBundle]

I'm trying to pre-populate a database with some User objects, but when I call $user->setPassword('some-password'); and then save the user object, the string 'some-password' is stored directly in the database, instead of the hashed+salted password.
My DataFixture class:
// Acme/SecurityBundle/DataFixtures/ORM/LoadUserData.php
<?php
namespace Acme\SecurityBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Acme\SecurityBundle\Entity\User;
class LoadUserData implements FixtureInterface
{
public function load(ObjectManager $manager)
{
$userAdmin = new User();
$userAdmin->setUsername('System');
$userAdmin->setEmail('system#example.com');
$userAdmin->setPassword('test');
$manager->persist($userAdmin);
$manager->flush();
}
}
And the relevant database output:
id username email salt password
1 System system#example.com 3f92m2tqa2kg8cookg84s4sow80880g test
Since you are using FOSUserBundle, you can use UserManager to do this. I would use this code (assuming you have $this->container set):
public function load(ObjectManager $manager)
{
$userManager = $this->container->get('fos_user.user_manager');
$userAdmin = $userManager->createUser();
$userAdmin->setUsername('System');
$userAdmin->setEmail('system#example.com');
$userAdmin->setPlainPassword('test');
$userAdmin->setEnabled(true);
$userManager->updateUser($userAdmin, true);
}
Call setPlainPassword instead.
<?php
namespace Acme\SecurityBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Acme\SecurityBundle\Entity\User;
class LoadUserData implements FixtureInterface, ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function load(ObjectManager $manager)
{
$userAdmin = new User();
$userAdmin->setUsername('System');
$userAdmin->setEmail('system#example.com');
$userAdmin->setPlainPassword('test');
$userAdmin->setRoles(array('ROLE_SUPER_ADMIN'));
$manager->persist($userAdmin);
$manager->flush();
}
}
Four lines of code and you are done. It will handle everything for you:
$userManager = $this->container->get('fos_user.user_manager');
$user->setPlainPassword($password);
$userManager->updatePassword($user);
This worked for me
public function load(ObjectManager $manager){
$userAdmin = new User();
$userAdmin->setUsername('admin');
$userAdmin->setPlainPassword('admin');
$userAdmin->setEmail('admin#gmail.com');
$userAdmin->setEnabled(true);
$manager->persist($userAdmin);
$manager->flush();
}
Note the difference when setting the password. Querying the database you find
id username username_canonical email email_canonical enabled salt password
2 admin admin admin#gmail.com admin#gmail.com 1 4gm0bx6jzocgksw0wws8kck04kg40o8 m2ZyJM2+oBIzt/NZdnOX4nFvjV/SWTU1qJqe6dWZ0UwLF5gB8N...
$userAdmin->setUsername('System');
$userAdmin->setEmail('system#example.com');
$userAdmin->setPlainPassword('test');
$userAdmin->setEnabled(true);
setPlainPassword works for me.
/**
* 添加用户
* #param $param
* #return int
*/
public function doAdd($param)
{
$entity = new User();
$em = $this->getEntityManager();
$entity->setUsername($param['username'])
->setPlainPassword($param['password'])
->setEmail($param['email'])
->setEnabled(true)
->setRealName($param['realName']);
$em->persist($entity);
$em->flush();
return $entity->getId();
}
Above worked for me, so I got some conclusion:
1. must use the setPlainPassword
2. must setEnabled(true)
Here a sample class to create an admin user via ORM Fixtures:
<?php
namespace Acme\SecurityBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Acme\SecurityBundle\Entity\User;
class LoadFOSAdminUser extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function load(ObjectManager $manager)
{
$userManager = $this->container->get('fos_user.user_manager');
$userAdmin = $userManager->createUser();
$userAdmin->setUsername('admin');
$userAdmin->setEmail('admin#example.com');
$userAdmin->setPlainPassword('admin');
$userAdmin->setEnabled(true);
$userAdmin->setRoles(array('ROLE_ADMIN'));
$userManager->updateUser($userAdmin, true);
}
public function getOrder()
{
return 1;
}
}

Categories