Symfony autowiring interface to a real service - symfony

I need to configure an autowiring for a Symfony 2.8 application.
A service has a dependency on a Symfony\Component\Templating\EngineInterface:
use Symfony\Component\Templating\EngineInterface;
class MyService
{
/** #var EngineInterface */
private $templating;
public function __construct(EngineInterface $templating)
{
$this->templating = $templating;
}
}
But I obviously get an error since this interface is implemented in three different classes.
Unable to autowire argument of type
"Symfony\Component\Templating\EngineInterface" for the service
"myservice". Multiple
services exist for th is interface (templating.engine.delegating,
templating.engine.php, templating.engine.twig).
I would like to inject the templating.engine.delegating every time when the EngineInterface is required so I add a definition to my service.yml
services:
template:
autowiring_type: 'Symfony\Component\Templating\EngineInterface'
alias: 'templating.engine.delegating'
But I still get the same error - application can not find which service to use for the EngineInterface
So the question is it possible to wire an interface to an already defined kernel service?
I've managed to handle similar problem when have to wire an abstract class to a service.
A service:
use Doctrine\Common\Cache\CacheProvider;
class MyOtherService
{
/** #var CacheProvider */
private $cache;
public function __construct(CacheProvider $cache)
{
$this->cache = $cache;
}
}
services:
memcache_cache:
class: 'Doctrine\Common\Cache\MemcacheCache'
autowiring_types: 'Doctrine\Common\Cache\CacheProvider'
calls:
- [ setMemcache, ["#memcache"] ]
- [ setNamespace, ["%memcache.prefix%"] ]
So, I've created a service (memcache_cache) and defined a autowiring_types for it. Every time the abstract CacheProvider is injected the memcache_cache should be used.
But it was easy as this is not a kernel service. I've defined it myself in a services.yml, whereas I don't have an access to extends the templating.engine.delegating service in a way to add the autowiring_types: 'Symfony\Component\Templating\EngineInterface'

Related

Symfony 4.4 service autowired in controller's constructor doesn't get call from service definition (setContainer)

Foreword: please disregard injecting the whole container and other unclean things, I just wanted to show not-working example, this is before refactor.
I have a service defined in YAML:
app.service.my_service:
class: App\Services\MyService
public: true
calls:
- [setContainer, ['#service_container']]
part of my service:
class MyService implements ContainerAwareInterface
{
/** #var ContainerInterface */
private $container;
/** #var EntityManagerInterface */
private $em;
public function setContainer(?ContainerInterface $container = null)
{
$this->container = $container;
$this->em = $container->get('doctrine')->getManager();
}
Then I have a controller, which have that service autowired in constructor, and not instantiaded from container:
class MyController
{
/**
* #var MyService
*/
private $my_service;
function __construct(MyService $my_service) {
$this->my_service = $my_service;
}
While it actually autowires service itself, it completely ignores the setContainer call, so I end up with empty container.
I wanted to avoid calling $this->get('app.service.my_service') everywhere and I can't simply call it once in controller's contructor, or call setContainer in the constructor on autowired service, because at that time container is empty.
Any chances I can do it in a clean way?
Since you already know injecting the container is a bad idea then I guess I'll spare you the lecture. I have not encountered this particular issue but you can try using the little known '#required' container directive and see if it helps.
/** #required */
public function setContainer(ContainerInterface $container)
{
$this->container = $container;
$this->em = $container->get('doctrine.orm.default_entity_manager);
}
This is more of a guess than an answer but it is easy to try.
Update: It worked. Cool.
I just wanted to add that, in most cases, dependencies should be injected into the constructor. This avoids having partially initialized services. On the other hand, as long as you are generating your services via the container then it is not really a problem.
I find it to be very useful in traits. For example:
trait RouterTrait
{
private RouterInterface $router;
/** #required */
public function setRouter(RouterInterface $router)
{
$this->router = isset($this->router) ? $this->router: $router;
}
// These are just copied from AbstractController
protected function generateUrl(
return $this->router->generate(...
protected function redirectToRoute(
#
class SomeService
use RouterTrait;
public function someMethod()
$this->generateUrl('''
So if a service needs to do some routing then they just need to use the router trait and the router is automatically injected. Saves adding boilerplate code to the service's constructor. I do put a guard around setRouter so it can only be effectively called once which eliminates another concern about using setters for dependencies.
Update #2:
The problem with the original code is that the MyService was defined with a service is of app.service.my_service and not the class name. Autowire actually generated a second MyService and injected it since autowire looks for service ids that match the class name. Changing the service definition to:
App\Services\MyService:
public: true
calls:
- [setContainer, ['#service_container']]
Would have also worked. Or explicitly injecting app.service.my_service into the controller.

Tagged services and dependency injection

In one of my services, I need to access all services that implement an interface, so I defined in services.yaml:
_instanceof:
App\ReportPlaceholder\ReportPlaceholderInterface:
tags: ['app.reportplaceholder']
report_helper:
class: App\Service\ReportHelper
public: true
arguments:
- $placeholders: !tagged app.reportplaceholder
and the corresponding class reads:
class ReportHelper
{
/**
* #var EntityManagerInterface
*/
private $entityManager;
/**
* #var ReportPlaceholderInterface[]
*/
public $placeholders;
/**
* #var DataEvaluator
*/
private $dataEvaluator;
public function __construct(EntityManagerInterface $entityManager,
DataEvaluator $dataEvaluator,
iterable $placeholders = [])
{
$this->entityManager = $entityManager;
$this->dataEvaluator = $dataEvaluator;
dump([debug_backtrace(), $this->placeholders = $placeholders]);
}
}
The weird thing is I am always getting two dump messages !!!
It seems like the ReportHelper service is created twice, once with and once without the tagged services.
Looking at the call stack I see that once (with empty iterator) it is /var/www/symfony/var/cache/dev/ContainerAbdE4g8/getReportHelper2Service.php (notice the "2"), and the second constructor call comes from an event dispatcher /var/www/symfony/vendor/symfony/event-dispatcher/EventDispatcher.php
Found the solution myself, for those who are interested.
The error is connected with a misunderstandin I had of symfony services and their names.
So if you define a service with name like I did, eg
report_helper:
class: App\Service\ReportHelper
arguments:
$xyz: '#other_service'
and then use standard dependency injection
class X {
public function __construct(ReportHelper $rh){}
}
then these are two different services!!!
Because, there is implicitly via autowiring a second default service with name App\Service\ReportHelper instantiated, that is actually passed to X in the latter case!
So, by exchanging the class: in my service definition by the keyword alias:, only one instance is created with two ids.
report_helper:
alias: App\Service\ReportHelper
arguments:
$xyz: '#other_service'

Subscriber call twice symfony

I use FOSUserEvents after submit form but the subscriber call twice.
In this way my captcha is valid the first time and not valid the second
this is my code
<?php
namespace AppBundle\EventListener;
class CaptchaSubscriber implements EventSubscriberInterface
{
private $router;
private $requestStack;
private $templating;
/**
* RedirectAfterRegistrationSubscriber constructor.
*/
public function __construct(RouterInterface $router, RequestStack $requestStack, \Twig_Environment $templating)
{
$this->router = $router;
$this->requestStack = $requestStack;
$this->templating = $templating;
}
public function onRegistrationInit(GetResponseUserEvent $event)
{
if ($this->requestStack->getMasterRequest()->isMethod('post')) {
...handle captcha...
}
}
public static function getSubscribedEvents()
{
return [
FOSUserEvents::REGISTRATION_INITIALIZE => 'onRegistrationInit'
];
}
}
my symfony is 3.3
UPDATE
I added
$event->stopPropagation();
with this snippet the code works, but i don't know if it is the best practice
In my case of symfony 4.2 it depends on the service definition if it occures or not.
My Subscriber gets registered twice if I define the service like this:
# oauth process listener
app.subscriber.oauth:
class: App\EventListenerSubscriber\OauthSubscriber
arguments: ['#session', '#router', '#security.token_storage', '#event_dispatcher', '#app.entity_manager.user', '#app.fos_user.mailer.twig_swift']
tags:
- { name: kernel.event_subscriber }
But it gets registerd only once if I chenge the definition to this:
# oauth process listener
App\EventListenerSubscriber\OauthSubscriber:
arguments: ['#session', '#router', '#security.token_storage', '#event_dispatcher', '#app.entity_manager.user', '#app.fos_user.mailer.twig_swift']
tags:
- { name: kernel.event_subscriber }
I posted a bug report on github and got immediately an answer, that in newer symfony versions event listeners and subscribers get registered automatically with their class name as key (under some default conditions - must read on that topic).
So there is no need to register them explicitely as services.
I we do this anyway, but using an arbitrary key instead of class name, there will be two services.
If you are using Autowiring/Autoconfiguration, it's possible that you've added the subscriber service you show above, twice. I've done it myself when I first added the autowiring, but I also had the subscriber listed explicitly in the configuration as well.
You can see what events are registered (and check if any are registered more than once to perform the same service/action) with:
bin/console debug:event-dispatcher

Symfony 3 service configuration providing dynamic configuration

I would like to know how I could avoid setter injection under the following use case.
I have a collection of tagged services:
interface EmailFormatter
class CvEmailFormatter implements EmailFormatter
class RegistrationEmailFormatter implements EmailFormatter
class LostPasswordEmailFormatter implements EmailFormatter
I use a CompilerPass to inject these into a mailer service by invoking its addEmailFormatter() method.
Each EmailFormatter instance requires some configuration that must be retrieved at run time from the current user:
/**
* #var FormatterConfig[]
* #ORM\OneToMany(targetEntity="AppBundle\Entity\FormatterConfig", mappedBy="user", cascade={"persist"}, orphanRemoval=true)
*/
protected $formatterConfigs;
Right now I am checking to see if the user has created a configuration for a particular EmailFormatter inside the mailer service's addEmailFormatter() method. If they have then setConfig() injects the configuration object. If no config exists yet the EmailFormatter does not get added at all.
public function addEmailFormatter(EmailFormatter $formatter, $alias)
{
$formatterConfig = $this->user->getFormatterConfig($alias);
if (null !== $formatterConfig) {
$this->formatters[$alias] = $formatter;
$formatter->setConfig($formatterConfig);
}
}
Could this be a legitimate use case given I am working with a mutable collection of services?

How to inject Doctrine Entity Manager into Symfony 4 Service

I have a controller
use Doctrine\ORM\EntityManagerInterface:
class ExampleController{
public function someFunction(ExampleService $injectedService){
$injectedService->serviceFunction();
}
}
With a Service
use Doctrine\ORM\EntityManagerInterface;
class ExampleService{
public function __construct(EntityManagerInterface $em){
...
}
}
However, calls to someFunction() fail due to 0 parameters being passed (the EntityManagerInterface is not being injected). I am attempting to use the EntityManager from the Service. Autowiring is on. I've tried the solutions for Symfony3 but they don't seem to work unless I'm missing something.
Edit: Here is my services.yaml:
services:
_defaults:
autowire: true
autoconfigure: true
public: false
App\:
resource: '../src/*'
exclude: '../src/{Entity,Migrations,Tests,Kernel.php}'
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
Use only in Symfony 4.
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\Name; //if you use entity for example Name
class ExampleService{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
function newName($code) // for example with a entity
{
$name = new Name();
$name->setCode($code); // use setter for entity
$this->em->persist($name);
$this->em->flush();
}
}
I know it's an old post, but just in case somebody struggles with this, there's a typo in the use statment:
use Doctrine\ORM\EntityManagerInterface: //<- see that's a colon, not a semicolon
Agree with Yarimadam. Service container, dependency injection and autowiring is not a story about injecting into methods. Dependencies injected into objects we are calling "services".
When application is up, service container is built injecting one services into another ones via class constructor or "set" method invocation.
Your ExampleController::someFunction is intended to be called only by you, so only way how this method will receive $injectedService as an argument, is that you will pass it evidently. This is the wrong way.
A classic symfony service with autowiring uses constructor injection method to inject dependencies. In your case, you don't have a constructor.
You may consider to add a constructor method and set dependency to a private class property. And use accordingly.
Or you can utilize setter injection.
Service Configuration:
services:
app.example_controller:
class: Your\Namespace\ExampleController
calls:
- [setExampleService, ['#exampleService']]
Controller Class:
class ExampleController
{
private $exampleService;
public function someFunction() {
$this->exampleService->serviceFunction();
}
public function setExampleService(ExampleService $exampleService) {
$this->exampleService = $exampleService;
}
}

Resources