Cannot autowire service ... Though service parameter is configure - php

I have a problem autowiring some parameters in services config.
I can't clear:cache because it triggers the error so I don't think it is a cache issue.
And I have copy/paste all my files into a whole new Symfony project and still got the same problem.
This is my services.yaml
#config/services.yaml
parameters:
app.medipim_api_key_value: '%env(resolve:MEDIPIM_API_KEY)%'
app.medipim_api_key_id: 326
services:
[6 lines ...]
# 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}'
App\Services\MedipimApi:
arguments:
$medipim_api_key_id: '%app.medipim_api_key_id%'
$medipim_api_key_value: '%app.medipim_api_key_value%'
[10 lines ...]
This is my services :
#src/Services/MedipimApi.php
class MedipimApi
{
const FILTER_CNK_AND_ACTIVE = 'filter_cnk_and_active';
/**
* #var MedipimApiV3Client
*/
private MedipimApiV3Client $medipimApiV3Client;
private TranslatorInterface $translator;
/**
* MedipimApi constructor.
* #param string $medipim_api_key_id API key id for Medipim API call
* #param string $medipim_api_key_value Api key (secret) value for Medipim API call
* #param TranslatorInterface $translator TranslatorInterface object for I18N purpose
*/
public function __construct(string $medipim_api_key_id, string $medipim_api_key_value, TranslatorInterface $translator)
{
$this->translator = $translator;
$this->medipimApiV3Client = new MedipimApiV3Client($medipim_api_key_id, $medipim_api_key_value);
}
....
When I try to call this service, I got this error :
Cannot autowire service "App\Services\MedipimApi": argument
"$medipim_api_key_id" of method "__construct()" is type-hinted
"string", you should configure its value explicitly.
It is working only when I copy the value of services.yaml and paste it into my services_env.yaml (where env is my current environment).
#config/services_dev.yaml
parameters:
app.medipim_api_key_value: '%env(resolve:MEDIPIM_API_KEY)%'
app.medipim_api_key_id: 326
services:
[6 lines ...]
# 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}'
App\Services\MedipimApi:
arguments:
$medipim_api_key_id: '%app.medipim_api_key_id%'
$medipim_api_key_value: '%app.medipim_api_key_value%'
[10 lines ...]
Now it works!
Why did that work?
Symfony is suppose to load file as described here :
For the dev environment, Symfony loads the following config files and
directories and in this order:
config/packages/*
config/packages/dev/*
config/services.yaml
config/services_dev.yaml
What did I miss?
EDIT
My kernel class looks right. It is loading services.yaml and then services_env.yaml as it should be :
class Kernel extends BaseKernel
{
[1 line...]
protected function configureContainer(ContainerConfigurator $container): void
{
$container->import('../config/{packages}/*.yaml');
$container->import('../config/{packages}/'.$this->environment.'/*.yaml');
if (is_file(\dirname(__DIR__).'/config/services.yaml')) {
$container->import('../config/{services}.yaml');
$container->import('../config/{services}_'.$this->environment.'.yaml');
} elseif (is_file($path = \dirname(__DIR__).'/config/services.php')) {
(require $path)($container->withPath($path), $this);
}
}
[14 lines...]

Why you configure param xx_id as int in your yml file ? In your service you type-hinted id as string !

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 4 : problem with locale in Command

I'm building a Symfony 4 application using locale.
My locale is binded in services.yaml :
parameters:
locale: 'en'
app_locales: en|fr|de|es|pt|
services:
_defaults:
autowire: true
autoconfigure: true
bind:
$locale: "#=service('request_stack').getCurrentRequest().getLocale()"
$listLocales: '%app_locales%'
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
App\Repository\:
esource: '../src/Repository'
Also, locale is injected and can be used in my repositories (used for elastic search request).
I need to add a command that uses one of my repositories to retrieve some data but I get this error:
In getSomeRepositoryService.php line 12:
Call to a member function getLocale() on null
var/cache/dev/ContainerJyByO4D/getSomeRepositoryService.php:
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.
// Returns the private 'App\Repository\SomeRepository' shared autowired service.
include_once $this->targetDirs[3].'/src/Repository/AbstractRepository.php';
include_once $this->targetDirs[3].'/src/Repository/SomeRepository.php';
return $this->privates['App\Repository\SomeRepository'] = new \App\Repository\SomeRepository(($this->privates['Elastica\Client'] ?? $this->load('getClientService.php')), ($this->services['request_stack'] ?? ($this->services['request_stack'] = new \Symfony\Component\HttpFoundation\RequestStack()))->getCurrentRequest()->getLocale());
Locale is injected in AbstractRepository constructor.
Here's my command (simplified version) :
classFooCommand extends Command
{
protected $someRepository;
public function __construct(SomeRepository $someRepository)
{
$this->someRepository = $someRepository;
parent::_construct();
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$demoData = $this->someRepository->getFoo();
var_dump($demoData);
}
}
How can the locale get injected in my command correctly?

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

Symfony 4 not autowiring dynamic route controllers

Symfony version: 4.1.3
I have an application which loads dynamic routes based on configuration by virtue of a route loader service, but it appears that Symfony 4 is not autowiring the dynamic route controllers.
I am using the standard Symfony 4 application services.yaml file:
/config/services.yaml
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 # Allows optimizing the container by removing unused services; this also means
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
Application\Web\:
resource: '../src/*'
exclude: '../src/{Search/Model,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
Application\Web\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
Route Loader: src/Component/RouteLoader.php
<?php
namespace Application\Web\Component;
use Application\Symfony\Bundle\ConfigBundle\ReaderInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Class RouteLoader
* #package Application\Web\Component
*/
class RouteLoader
{
/**
* #var ReaderInterface
*/
private $configurationReader;
public function __construct(ReaderInterface $configurationReader)
{
$this->configurationReader = $configurationReader;
}
public function load(): RouteCollection
{
$routes = new RouteCollection();
foreach ($this->configurationReader->find('category_navigation') as $label => $configuration) {
$slug = strtolower($label);
$route = new Route(
$configuration['url'],
[
'_controller' => [$configuration['controller'], 'dispatch'],
'categories_slug' => $slug,
'category_label' => $label,
'page_title' => $configuration['page_title'] ?? null,
'page_description' => $configuration['page_description'] ?? null,
],
[],
[],
'',
[],
['GET']
);
$routes->add($slug . '.home', $route);
}
return $routes;
}
}
Controller constructor: src/Controller/Page.php
<?php
namespace Application\Web\Controller;
//.... other class code
public function __construct(
ClientInterface $client,
ReaderInterface $configurationReader,
\Twig_Environment $twigEnvironment,
ContentSearcher $contentSearcher,
Environment $environment,
TokenStorageInterface $tokenStorage,
UrlGeneratorInterface $urlGenerator
)
When I try to load the page, Symfony whines with the following error:
Controller "\Application\Web\Controller\Page" has required constructor arguments and does not exist in the container. Did you forget to define such a service?
However, when I define the route directly in the config/routes.yaml file, the controller is autowired in style with no issues!
My questions are:
Is this a limitation of Symfony's autowiring capability, i.e. not supported for dynamic route controllers?
Is there something that I'm missing when it comes to defining the routes that will make the autowiring work?
Have I potentially identified a bug?
Any ideas?
EDIT: Stack traces
InvalidArgumentException:
Controller "\Application\Web\Controller\Page" has required constructor arguments and does not exist in the container. Did you forget to define such a service?
at vendor/symfony/http-kernel/Controller/ContainerControllerResolver.php:62
at Symfony\Component\HttpKernel\Controller\ContainerControllerResolver->instantiateController('\\Application\\Web\\Controller\\Page')
(vendor/symfony/framework-bundle/Controller/ControllerResolver.php:54)
at Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver->instantiateController('\\Application\\Web\\Controller\\Page')
(vendor/symfony/http-kernel/Controller/ControllerResolver.php:49)
at Symfony\Component\HttpKernel\Controller\ControllerResolver->getController(object(Request))
(vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php:38)
at Symfony\Component\HttpKernel\Controller\TraceableControllerResolver->getController(object(Request))
(vendor/symfony/http-kernel/HttpKernel.php:132)
at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), 1)
(vendor/symfony/http-kernel/HttpKernel.php:66)
at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), 1, true)
(vendor/symfony/http-kernel/Kernel.php:188)
at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
(public/index.php:37)
ArgumentCountError:
Too few arguments to function Application\Web\Controller\Base::__construct(), 0 passed in /var/www/html/vendor/symfony/http-kernel/Controller/ControllerResolver.php on line 133 and exactly 7 expected
at src/Controller/Base.php:55
at Application\Web\Controller\Base->__construct()
(vendor/symfony/http-kernel/Controller/ControllerResolver.php:133)
at Symfony\Component\HttpKernel\Controller\ControllerResolver->instantiateController('\\Application\\Web\\Controller\\Page')
(vendor/symfony/http-kernel/Controller/ContainerControllerResolver.php:55)
at Symfony\Component\HttpKernel\Controller\ContainerControllerResolver->instantiateController('\\Application\\Web\\Controller\\Page')
(vendor/symfony/framework-bundle/Controller/ControllerResolver.php:54)
at Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver->instantiateController('\\Application\\Web\\Controller\\Page')
(vendor/symfony/http-kernel/Controller/ControllerResolver.php:49)
at Symfony\Component\HttpKernel\Controller\ControllerResolver->getController(object(Request))
(vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php:38)
at Symfony\Component\HttpKernel\Controller\TraceableControllerResolver->getController(object(Request))
(vendor/symfony/http-kernel/HttpKernel.php:132)
at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), 1)
(vendor/symfony/http-kernel/HttpKernel.php:66)
at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), 1, true)
(vendor/symfony/http-kernel/Kernel.php:188)
at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
(public/index.php:37)
Answering your questions:
No, at least not in this case. The only role of the Route Loader is to build a collection of routes so a request can be matched against it. By providing the _controller parameter, you are telling SF which controller is mapped to that route. The Dependency Injection component is the one in charge of autoloading the controllers into the container as a service. But if the controller reaches the ContainerControllerResolver and passes from lines 50-52, it is definitely due to the fact that your controller is not registered as a service (or more precisely, to the fact that whatever is the value of $class in ContainerControllerResolver::instantiateController() does not exist in the container).
I cannot reproduce, since I don't have your services. But my best guess is that is probably not working passing the _controller argument as some sort of callable array. Try pass it as a string like Application/Web/Pages::instance. The ContainerControllerResolver is able to work with that.
I have my doubts, but it's probable.
It would be very helpful to be able to see an stack trace.
UPDATE:
You have double backslashes in the string that conforms your class name. Try reformatting to: Application\Web\Controller\Page.
If you want to be sure of this fix before refactoring, run bin/console debug:container Page and check if your Page controller, as a service, exists. If it does, then the problem is, most certainly, the FQCN with double backslashes.

Categories