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

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

Related

Bypass auto wiring using services.yml symfony

I have a quick question regarding auto wiring on symfony.
I am currently migrating my symfony 2.8 app to symfony 5.4. I have my BaseManager class which is used by several manager of my application.
Several managers therefore use this class and pass it 2 arguments as parameters which are EntityManager and $class.
Of course, the $class variable is different for each manager and therefore cannot be managed by auto wiring.
So I have my managers that are declared as a service. However these declarations are not recognized by the auto wiring because I use an alias for these services. Is there a way to link my service alias to my manager without having to recreate several statements with the full name of the manager in the services.yml? this is a bit redundant, especially since I have about forty managers to transfer.
Thank you
This is my custom services.yml
# Employee Manager
WORD\EmployeeBundle\Service\EmployeeManager:
arguments:
- '#doctrine.orm.entity_manager'
- '%word.employee.model.employee.class%'
public: true
# Employee Manager
word.employee.manager.employee:
class: 'WORD\EmployeeBundle\Service\EmployeeManager'
arguments:
- '#doctrine.orm.entity_manager'
- '%word.employee.model.employee.class%'
public: true
This is my custom baseManager who expect $em and $class
abstract class BaseManager{
protected $em;
protected $repository;
protected $class;
public function __construct(EntityManagerInterface $em, string $class)
{
$this->em = $em;
$this->repository = $em->getRepository($class);
$metadata = $em->getClassMetadata($class);
$this->class = $metadata->name;
}

DependencyInjection in AbstractEntity

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.

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?

Inject EntityManagerInterface to my service with `must implement interface Doctrine\ORM\EntityManagerInterface error`

I have created a service at ./src/Service, and I want to use the Doctrine Entity Manager in my service, so I inject it in the __construct method:
namespace App\Service;
use App\Entity\Category;
use Doctrine\ORM\EntityManagerInterface;
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError;
class CommonPageGenerator
{
/**
* #var EntityManagerInterface
*/
private $em;
/**
* #var Environment
*/
private $templating;
public function __construct(EntityManagerInterface $em, Environment $templating)
{
$this->em = $em;
$this->templating = $templating;
}
public function page1($title){ return; }
}
I then inject this service in a controller:
/**
* #Route("/overseas", name="overseas")
* #param CommonPageGenerator $commonPageGenerator
*/
public function overseas(CommonPageGenerator $commonPageGenerator)
{
return $commonPageGenerator->page1('overseas');
}
But I get the following error:
Argument 1 passed to App\Service\CommonPageGenerator::__construct() must implement interface Doctrine\ORM\EntityManagerInterface, string given, called in /Users/tangmonk/Documents/mygit/putixin.com/putixin_backend/var/cache/dev/ContainerB7I3rzx/getCommonPageGeneratorService.php on line 11
My services.yaml file:
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.
bind:
$em: 'doctrine.orm.default_entity_manager'
I am using Symfony 4.3
You do not need binding $em to the Doctrine entity manager.
If you remove that line and leave the type hint only (__construct(EntityManagerInterface $em, Environment $templating) should be enough.
Thus leaving your __construct() method like this:
// you can of course import the EngineInterface with a "use" statement.
public function __construct(
EntityManagerInterface $em,
Environment $templating)
{
$this->em = $em;
$this->templating = $templating;
}
If you do this and remove the bind configuration, automatic dependency injection should work by itself.
(Normally I would suggest replacing Environment with Symfony\Bundle\FrameworkBundle\Templating\EngineInterface, to depend in the interface provided by the framework to integrate with the templating component. But this component and its integration have been deprecated in 4.3, and will be removed by 5.0; so you are fine by depending directly on Twig.)
But if want to you leave the binding in place for some reason, you should prefix the service name with an # symbol, so Symfony knows you are trying to inject a service and not string. Like so:
bind:
$em: '#doctrine.orm.default_entity_manager'
Docs.

Cannot autowire service: Argument references class but no such service exists

I'm upgrading a project from Symfony 3 to Symfony 4
(https://github.com/symfony/symfony/blob/master/UPGRADE-4.0.md) and I have many repository/services like this:
namespace App\Entity;
use App\Entity\Activation;
use Doctrine\ORM\EntityRepository;
use Predis\Client;
class ActivationRepository extends EntityRepository
{
// ...
}
And when I try to run the project in the browser like this:
http://localhost:8000/login
I get this error:
(1/1) RuntimeException
Cannot autowire service "App\Entity\ActivationRepository":
argument "$class" of method
"Doctrine\ORM\EntityRepository::__construct()"
references class "Doctrine\ORM\Mapping\ClassMetadata"
but no such service exists.
Does this mean you have to create a service for "Doctrine\ORM\Mapping\ClassMetadata" in your services.yaml file?
Thanks to autowiring my new services.yaml file is fairly small compared to the old one, which had 2000+ lines.
The new services.yaml just has several of these (so far):
App\:
resource: '../src/*'
# Controllers
App\Controller\:
resource: '../src/Controller'
autowire: true
public: true
tags: ['controller.service_arguments']
# Models
App\Model\:
resource: '../src/Model/'
autowire: true
public: true
// etc
Question:
Do you really need to add service definitions to services.yaml for third party vendor classes? And if so, can I get an example of how to do that please?
Any advice from anyone who has already upgraded from Symfony 3 to Symfony 4 would be great.
PHP 7.2.0-2+ubuntu16.04.1+deb.sury.org+2 (cli) (built: Dec 7 2017 20:14:31) ( NTS )
Linux Mint 18, Apache2 Ubuntu.
EDIT / FYI:
This is the "Doctrine\ORM\EntityRepository::__construct()" which the ActivationRepository extends:
/**
* Initializes a new <tt>EntityRepository</tt>.
*
* #param EntityManager $em The EntityManager to use.
* #param Mapping\ClassMetadata $class The class descriptor.
*/
public function __construct(EntityManagerInterface $em, Mapping\ClassMetadata $class)
{
$this->_entityName = $class->name;
$this->_em = $em;
$this->_class = $class;
}
which is located here:
/vendor/doctrine/orm/lib/Doctrine/ORM/EntityRepository.php
Starting from the 1.8 version of DoctrineBundle, you can extend your class using Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository instead of Doctrine\ORM\EntityRepository. The result will be the same, but this does support the autowire.
Example:
use App\Entity\Activation;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;
class ActivationRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Activation::class);
}
// ...
}
Do you really need to add service definitions to services.yaml for third party vendor classes?
No, don't do that. My personal suggestion is: don't extend EntityRepository. Ever. You don't want your repository's interface to have method like createQuery or flush. At least, you don't want that if you consider a repository just like a collection of objects. If you extend EntityRepository you will have a leaky abstraction.
Instead you can inject the EntityManager inside your repository, and that's it:
use App\Entity\Activation;
use App\Repository\ActivationRepository;
use Doctrine\ORM\EntityManagerInterface;
final class DoctrineActivationRepository implements ActivationRepository
{
private $entityManager;
private $repository;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
$this->repository = $this->entityManager->getRepository(Activation::class);
}
public function store(Activation $activation): void
{
$this->entityManager->persist($activation);
$this->entityManager->flush();
}
public function get($id): ?Activation
{
return $this->repository->find($id);
}
// other methods, that you defined in your repository's interface.
}
No other steps are required.
My issue was a wrong namespace.
File real position was App\Infrastructure\MySQL\Rubric\Specification
But namespace was set to App\Infrastructure\Rubric\Specification
Result "[blah blah] but no such service exists".
I got this issue. My service had a private constructor. I had to change:
private function __construct(
to
public function __construct(

Categories