Tagged services and dependency injection - symfony

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'

Related

Use Action class instead of Controller in Symfony

I am adherent of Action Class approach using instead of Controller. The explanation is very simple: very often Controller includes many actions, when following the Dependency Injection principle we must pass all required dependencies to a constructor and this makes a situation when the Controller has a huge number of dependencies, but in the certain moment of time (e.g. request) we use only some dependencies. It's hard to maintain and test that spaghetti code.
To clarify, I've already used to work with that approach in Zend Framework 2, but there it's named Middleware. I've found something similar in API-Platform, where they also use Action class instead of Controller, but the problem is that I don't know how to cook it.
UPD:
How can I obtain the next Action Class and replace standard Controller and which configuration I should add in regular Symfony project?
<?php
declare(strict_types=1);
namespace App\Action\Product;
use App\Entity\Product;
use Doctrine\ORM\EntityManager;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class SoftDeleteAction
{
/**
* #var EntityManager
*/
private $entityManager;
/**
* #param EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* #Route(
* name="app_product_delete",
* path="products/{id}/delete"
* )
*
* #Method("DELETE")
*
* #param Product $product
*
* #return Response
*/
public function __invoke(Request $request, $id): Response
{
$product = $this->entityManager->find(Product::class, $id);
$product->delete();
$this->entityManager->flush();
return new Response('', 204);
}
}
The question is a bit vague for stackoverflow though it's also a bit interesting. So here are some configure details.
Start with an out of the box S4 skeleton project:
symfony new --version=lts s4api
cd s4api
bin/console --version # 4.4.11
composer require orm-pack
Add the SoftDeleteAction
namespace App\Action\Product;
class SoftDeleteAction
{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function __invoke(Request $request, int $id) : Response
{
return new Response('Product ' . $id);
}
}
And define the route:
# config/routes.yaml
app_product_delete:
path: /products/{id}/delete
controller: App\Action\Product\SoftDeleteAction
At this point the wiring is almost complete. If you go to the url you get:
The controller for URI "/products/42/delete" is not callable:
The reason is that services are private by default. Normally you would extend from AbstractController which takes care of making the service public but in this case the quickest approach is to just tag the action as a controller:
# config/services.yaml
App\Action\Product\SoftDeleteAction:
tags: ['controller.service_arguments']
At this point you should have a working wired up action.
There of course many variations and a few more details. You will want to restrict the route to POST or fake DELETE.
You might also consider adding an empty ControllerServiceArgumentsInterface and then using the services instanceof functionality to apply the controller tag so you no longer need to manually define your controller services.
But this should be enough to get you started.
The approach I was trying to implement is named as ADR pattern (Action-Domain-Responder) and Symfony has already supported this started from 3.3 version. You can refer to it as Invokable Controllers.
From official docs:
Controllers can also define a single action using the __invoke() method, which is a common practice when following the ADR pattern (Action-Domain-Responder):
// src/Controller/Hello.php
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* #Route("/hello/{name}", name="hello")
*/
class Hello
{
public function __invoke($name = 'World')
{
return new Response(sprintf('Hello %s!', $name));
}
}

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.

Symfony autowiring interface to a real service

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'

Symfony Customrepository inject translator

I am not sure if this is even best practice or possible at all.
So I have a situation where I use DataTables and I need to change a boolean value to text in order to display true/false instead of numbers. But I also need to do that in different languages.
Since I need this in several places in the app i was thinking that I should make an app specific Repository class that extends EntityRepository and use it as extended class for the repositories I am building. For this i want to inject translator object in in order to translate some keys, but translation is never set:
CustomRepository class
class CustomRepository extends EntityRepository
{
/**
* #var Translator
*/
protected $translator;
/**
* #param Translator $translator
*/
public function setTranslator(Translator $translator)
{
$this->translator = $translator; //*******this one is not set...
}
/**
* Replace bool results into string values
*
* #param $aRes
* #param $sField
*
* #return mixed
*/
protected function _replaceBoolToStringResult(&$aRes, $sField)
{
if (1 == $aRes[$sField]) {
$aRes[$sField] = str_replace('1', $this->translator->trans('site.true'), $aRes[$sField]);
} else {
$aRes[$sField] = str_replace('0', $this->translator->trans('site.false'), $aRes[$sField]);
}
return $aRes;
}
}
services.yml
app.custom.repository:
class: App\CommonBundle\Repository\CustomRepository
#should i call here all the constructor vars from EntityRepository class as arguments?
calls:
- [setTranslator, ["#translator.default"]]
Repository with custom DQL
class SettingsRepository extends CustomRepository
{
public function findOverviewSettingsAsJson()
{
$aResult = $this->createQueryBuilder('s')
->select('s.identifier, s.type, s.isActive')
->getQuery()
->getScalarResult();
// ******** HERE I WANT TO USE _replaceBoolToStringResult
return json_encode($aResult);
}
}
I found this article by Matthias to be useful on this issue. (I know link only answers are frowned on...)
You must use the factory pattern when you use a repository as a service.
See possible duplicates :
Symfony 2: Creating a service from a Repository
How to inject a repository into a service in Symfony2?
Note : the syntax changed in latest SF version : http://symfony.com/doc/current/components/dependency_injection/factories.html
Edit :
You should use your repository as a service :
app.custom.repository:
class: App\CommonBundle\Repository\CustomRepository
factory: ["#doctrine.orm.entity_manager", getRepository]
arguments:
- App\CommonBundle\Entity\CustomEntity
calls:
- [setTranslator, ["#translator.default"]]
Then call this service as any other service in your code. For example from inside a controller :
$this->get('app.custom.repository')->...

How to get #request_stack service in app/console context?

I have services that require the #request_stack to fetch parameters.
Now, I want to expose certain functionality to console commands callable via ./app/console//. Yet in the context of an ./app/console, there is no #request_stack, yet one can input arguments.
In order to resolve this issue, I am now creating basically two services, one basic, only waiting for the params, and one being able to use the #request_stack.
Yet I dislike that there are two ways for the data to be fetched in the request-based flow and via the app/console.
Hence I am wondering, as I am simply want the data that comes per default via the request to also be able to be inputted via console arguments:
Can I setup a custom request_stack to simulate a request during a console command?
When I was investigating this issue, I stumbled across request stack push method, where a warning was already in place in the doc block:
/**
* Pushes a Request on the stack.
*
* This method should generally not be called directly as the stack
* management should be taken care of by the application itself.
*/
public function push(Request $request)
{
$this->requests[] = $request;
}
So while it would be possible to do it this way, I decided against the approach of my original question and to refactor my application instead.
I have created a context value object which just holds the parameter data:
/**
* Context
**/
class Context
{
/**
* #var string
*/
private $countryCode;
/**
* Context constructor.
* #param string $countryCode
*/
public function __construct($countryCode = '')
{
$this->countryCode = $countryCode;
}
/**
* #return string
*/
public function getCountryCode()
{
return $this->countryCode;
}
}
And a ContextFactory that creates the context with by the request stack:
class ContextFactory extends RequestAwareService
{
/**
* ContextFactory constructor.
* #param RequestStack $stack
*/
public function __construct(RequestStack $stack)
{
$this->setRequestStack($stack);
}
/**
* #return Context
*/
public function create()
{
return new Context($this->request->getCountryCode());
}
}
(The RequestAwareService is just a helper class to more easily parse the request.)
I then defined the services in my Bundle services.yml:
context.factory:
class: Kopernikuis\MyBundle\Service\Config\ContextFactory
arguments:
- '#request_stack'
context:
class: Kopernikuis\MyBundle\Service\Config\Context
factory:
- '#context.factory'
- create
Instead of injecting the #request_stack, I am now injecting my #context value object, which also had the benefit of reducing the hierarchy as now only one service parses the request_stack once, and I also noticed that certain functionality got much simpler as I could remove parameters from method calls, as they were all provided by the context object instead.
And in my custom commands, I can just replace my context
protected function execute(InputInterface $input, OutputInterface $output)
{
// #todo: use variable from InputInterface
$context = new Context('fnordfoo');
$this->getContainer()->set('context', $context);
}
With the newly gained knowledge, I strongly disagree with my original intent of trying to manually set the #request_stack.
Refactoring the code base to not necessarily require the #request_stack was a more solid choice.

Resources