DependencyInjection in AbstractEntity - php

i started working on a new Symfony 6.0 project.
I created a new Entity called Project. In this entity I want to set the created_by property automaticlly on PrePersist (hook) call...
Therefore I created an AbstractEntity to extend the original Project entity.
In AbstractEntity I want to automatically inject Symfony\Component\Security\Core\Security service.
BUT the autowire stuff just doesn't work.
# config/services.yaml
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/' # --> i removed that line (doesnt work)
- '../src/Kernel.php'
#this also does not work
App\Entity\AbstractEntity:
autowire: true
#this also does not work
App\Entity\AbstractEntity:
arguments:
- '#security.helper'
// src/Entity/AbstractEntity.php
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\Security;
#[ORM\MappedSuperclass]
#[ORM\HasLifecycleCallbacks]
abstract class AbstractEntity
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
}

The entity should not have any dependencies and contain logic. If you want to do something, consider creating Doctrine Lifecycle Listeners prePersist or Doctrine Entity Listeners.
Lifecycle listeners are defined as PHP classes that listen to a single
Doctrine event on all the application entities.
Add to services.yaml file
App\EventListener\CreatedByLifecycleEvent:
tags:
-
name: 'doctrine.event_listener'
event: 'prePersist'
And create a listener
namespace App\EventListener;
use Doctrine\Persistence\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\Security;
class CreatedByLifecycleEvent
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function prePersist(LifecycleEventArgs $args): void
{
$entity = $args->getObject();
if(method_exists($entity,'setCreatedBy') and !empty($user = $this->security->getUser())){
$entity->setCreatedBy($user);
}
}
}
Thus, when saving any entity, provided that the setCreatedBy method exists, our listener will set the current user.

Related

symfony5 service - how to inject session and user without the ContainerInterface

I have this deprecation message:
Since symfony/dependency-injection 5.1: The
"Symfony\Component\DependencyInjection\ContainerInterface" autowiring
alias is deprecated. Define it explicitly in your app if you want to
keep using it.
From threads such as this Symfony: Explicit define Container in Service I understand that the long-term solution is to stop using the ContainerInterface all together in my services.
My services.yaml looks like this:
parameters:
#locale: en
basepath: '%env(basepath)%'
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller/'
tags: ['controller.service_arguments']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
globalHelper:
class: App\Service\globalHelper
public: false
The service in question (globalHelper) looks like this:
<?php
namespace App\Service;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
use Doctrine\ORM\EntityManagerInterface as EntityManager;
class globalHelper {
private $container;
private $em;
public function __construct(Container $container, EntityManager $em) {
$this->container = $container;
$this->em = $em;
}
I only user the container to fetch session variables like this
$this->container->get('session')->getFlashBag()->add($type, $message);
And to get the current user (security context) like this
$this->container->get('security.context')->getToken()->getUser();
Can I get these sub-components of the container separately instead? What component then would I inject to access these two parts (session and user) respectively?
--------------- Addition --------------
According to Alexis' suggestion below I modified the head of the file with
<?php
namespace App\Service;
//use Symfony\Component\DependencyInjection\ContainerInterface as Container;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Security;
use Doctrine\ORM\EntityManagerInterface as EntityManager;
class globalHelper {
//private $container;
private $requestStack;
private $security;
private $em;
//public function __construct(Container $container, RequestStack $requestStack, Security $security, EntityManager $em) {
public function __construct(RequestStack $requestStack, Security $security, EntityManager $em) {
//$this->container = $container;
$this->requestStack = $requestStack;
$this->security = $security;
$this->em = $em;
}
then replaced
$this->container->get('session')->getFlashBag()->add($type, $message);
with
$this->requestStack->getSession()->getFlashBag()->add($type, $message);
and get this error:
Attempted to call an undefined method named "getSession" of class
"Symfony\Component\HttpFoundation\RequestStack".
if I instead to this:
$this->requestStack->get('session')->getFlashBag()->add($type, $message);
Attempted to call an undefined method named "get" of class
"Symfony\Component\HttpFoundation\RequestStack". Did you mean to call
e.g. "getCurrentRequest", "getMasterRequest" or "getParentRequest"?
First it’s not mandatory to declare your service help with
autoconfigure: true
Then you must inject
Symfony\Component\HttpFoundation\RequestStack
and make
$requestStack->getSession()
Here's the docs
https://symfony.com/doc/current/session.html
For user you inject
Symfony\Component\Security\Core\Security
and make
$security->getUser()
Here's the docs
https://symfony.com/doc/current/security.html#fetching-the-user-from-a-service
-- EDIT --
Prio symfony 5.3 session can directly be injected with
Symfony\Component\HttpFoundation\Session\SessionInterface
It's depreciated after. Here's the blog post :
https://symfony.com/blog/new-in-symfony-5-3-session-service-deprecation
Using symfony 6.2, you can also add this on your service.yml:
Symfony\Component\HttpFoundation\Session\SessionInterface:
factory: "#=service('request_stack').getCurrentRequest()?.getSession()"
It can return null value

Symfony 5 - Inject Security service in Entity doesn't work

I try to get security service in an Entity.
When i want to access it in my entity, the property "$this->security" is null
See the entity :
<?php
namespace App\Entity\Production;
use Symfony\Component\Security\Core\Security;
/**
* #ORM\Entity(repositoryClass=MarqueRepository::class)
* #ORM\HasLifecycleCallbacks()
*/
class Marque
{
/* Others properties useless in the stackoverflow question*/
/**
* #var Security
*/
private $security;
public function __construct(Security $security)
{
$this->security = $security;
dd($this->security);
}
}
Autowiring is active in "services.yaml".
I removed the folder "Entity" in the "exclude src"
Could you help me ? Thxs
Bad practice.
As said in comment by Lunin Roman and Mcsky, security check could be made in service/controller/etc.
I was read some commentaries about this bad practice, unless that you just need retrive the currently user id logged. Then it is my case.
class Aaaa
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
}
services.yaml
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Kernel.php'
- '../src/Tests/
so i got this error..
Cannot create an instance of App\Entity\Aaaaa from serialized data
because its constructor requires parameter "security" to be present.
Could by config error?

Symfony Listener gone missing after minor changes in Controller

I am encountering some weir behaviour with Symfony listener. I have a working configuration, but when I change or add something to the controller, Expected to find class "App\EventListener\AuthenticationSuccessListener" shows up. The change could be only something inside route path string. All code is here.
Remove cache and server restart doesn't help.
Listener
namespace AppBundle\EventListener;
use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent;
use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationFailureEvent;
use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;
use Symfony\Component\Serializer\SerializerInterface;
class AuthenticationListener
{
/**
* #var SerializerInterface
*/
private $serializer;
public function __construct(SerializerInterface $serializer)
{
$this->serializer = $serializer;
}
/**
* #param AuthenticationSuccessEvent $event
*/
public function onAuthenticationSuccessResponse(AuthenticationSuccessEvent $event)
{
$event->setData([
'user' => $this->serializer->normalize($event->getUser(), null, ['groups' => ['basic']]),
'token' => $event->getData()['token'],
]);
}
}
services.yaml
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
services:
app.event.authentication_success_response:
class: AppBundle\EventListener\AuthenticationListener
tags:
- { name: kernel.event_listener, event: lexik_jwt_authentication.on_authentication_success, method: onAuthenticationSuccessResponse }
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
Controller
<?php
namespace App\Controller;
use App\Entity\User;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface;
class ApiUserController extends AbstractController
{
/**
* #Route("/api/verify_token", methods="POST", name="api_verify_token")
* #IsGranted("ROLE_USER")
*/
public function verifyToken(User $user = null){
return new JsonResponse([], 200);
}
}
Solved by #Jakumi answer. The error was caused by namespace AppBundle\EventListener. After the change to App\EventListener error disappear.

Problem with autowiring RegistryInterface in ServiceEntityRepository

I was following Symfony docs and wanted to use ServiceEntityRepository (link).
I created entity:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\BetRepository")
*/
class Bet
{
....
Repository was automatically created:
namespace App\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;
use App\Entity\Bet;
class BetRepository extends ServiceEntityRepository
{
public function __construct(RegistryInterface $registry)
{
parent::__construct($registry, Bet::class);
}
}
I wanted to create BetService which uses BetRepository:
namespace App\Service;
use App\Repository\BetRepository;
final class BetService
{
private $betRepository;
public function __construct(BetRepository $betRepository)
{
$this->betRepository = $betRepository;
}
finally I created a controller and wanted to inject BetService into it:
namespace App\Controller;
use App\Service\BetService;
final class BetController extends AbstractController
{
private $betService;
public function __construct(BetService $betService)
{
$this->betService = $betService;
}
}
Problem is I keep getting error:
Cannot autowire service "App\Repository\BetRepository": argument
"$registry" of method "__construct()" references interface
"Symfony\Bridge\Doctrine\RegistryInterface" but no such service
exists. Did you create a class that implements this interface?
my services.yaml is default one from installation:
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
public: false
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
I am using Symfony 4.2
I tried autowiring EntityManager to BetRepository however I get error that EntityManager constructor is not public and tbh I feel this is not the right approach as docs say it should work out of box.
I can provide composer.json (didn't initially as already question is quite long)
Thanks everyone in advance!

My event listener doesn't work from my reusable bundle in Symfony 4.1

Short story : I have some diffulties to use a listener properly on my custom "vendor" bundle.
I'm coding a reusable bundle for Symfony 4.1 framework (for managing Users). I put the whole bundle in this location of a new symfony project : myproject/lib/AcmeUserBundle/src/
Just like this Demo
Because my bundle is at a non usual place, I changed my config/services.yaml like this (in case this is relevent) :
services:
# default configuration for services in *this* file
_defaults:
autowire: true
autoconfigure: true
public: false
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
Acme\UserBundle\:
resource: '../lib/AcmeUserBundle/src/*'
exclude: '../lib/AcmeUserBundle/src/{DependencyInjection,Entity,Migrations,Tests}'
Acme\UserBundle\Controller\:
resource: '../lib/AcmeUserBundle/src/Controller'
tags: ['controller.service_arguments']
Then, I coded a login form (that you don't care much), and when a user logged in, I want to update his last datetime connexion info in database. So I have built a listener following this tutorial on the security.interactive_login event :
<?php
namespace Acme\UserBundle\EventListener;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
class LoginListener
{
/**
* #var EntityManagerInterface
*/
private $entityManager;
/**
* LoginListener constructor.
*
* #param EntityManagerInterface $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* #param InteractiveLoginEvent $event
*/
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
// Get the User entity.
$user = $event->getAuthenticationToken()->getUser();
/** #var \Acme\UserBundle\Entity\User $user */
$user->setLastLoggedAt(new DateTime());
$this->entityManager->persist($user);
$this->entityManager->flush();
}
}
lib/AcmeUserBundle/src/Resources/config/services.yaml looks like :
services:
Acme\UserBundle\EventListener\LoginListener:
tags:
- { name: kernel.event_listener, event: security.interactive_login }
lib/AcmeUserBundle/src/DependencyInjection/AcmeUserExtension.php looks like :
class AcmeUserExtension extends Extension
{
/**
* #param array $configs
* #param ContainerBuilder $container
*/
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader(
$container,
new FileLocator(__DIR__.'/../Resources/config')
);
$loader->load('services.yaml');
}
}
I can see my service using these symfony commands :
php bin/console debug:autowiring
php bin/console debug:container
But I can't see anything with :
php bin/console debug:event-dispatcher
When I log-in with my user, no error occurs, and of course, no date is inserted in database. That's why I think my listener is not properly registered.
Any idea why ?
EDIT after Cerad's comment :
Output of php bin/console debug:container LoginListener
---------------- ------------------------------------------------
Option Value
---------------- ------------------------------------------------
Service ID Acme\UserBundle\EventListener\LoginListener
Class Acme\UserBundle\EventListener\LoginListener
Tags -
Public no
Synthetic no
Lazy no
Shared yes
Abstract no
Autowired yes
Autoconfigured yes
---------------- ------------------------------------------------
We can see that the Tags part is empty. That's the reason why my listener is ignored.
And... it works if I comment that part of my config/service.yaml :
...
#Acme\UserBundle\:
# resource: '../lib/AcmeUserBundle/src/*'
# exclude: '../lib/AcmeUserBundle/src/{DependencyInjection,Entity,Migrations,Tests}'
...
New output of php bin/console debug:container LoginListener
---------------- -----------------------------------------------------------
Option Value
---------------- -----------------------------------------------------------
Service ID Acme\UserBundle\EventListener\LoginListener
Class Acme\UserBundle\EventListener\LoginListener
Tags kernel.event_listener (event: security.interactive_login)
Public no
Synthetic no
Lazy no
Shared yes
Abstract no
Autowired no
Autoconfigured no
---------------- -----------------------------------------------------------
Tags is now correct and my listener works properly

Categories