How to inject an array of parameters in Symfony service? - php

I'm trying to inject some twig views as templates for a custom mailer service, which will be used as dependency by another service.
I don't get why, but it's like Symfony doesn't see the parameters I'm trying to inject in $parameters.
What is the proper way to inject this array of services as parameter ?
Here is the services.yaml part:
parameters:
locale: 'en'
template: '%fos_user.registration.confirmation.template%'
resetting: '%fos_user.resetting.email.template%'
from_email: 'somemail#mail.com'
confirmation: '%fos_user.registration.confirmation.from_email%'
resetting_password: '%fos_user.resetting.email.from_email%'
services:
user.mailer.rest:
class: App\Mailer\RestMailer
public: false
parent: fos_user.mailer.twig_swift
autoconfigure: false
autowire: true
arguments:
$parameters:
- '#template'
- '#resetting'
The RestMailer service constructor:
public function __construct(\Swift_Mailer $mailer, UrlGeneratorInterface $router, \Twig_Environment $twig, array $parameters)
{
$this->mailer = $mailer;
$this->router = $router;
$this->twig = $twig;
$this->parameters = $parameters;
}
public function sendConfirmationEmailMessage(UserInterface $user)
{
$template = $this->parameters['template']['confirmation'];
//...
Here is the returned error:
In DefinitionErrorExceptionPass.php line 54:
Cannot autowire service "App\Mailer\RestMailer": argument "$parameters" >of method "__construct()" is type-hinted "array", you should configure >its value explicitly.

Did you already configure in the services.yml?
If you want to use the parameters in config.yml, you have to set up in the services.yml to used/call the parameters.

Related

Cannot autowire service "App\Service\MatchCarAdService": argument "$templating" of method

Hi I'm creating a service. This is the code,
namespace App\Service;
use Symfony\Component\DependencyInjection\ContainerInterface;
use App\Entity\CarAd;
class MatchCarAdService {
protected $mailer;
protected $templating;
public function __construct(ContainerInterface $container, \Swift_Mailer $mailer, $templating) {
$this->container = $container;
$this->mailer = $mailer;
$this->templating = $templating;
}
public function sendMail() {
$message = (new \Swift_Message('Hello Email'))
->setFrom('vimuths#yahoo.com')
->setTo('vimuths#yahoo.com')
->setBody(
$this->templating->render(
// templates/emails/matching-cars.html.html.twig
'emails/matching-cars.html.html.twig', []
), 'text/html'
);
$this->mailer->send($message);
This is services.yml
MatchCarAdService:
class: App\Service\MatchCarAdService
arguments: ['#mailer','#templating']
But I'm getting this error,
Cannot resolve argument $matchService of
"App\Controller\Api\SearchController()": Cannot autowire service
"App\Service\MatchCarAdService": argument "$templating" of method
"__construct()" has no type-hint, you should configure its value
explicitly.
Right now your constructor has 3 parameters but in arguments you are putting 2 only.
So there are two possible solutions:
Configure in your yaml
MatchCarAdService:
class: App\Service\MatchCarAdService
arguments: ['#container', '#mailer','#templating']
Use auto wiring with type hint
There it depends on your Symfony version, but change constructor to
public function __construct(ContainerInterface $container, \Swift_Mailer $mailer, Symfony\Component\Templating\EngineInterface; $teplating) {
$this->container = $container;
$this->mailer = $mailer;
$this->templating = $templating;
}
And you may have to composer require symfony/templating in order to get the Symfony\Bundle\FrameworkBundle\Templating\EngineInterface service.
Also the following configuration has to be added under framework:
templating:
enabled: true
engines: ['twig']
Symfony 3.3+ Answer
Answer by #M. Kebza solves your situation. But you can make this even simpler and bug-proof. Just use Symfony 3.3+ features.
A. Use Autowiring
services:
_defaults:
autowire: true
App\Service\MatchCarAdService: ~
App\Service\CleaningService: ~
App\Service\RentingService: ~
B. Use Autowiring + Autodiscovery - Even better!
services:
_defaults:
autowire: true
App\:
resource: ../src
This load all services in App\ namespace from ../src directory by PSR-4 convention.
You can see more examples in How to refactor to new Dependency Injection features in Symfony 3.3 post.

Autowire String parameter in symfony

How can I wire a String parameter in Symfony 3.4?
I have simple service and I want to wire a url parameter specified in parameters.yml:
namespace AppBundle\Service;
use Psr\Log\LoggerInterface;
class PythonService {
private $logger;
private $url;
/**
* #param LoggerInterface $logger
* #param String $url
*/
public function __construct(LoggerInterface $logger, String $url) {
$this->logger = $logger;
$this->url = $url;
}
}
My service.yml looks like:
AppBunde\Services\PythonService:
arguments: ['#logger', '%url%']
But I am getting error:
Cannot autowire service "AppBundle\Service\PythonService": argument "$url" of method "__construct()" is type-hinted "string", you should configure its value explicitly.
I tried also manually specify parameters:
AnalyticsDashboardBunde\Services\PythonService:
arguments:
$logger: '#logger'
$url: '%session_memcached_host%'
This gives me following error:
Invalid service "AppBundle\Services\PythonService": class "AppBundle\Services\PythonService" does not exist.
First, you have a typo in AppBundle\Services\PythonService (Services <> Service).
Then, string <> String. No uppercase in php.
You can bind an argument to a certain parameter/service:
service.yml:
services:
_defaults:
bind:
$memcacheHostUri: '%session_memcached_host%'
Service class: (have to be the same var name as specified ^)
public function __construct(LoggerInterface $logger, string $memcacheHostUri)
Controller action:
public function myAwesomeAction(PythonService $pythonService)
{
$pythonService->doPythonStuffs();
}
With this solution, if you create others services which need the memecacheHostUri, it will be autowired for these services too.
Resources:
Argument binding
// services.yml
app.python_service:
class: AppBundle\Service\PythonService
arguments:
$logger: '#monolog.logger.request'
$url: 'link'
public: true
// in controller
//use container:
$pS = $this->container->get('app.python_service');

Sylius/Symfony 3 inject service in a service

I created a service to extend the menu in admin of Sylius. It's work well ;)
I follow the official doc
I try to inject the router service in, but I've this following error :
Type error: Too few arguments to function
XXMenuListener::__construct(), 0 passed in
appDevDebugProjectContainer.php on line 1542 and exactly 1 expected
The declaration of this service :
services:
app.listener.admin.menu_builder:
class: XXX\Menu\AdminMenuListener
autowire: true
arguments:
- '#router'
tags:
- { name: kernel.event_listener, event: sylius.menu.admin.main, method: addAdminMenuItems }
and the service himself :
<?php
namespace XXX\Menu;
use Sylius\Bundle\UiBundle\Menu\Event\MenuBuilderEvent;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
final class AdminMenuListener
{
private $router;
public function __construct(Router $router){
$this->router = $router;
}
/**
* #param MenuBuilderEvent $event
*/
public function addAdminMenuItems(MenuBuilderEvent $event){
$menu = $event->getMenu();
$newSubmenu = $menu
->addChild('new')
->setLabel('XXX')
;
$newSubmenu
->addChild('new-subitem')
->setLabel('XXX')
//->setUri('https://www.google.com');
->setUri($this->router->generate('foo'))
;
}
}
What is wrong in ? Thanks for your help!
I think you need to clear cache if not helped to clean the cache directory manually.
In any case, you don't need a router service because menubuilder already has it.
For example:
for uri
$newSubmenu
->addChild('new-subitem')
->setLabel('XXX')
->setUri('https://www.google.com')
;
for route
$newSubmenu
->addChild('new-subitem', ['route' => 'foo'])
->setLabel('XXX')
;
If you use autowire to true you don't need to specify the router service. Something like this should be enough :
services:
app.listener.admin.menu_builder:
class: XXX\Menu\AdminMenuListener
autowire: true
tags:
- { name: kernel.event_listener, event: sylius.menu.admin.main, method: addAdminMenuItems }
In any case, your error indicates that you don't have any arguments. May be it's a caching issue or may be you have another service declaration for the same class XXX\Menu\AdminMenuListener without autowire to true and without arguments.

How to call Entity Manager in a constructor?

I've been trying to call Entity Manager in a constructor:
function __construct()
{
$this->getDoctrine()->getEntityManager();
...
but, as I've seen in this answer: Stackoverflow question, it can't be done.
So I wonder if there is a way to achieve it, as I have to call it often, and want to do some stuff in the constructor after getting the repository.
Edit:
I've tried with #MKhalidJunaid answer:
//src/MSD/HomeBundle/Resources/config/services.yml
services:
imageTransController.custom.service:
class: MSD\HomeBundle\Controller\ImageTransController
arguments:
EntityManager: "#doctrine.orm.entity_manager"
-
//app/config/config.php
imports:
- { resource: parameters.yml }
- { resource: security.yml }
- { resource: doctrine_extensions.yml }
- { resource: "#MSDHomeBundle/Resources/config/services.yml" }
-
//src/MSD/HomeBundle/Controller/ImageTransController.php
namespace MSD\HomeBundle\Controller;
use Doctrine\ORM\EntityManager;
use MSD\HomeBundle\Entity\Imagen as Imagen;
use MSD\HomeBundle\Controller\HomeController as HomeController;
class ImageTransController extends HomeController
{
protected $em ;
function __construct(EntityManager $entityManager)
{
...
but I'm getting this error:
Catchable Fatal Error: Catchable Fatal Error: Argument 1 passed to MSD\HomeBundle\Controller\ImageTransController::__construct() must be an instance of Doctrine\ORM\EntityManager, none given, called in /home/manolo/MiServer/itransformer/app/cache/dev/jms_diextra/controller_injectors/MSDHomeBundleControllerImageTransController.php on line 13 and defined in /home/manolo/MiServer/itransformer/src/MSD/HomeBundle/Controller/ImageTransController.php line 38 (500 Internal Server Error)
New attempt:
I've also tried with #praxmatig answer:
//services.yml
parameters:
msd.controller.imagetrans.class: MSD\HomeBundle\Controller\ImageTransController
services:
msd.imagetrans.controller:
class: "%msd.controller.imagetrans.class%"
arguments: [ #doctrine.orm.entity_manager ]
-
//ImageTransController.php
namespace MSD\HomeBundle\Controller;
use Doctrine\ORM\EntityManager;
class ImageTransController
{
protected $em ;
function __construct(EntityManager $em)
{
$this->em = $em;
}
...
-
//routing.yml
msd_home_cambiardimensiones:
pattern: /cambiardimensiones
defaults: { _controller: MSDHomeBundle:msd.imagetrans.controller:cambiardimensionesAction }
but I get this error:
Unable to find controller "MSDHomeBundle:msd.imagetrans.controller" - class "MSD\HomeBundle\Controller\msd.imagetrans.controllerController" does not exist. (500 Internal Server Error)
You need to make a service for your class and pass the doctrine entity manager as the argument doctrine.orm.entity_manager.Like in services.yml
services:
test.cutom.service:
class: Test\YourBundleName\Yourfoldernameinbundle\Test
#arguments:
arguments: [ #doctrine.orm.entity_manager ]
#entityManager: "#doctrine.orm.entity_manager"
You must import your services.yml in config.yml
imports:
- { resource: "#TestYourBundleName/Resources/config/services.yml" }
Then in your class's constructor get entity manager as argument
use Doctrine\ORM\EntityManager;
Class Test {
protected $em;
public function __construct(EntityManager $entityManager)
{
$this->em = $entityManager;
}
}
Hope this makes sense
Don't extend the base controller class when you register controller as a service. There is a documentation about it here
class ImageTestController
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function someAction()
{
// do something with $this->em
}
}
// services.yml
services:
acme.controller.image_test:
class: Acme\SomeBundle\Controller\ImageTestController
// routing.yml
acme:
path: /
defaults: { _controller: acme.controller.image_test:someAction }
Why do you want to grab the Doctrine 2 EntityManager in the constructor of a controller?
Why not simply do $em = $this->getDoctrine()->getManager(); (or $em = $this->getDoctrine()->getEntityManager(); in Symfony 2.0) in the action(s) you need it? This saves you from the overhead of initializing the EntityManager when you don't need it.
If you really do want to do this, there are clear instructions on How to define Controllers as Services. Basically it looks like this:
# src/MSD/HomeBundle/Controller/ImageTransController.php
namespace MSD\HomeBundle\Controller;
use Doctrine\ORM\EntityManager;
class ImageTransController
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function indexAction()
{
// use $this->em
}
}
# src/MSD/HomeBundle/Resources/config/services.yml
parameters:
msd.controller.image_trans.class: MSD\HomeBundle\Controller\ImageTransController
services:
msd.controller.image_trans:
class: "%msd.controller.image_trans.class%"
arguments: ["#doctrine.orm.default_entity_manager"]
# app/config/routing.yml
msd_home_cambiardimensiones:
path: /cambiardimensiones
defaults: { _controller: msd.controller.image_trans:indexAction }
You have to add
use Doctrine\ORM\EntityManager;
in your controller
I see that you are trying to get the entity manager in the constructor of the controller, which is not the way to do so , unless you plan to define your controller as a service.
which on this case, you need to use dependency injection to inject the service entity manager.
But in general the common way to use entity manager in a controller is simply by getting it using the following code:
$entityManager = $this->container->get('doctrine.orm.entity_manager');
I think you are in the right direction, I would take the second option:
For the second option I think that the definition inside routing.yml is wrong
//routing.yml
msd_home_cambiardimensiones:
pattern: /cambiardimensiones
defaults: { _controller: msd.imagetrans.controller:cambiardimensionesAction }
Here just remove MSDHomeBundle from the _controller inside defaults
For the first option:
Does HomeController has its own constructor?
//src/MSD/HomeBundle/Resources/config/services.yml
services:
imageTransController.custom.service:
class: MSD\HomeBundle\Controller\ImageTransController
arguments: [#doctrine]
it could help then inside the constructor
__construct(Registry $doctrine)
$this->doctrine = $doctrine;
or
$this->em = $doctrine->getManager();

How to inject a service in another service in Symfony?

I am trying to use the logging service in another service in order to trouble shoot that service.
My config.yml looks like this:
services:
userbundle_service:
class: Main\UserBundle\Controller\UserBundleService
arguments: [#security.context]
log_handler:
class: %monolog.handler.stream.class%
arguments: [ %kernel.logs_dir%/%kernel.environment%.jini.log ]
logger:
class: %monolog.logger.class%
arguments: [ jini ]
calls: [ [pushHandler, [#log_handler]] ]
This works fine in controllers etc. however I get no out put when I use it in other services.
Any tips?
You pass service id as argument to constructor or setter of a service.
Assuming your other service is the userbundle_service:
userbundle_service:
class: Main\UserBundle\Controller\UserBundleService
arguments: [#security.context, #logger]
Now Logger is passed to UserBundleService constructor provided you properly update it, e.G.
protected $securityContext;
protected $logger;
public function __construct(SecurityContextInterface $securityContext, Logger $logger)
{
$this->securityContext = $securityContext;
$this->logger = $logger;
}
For Symfony 3.3, 4.x, 5.x and above, the easiest solution is to use Dependency Injection
You can directly inject the service into another service, (say MainService)
// AppBundle/Services/MainService.php
// 'serviceName' is the service we want to inject
public function __construct(\AppBundle\Services\serviceName $injectedService) {
$this->injectedService = $injectedService;
}
Then simply, use the injected service in any method of the MainService as
// AppBundle/Services/MainService.php
public function mainServiceMethod() {
$this->injectedService->doSomething();
}
And viola! You can access any function of the Injected Service!
For older versions of Symfony where autowiring does not exist -
// services.yml
services:
\AppBundle\Services\MainService:
arguments: ['#injectedService']
More versatile option, is to once create a trait for the class you would want to be injected. For instance:
Traits/SomeServiceTrait.php
Trait SomeServiceTrait
{
protected SomeService $someService;
/**
* #param SomeService $someService
* #required
*/
public function setSomeService(SomeService $someService): void
{
$this->someService = $someService;
}
}
And where you need some service:
class AnyClassThatNeedsSomeService
{
use SomeServiceTrait;
public function getSomethingFromSomeService()
{
return $this->someService->something();
}
}
The class will autoload due to #required annotation. This generaly makes it much faster to implement when you want to inject services into numerous classes (like event handlers).

Categories