loading services within controller class - symfony

I have been recently asked to clean up our legacy symfony code and check whether performance would be gained as well while doing it.
The first thing which I had spotted is that in almost every controller the services will be loaded via
public function someAction(Request $request){
$someService = $this->get(someService::class);
... there are plenty of them
}
My question:
Would it be better to use the dependency Injection within the Controller constructor instead?
public function __construct(SomeService1 $someService1, SomeService2 ...)
the service.yml file has already the autowire:true attribute enabled.
I have previously done some research on the SO but afterwords I'm more confused and not really sure which of them is the recommendable approach.
My Symfony version is 3.3.17

If you extend the base AbstractController class, you can't access services directly from the container via $this->container->get() or $this->get(). Instead, you must use dependency injection to fetch services: most easily done by type-hinting action method arguments:
Don't use $this->get() or $this->container->get() to fetch services from the container. Instead, use dependency injection.
If you need a service in a controller, just type-hint an argument with its class (or interface) name. Symfony will automatically pass you the service you need:
use Psr\Log\LoggerInterface;
// ...
/**
* #Route("/lucky/number/{max}")
*/
public function number($max, LoggerInterface $logger)
{
$logger->info('We are logging!');
// ...
}
Since 3.3, controllers are imported separately to make sure services can be injected as action arguments even if you don't extend any base controller class:
# services.yml
services:
# ...
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
By not fetching services directly from the container, you can make your services private, which has several advantages.
There is no runtime performance impact for using any of these features. However, there is some performance impact in the dev environment. Most importantly, your container will likely be rebuilt more often when you modify your service classes.
Resources:
http://symfony.com/doc/current/best_practices/controllers.html#fetching-services
http://symfony.com/doc/current/service_container.html#fetching-and-using-services
http://symfony.com/doc/current/service_container/3.3-di-changes.html

Related

Public service is handled as private

I have a library used by two Symfony apps, this library defines a set of services that I want to be public (I want to be able to retrieve them directly through the container. When I try to access one service I have this:
The "Library\Service\DerivedServices\OneSpecificImplementation" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead.
The problem is that said service is declared public.
Basically there is:
a Library\Service\BaseService class that has two setters for the common dependencies (doctrine and a logger in this snippet);
several derived class (in the Library\Service\DerivedServices namespace), each defines a new service (with its own constructor to handle DI directly).
So here are my services definitions:
# Base: inject common dependencies
Library\Service\BaseService:
abstract: true
calls:
- [setDoctrine, ['#doctrine.orm.entity_manager']]
- [setLogger, ['#Library\Service\Logger']]
# These services are public to be retrieved directly by the container interface
Library\Service\DerivedServices\:
resource: '../vendor/company/library/src/Library/Service/DerivedServices'
public: true
autowire: true
autoconfigure: false
parent: Library\Service\BaseService
Then, the Symfony application retrieves one derived service like:
$this->get('Library\Service\DerivedServices\OneSpecificImplementation');
These didn't make any difference:
I have changed the order of the service definitions
Both apps run Symfony 4.3.3
I think this is something trivial configuration-wise, but I can't pinpoint it (and after 2 hours trying to debug why the framework compiles my service as private, I thought that someone probably had this and could probably help me).
It turns out the order of declaration of the services matter a lot. As I thought so, the problem was configuration-wise.
I had:
Library\Service\BaseService:
...
Library\Service\DerivedServices\:
...
Library\Service\:
resource: '../vendor/company/library/src/Library/Service'
The last instruction redeclared all services as private (by default).
I changed this to:
Library\Service\:
resource: '../vendor/company/library/src/Library/Service'
Library\Service\BaseService:
...
Library\Service\DerivedServices\:
...
This declared all the services private first, then redeclared them with the new declaration: use of parent + public.

Symfony - Database connection with a Repository

I'm refactoring the code in one of my Controller to put it in a service.
In the Controller the entity manager is targeting a db connection called legacy.
The problem is, I did not injected the Entity Manager in my service but just the OrderRepository.
How can I target the good db connection with the repository in my service without the Entity Manager?
OrderController
// In my Controller
$em = $this->getDoctrine()->getManager('legacy');
$em->persist($order);
$em->flush();
OrderService
// In my Service
public function __construct(OrderRepository $orderRepository)
{
$this->orderRepository = $orderRepository;
}
public function updateOrderStatus(Order $order)
{
// some code ...
$this->orderRepository->save($order);
}
Based on some comments it would appear that the basic issue is having multiple entity managers. Nowadays there is a great deal of automated functionality which works well for one entity manager but no so much for multiple managers.
You basically need to define your services manually.
# services.yaml
# assume we have
# doctrine.orm.default_entity_manager and
# doctrine.orm.legacy_entity_manager
# already defined through configuration
# define legacy repository
order_repository.legacy:
class: Whatever\Repository\OrderRepository
factory: ['doctrine.orm.legacy_entity_manager', 'getRepository']
arguments:
- 'Whatever\Entity\Order'
# define default repository
order_repository.default:
class: Whatever\Repository\OrderRepository
factory: ['doctrine.orm.default_entity_manager', 'getRepository']
arguments:
- 'Whatever\Entity\Order'
# then your service
Whatever\Service\MyService:
'#order_repository.legacy'
And you should be good to go. Note that your repository needs to extend EntityRepository and not the doctrine bundle's ServiceEntityRepository.
And if you feel this is too much work then just inject the doctrine bundle's entity manager registry and do what the ControllerTrait::getDoctrine($name) does.
You must forgot that you can always get the entity manager inside your repository just like this:
$em = $this->getEntityManager();
then you can use it normally calling persist, flush etc.
Note that the Repository class itself is agnostic to functionalities of the database access layer like insert or update so it should call entity manager to execute them.

Symfony2 Dependency Injection the "Right Way"

This is a fairly open question, i'm looking for opinion more than anything...
Coming from Laravel, Dependency Injection (DI) was blissful, if you wanted to access a method from a class, you could either inject it by type hinting in the method or constructor and you'll have instant access to it; symfony2 doesn't seem this simple from the face of it.
We've got service containers, setter injection, constructor injection by setting up a service yml where you pre-define your arguments, but this just seems dirty.
So the "Laravel Way" of dependency injection is done like this
class Listener
{
protected $mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
public function userWasAdded(User $user)
{
// Do some stuff...
$this->mailer->send('emails.welcome', ['user' => $user], function($message)
{
$message->to($user->email, $user->name)->subject('Welcome!');
});
}
}
How could I, in Symfony2, replicate Laravel's form of dependency injection, this seems like a whole cleaner way of doing things, or perhaps there is a Symfony2 way of doing things I haven't yet discovered. Any help is appreciated.
I'm not familiar with Laravel but after reading doc I believe that DI in Laravel and Symfony are quite similar after all.
From the doc:
There is no need to bind classes into the container if they do not depend on any interfaces. The container does not need to be instructed on how to build these objects, since it can automatically resolve these objects using reflection.
In this case, Symfony has similar feature starting from 2.8 version which is auto wiring
In other case (meaning that class has constructor dependencies) you need to instruct your framework how to resolve these dependencies. Only difference here is that you have to do it in different ways.
In Symfony most common way is just to use yml file:
services:
mailer:
class: Mailer
arguments: ['%mailer.transport%']
But you can do it in xml or php as well (check the doc)
$container->setParameter('mailer.transport', 'sendmail');
$container
->register('mailer', 'Mailer')
->addArgument('%mailer.transport%');
But in Laravel it is the same: you need to instruct framework how to instantiate objects
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
Which option to choose to setup container (yml, xml, php) is just a matter of taste.

Access Config Parameter without Container

What is the recommended way of accessing the global config parameters? I know you should inject just what you need, but the whole point of global parameters (in most cases) is to set application defaults. And I WANT the defaults to be universal, even if I allow passing in overrides on a function by function basis.
THE PROBLEM
I have a ViewVersion entity with a ViewVersionRepository where I have common functions for interacting with that entity.
One of these functions requires that I know a certain parameter from the config.yml (e.g. cms.save.newVersionSeconds so I know when it's been more than 30 minutes since the last save and I should create a new version record). The entity repository is not container aware though, so I can't access the config with the normal method:
$myParam = $container->getParameter('my.custom.param');
Inject From Controller
If I call this function from a controller, no problem I can get the config in the controller and inject the parameter into the repo function.
$myParam = $container->getParameter('my.custom.param');
$viewVersionRepository->myFunction($myParam)
Global Service
Or I can configure the the repo as a service:
gutensite_cms.view_version_repository:
class: Gutensite\CmsBundle\Entity\View\ViewVersionRepository
factory_service: 'doctrine.orm.cms_entity_manager'
factory_method: 'getRepository'
calls:
- [setLimit, ['%cms.limit.list.admin%']]
- [setNewVersionSeconds, ['%cms.save.newVersionSeconds%']]
And then set the parameter with setter injection via the calls arguments in the services.yml. And then load the service:
$viewVersionRepository = $this->container->get('gutensite_cms.view_version_repository');
Complication
However... I have a situation where I'm loading this viewVersionRepository within a doctrine onFlush event listener. And there I don't have access to the container (natively), so I can't actually load the global service. I have access to the entity though so I was loading the repo via:
$repo = $em->getRepository('GutensiteCmsBundle:View\ViewVersion);
But when you load it that way, it's not loaded as a service so the setter injection doesn't happen...
ONE SOLUTION
So I could pass the container into the global onFlush event listener service, so that I can load the ViewVersionRepository as a service (and for that matter have the container to the load the parameters if I wanted).
gutensite_cms.listener.is_versionable:
class: Gutensite\CmsBundle\EventListener\IsVersionableListener
#only pass in the services we need
arguments: [ "#gutensite_cms.entity_helper" ]
calls:
- [setContainer, ["#service_container"]]
tags:
- { name: doctrine.event_listener, event: onFlush }
But I'm told over and over and over again* to **ONLY inject the parameters you need. And to avoid injecting the entire container. This onFlush event is really quite dynamic, loading the corresponding onFlush event in any entity's repository that has an onFlush method. So in one entity repository the custom function may need access to one parameter, but in another entity repository the may need access another. So I can't really pass in just one parameter.
And it seems to me that passing the entire container makes your application far more coupled than if you were to just used the vilified $GLOBALS or CONSTANT for your global default config. So surely there is a better way to avoid both these evils. Not to mention you should NEVER inject the container into an entity repository.
So isn't there some way to access the global config parameters WITHOUT injecting the entire blessed container? Does no one EVER need global config parameters in an entity repository? Sure I could move this function to another service... but why? This is one of the basic functions of this entity, e.g. it determines if it should be versioned or not.

How can I access a service outside of a controller with Symfony2?

I'm building a site that relies quite heavily on a third party API so I thought it would make sense to package up the API wrapper as a service, however I'm starting to find instances where it would be useful to have access to it outside of a controller such as in an entity repository.
Also related to that is it would be useful to be able to get access to config values outside of a controller (again such as in an entity repository).
Can anyone tell me if this is possible and if not is there a suggested approach to doing this kind of thing?
thanks for any help
The Symfony distribution relies heavily on dependency injection. This means that usually, dependencies are injected directly into your object via the constructor, the setters or via other means (like reflection over properties). Your API wrapper service is then a dependency for other objects of your application.
That being said, it would be rather difficult to inject this service in an entity repository's constructor because it already requires some other parameters and I think it would not be possible to inject them because of the way we request the repository for an entity.
What you could do is to create another service which will be responsible of doing the work you were about to do in the entity repository. This way, you will be able to inject the entity manager, which will be used to retrieve the entity repository, you custom service and also another service holding your configuration values (There are other ways to share configuration values).
In my use case, I use a Facebook helper service that wraps Facebook API calls. This service is then injected where I need it. My entity repository is only responsible of doing database calls so it receives only the arguments it needs and not the whole dependency. Thus, it will not receive the helper but rather only the arguments needed to do a request, for example, a Facebook user id. In my opinion, this is the way to do it since I think the entity repository should not have dependencies on such helper objects.
Here a small example using YAML as the configuration:
# app/config/config.yml
services:
yourapp.configuration_container:
class: Application/AcmeBundle/Common/ConfigurationContainer
# You could inject configurations here
yourapp.api_wrapper:
class: Application/AcmeBundle/Service/ApiWrapperService
# Inject other arguments if needed and update constructor in consequence
yourapp.data_access:
class: Application/AcmeBundle/Data/Access/DatabaseAccessService
arguments:
entityManager: "#doctrine.orm.entity_manager"
apiWrapperService: "#yourapp.api_wrapper"
configuration: "#yourapp.configuration_container"
# Application/AcmeBundle/Common/ConfigurationContainer.php
public ConfigurationContainer
{
public function __construct()
{
// Initialize your configuration values or inject them in the constructor
}
}
# Application/AcmeBundle/Service/ApiWrapperService.php
public ApiWrapperService
{
public function __construct()
{
// Do some stuff
}
}
# Application/AcmeBundle/Data/Access/DatabaseAccessService.php
public DatabaseAccessService
{
public function __construct(EntityManager $entityManager, ApiWrapperService $apiWrapperService, ConfigurationContainer $configuration)
{
...
}
}
The at sign (#) in the config.yml file means that Symfony should inject another service ,having the id defined after the at sign, and not a simple string. For the configuration values, as I said previously, there is other means to achieve the same goal like using parameters or a bundle extension. With a bundle extension, you could define the configuration values directly into the config.yml and your bundle would read them.
In conclusion, this should give you the general idea of injecting services. Here a small list of documentation on the subject. Alot of links use the XML service definition instead of the YAML definition but you should be able to understand them quite easily.
Symfony Official DI
Fabien Potencier's articles on DI
Richard Miller's articles on DI (Check in his blog for the other DI articles)
Take note that the configuration I'm giving is working for Beta1 of Symfony2. I didn't update yet to Beta2 so there could be some stuff not working as they are in the Beta2 version.
I hope this will help you defining a final solution to your problem. Don't hesitate to ask other questions if you want clarifications or anything else.
Regards,
Matt
I would wrap this kind of behavior in a Symfony service(like a manager).
i would not inject any parameters or logic into the entity repositories, as they should mainly be used to fetch data using object manager queries.
I would put the logic in the services and if the service , require a database access it will call the entity repository to fetch data.

Resources