Access services inside a regular class - php

I have a regular class in my Symfony2 project:
class RangeColumn extends Column{
//...
}
Now inside this class is a render function, in which I'd like to use Twig or the Translation Service of Symfony2 to render a specific template. How do I access this services in a proper way?

Code example:
<?php
class MyRegularClass
{
private $translator;
public function __construct(Translator $translator)
{
$this->translator = $translator;
}
public function myFunction()
{
$this->translator->trans('sentence_to_translate');
}
}
And if you want your class to become a service:
In your services.yml file located in your bundle,
parameters:
my_regular_class.class: Vendor\MyBundle\Classes\MyRegularClass
services:
mybundle.classes.my_regular_class:
class: %my_regular_class.class%
arguments: [#translator]
For more details, see the chapter about the Symfony2 Service Container

Use dependency injection. It's a really simple concept.
You should simply pass (inject) needed services to your class.
If dependencies are obligatory pass them in a constructor. If they're optional use setters.
You might go further and delegate construction of your class to the dependency injection container (make a service out of it).
Services are no different from your "regular" class. It's just that their construction is delegated to the container.

Related

How to access not-injected services directly on Symfony 4+?

I'm trying to update Symfony 2.8 to Symfony 4 and I am having serious problems with the Services Injection.
I'm looking the new way to use Services inside Controllers, with auto-wiring:
use App\Service\AuxiliarService;
class DefaultController extends AbstractController
{
public function index(AuxiliarService $service)
{
$var = $service->MyFunction();
....
This way works fine, but I dislike the explicit way to refer MyService as a parameter of the function. This way I don't even need to register the Service in the services.yaml
Is there any way to use Services as in Symfony 2.8:
class DefaultController extends Controller
{
public function index()
{
$var = $this->get('AuxiliarService')->MyFunction(); /*Doesn't need to be explicit indicate before*/
....
With the services.yaml
services:
auxiliar_service:
class: AppBundle\Services\AuxiliarService
arguments:
entityManager: "#doctrine.orm.entity_manager"
container: "#service_container" #I need to call services inside the service
This way I don't need to indicate the Service as a parameter in the function of the Controller. In some cases, inside a Service, I need to call more than 10 services depends on the data, so indicate them as a parameter in the function is annoying.
Another doubt in Symfony 4, is how to call a Service inside another Service without pass it as an argument or parameter. It used to be possible by injecting the service container to be able to call a service inside a service:
$this->container->get('anotherService')
In Symfony 4, I think it is more expensive (in code) use Service because you have to explicitely indicate them when you are going to use them.
tldr; you can achieve that by using Service Subscribers & Locators.
In your controller:
use App\Service\AuxiliarService;
class DefaultController extends AbstractController
{
public function index(AuxiliarService $service)
{
$var = $service->MyFunction();
}
public static function getSubscribedServices()
{
return array_merge(parent::getSubscribedServices(), [
// services you want to access through $this->get()
'auxiliar_service' => AuxiliarService:class,
]);
}
// rest of the implementation
}
If your service needs to implement a similar pattern, you'll need to implement ServiceSubscriberInterface (AbstractController, that you are extending for your controller, already does that for you).
class AuxiliaryService implements ServiceSubscriberInterface
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
protected function has(string $id): bool
{
return $this->container->has($id);
}
protected function get(string $id)
{
return $this->container->get($id);
}
public static function getSubscribedServices()
{
return [
// array_merge is not necessary here, because we are not extending another class.
'logger' => LoggerInterface::class,
'service2' => AnotherService::class,
'service3' => AndMore::class
];
}
}
That being said, you are very probably not doing things right if you want to continue this way
Before Symfony 4+ you could do $this->get('service') because these controllers all had access to the container. Passing the dependency container around for this it is an anti-pattern, and shouldn't be done.
If you do not declare your dependencies, your dependencies are hidden. Users of the class do not know what it uses, and it's easier to break the system by changing the behaviour of one of the hidden dependencies.
Furthermore, with Symfony providing auto-wiring and a compiled container; dependency injection is both easier to implement and faster to execute.
That you are having trouble with implementing this probably reveals deeper issues with your code in general, and you should do some work on segregating the responsibilities of your classes. The fact that one service may depend on that many other services which you can't even know until runtime it's a very strong smell that the concerns are not well separated.
Try to adapt to the changes, it will do your application and yourself good in the long term (even if brings a small amount of pain right now).

How does Symfony injects its DI Container inside every class that extends the Controller one?

for someone it might seem stupid, anyway I am new to the Symfony world and reading the documentation I came up with a doubt:
How does Symfony inject the DI Container instance inside of each Controller class which extends Controller in such a way that you can easily access a service from the container using the get($id) method??? Like here:
use Acme\HelloBundle\Newsletter\NewsletterManager;
// ...
public function sendNewsletterAction()
{
$mailer = $this->get('my_mailer');
$newsletter = new NewsletterManager($mailer);
// ...
}
I saw the source of the Controller class from GitHub, the Controller class in Symfony extends the abstract class ContainerAware which implements the ContainerAwareInterface interface, which has a method setContainer(ContainerInterface $container = null);
I can assume that the Controller itself attempts to set a reference to the Container instance calling setContainer inherited from the ContainerAware abstract class, but I am not sure whether I am right or not, but since I know that in Symfony2 every service (object with particular functionality) is under the supervision of the DI Container, who is responsible to inject the container to the Controller setContainer() setter? The Container itself? But how?
Thanks for the attention!
With controllers, Symfony2 uses the ControllerResolver class to determine if the Controller is an implementation of ContainerAwareInterface, and will inject the container that way. See this file (lines highlighted):
https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php#L79-L82
As for other services, you will have to manually inject the container into them via service definitions.

Symfony 2 sharing data between controllers

I have started to create a project using Symfony 2. I need to share data between all controllers.
I have added a base controller which extends symfony\controller and each of my controllers extends this base controller
class BaseController extends Controller
class HomeController extends BaseController
This base controller will be used for things like assigning global twig variables ( I know I can do this in the config but some of the variables will be gotten from other config files and database ).
So I thought I could reference container since Controller is container aware, however it isn't at the point I am using the functions (from constructor).
public function __construct ()
I have seen people mention passing the container in as a parameter and mention services but I have had a look and cannot figure it out. All I want to achieve is this:
public function __construct (Container $container) {
$container->get('twig').addGlobal('foo');
}
This is a common stumbling block to Symfony 2 newbies. The controller/container question has been asked hundreds of time before so you are not alone(hint).
Why doesn't your controller constructor code work?
Start by looking under vendor/symfony...FrameworkBundle/Controller/Controller.php. Hmm. No constructor there so where the heck is the container coming from? We see that Controller extends ContainerAware. That seems promising. We look at ContainerAware (the namespace helps to find where the file is) and once again, no constructor. There is however a setContainer method so we can assume that the container is injected into the controller after the constructor is called. Quite common in a dependency injection based framework.
So now we know why the constructor code fails. The container has not yet been injected. Stupid design right? Time for a different framework? Not really. Let's face it, having to have all your controllers extend a base controller just to get some twig variables set is not really the best design.
The Symfony way to execute code before the controller action is executed is to make a controller event listener. It will look something like this:
namespace Cerad\Bundle\CoreBundle\EventListener;
use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class ModelEventListener extends ContainerAware implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(KernelEvents::CONTROLLER => array(
array('doTwig', 0), // 0 is just the priority
));
}
public function doTwig(FilterControllerEvent $event)
{
// Ignore sub requests
if (HttpKernel::MASTER_REQUEST != $event->getRequestType()) return;
$this->container->get('twig')->addGlobal('foo');
}
}
// This goes in services.yml
parameters:
cerad_core__model_event_listener__class:
Cerad\Bundle\CoreBundle\EventListener\ModelEventListener
services:
cerad_core__model_event_listener:
class: '%cerad_core__model_event_listener__class%'
calls:
- [setContainer, ['#service_container']]
tags:
- { name: kernel.event_subscriber }
So now we have the desired functionality without the need for a base controller class.
Notice also that the controller can be accessed through the event. Since the controller has been created but the action method not yet called, you could call controller methods or inject data directly into the controller. This is seldom needed. In most cases, you would add additional information to the request object which then gets injected into the controller's action method.
It's really a nice design once you get comfortable with listeners and services.
Please read carefully that question - Symfony2 passing data between bundles & controllers, try to use code included in it.
You can use service to solve your problem, for example.
If you look at the Controller class you'll se the following:
class Controller extends ContainerAware
This means you can retrieve twig from the container as simple as this:
$twig = $this->get('twig');
But I would recommend you to use custom twig extension in your case.

How to get root dir in Symfony2

I'm trying to get the root dir in symfony2.
If I use:
$this->get('kernel')->getRootDir();
I get this error:
FatalErrorException: Error: Call to undefined method Test\Component\ClassLoader\DebugClassLoader::get()
How can I fix this?
Edit, seeing as this post has garnered so much attention and mine is at the top, the best way to get the root directory is to pass it in to your class as a constructor argument. You would use services.yml to do this, and in arguments:
serviceName:
class: Name\Of\Your\Service
arguments: %kernel.root_dir%
Then, the following code will have the root directory given to it when the framework instantiates it:
namespace Name\Of\Your;
class Service
{
public function __construct($rootDir)
{
// $rootDir is the root directory passed in for you
}
}
The rest of the answer below is the old, poor way of doing it without using Dependency Injection.
I want to make everyone aware that this is the Service Locator, which is an anti-pattern. Any developer should be able to see what a class, or controller, requires to function from the method signature only. Injecting a whole "container" is very generic, hard to debug and isn't the best way of doing things. You should use a Dependency Injection Container that allows you to inject specifically what you want anywhere in your application. Be specific. Check out a seriously awesome recursively instantiating dependency injection container called Auryn. Where your framework resolves your controller / action, place it there and use the container to create the controller and run the method instead. Boom! Instant SOLID code.
You're correct, the service container is accessed using $this->get('service').
However, in order to use $this->get(), you're going to need access to the get() method.
Controller Access
You gain access to this, and many other handy methods, by making sure your controller extends the base controller class that Symfony uses.
Make sure you're referencing the correct Controller base class:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class HelloController extends Controller
{
/** The Kernel should now be accessible via the container **/
$root = $this->get('kernel')->getRootDir();
}
Service Access
If you want to access the container from a service, you're going to have to define your controller as a service. You can find more information in this post, this post and this post about how to do this. Another useful link. Either way, you now know what to look for. This post may also be useful:
use Symfony\Component\DependencyInjection\ContainerInterface;
class MyClass
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function doWhatever()
{
/** Your container is now in $this->container **/
$root = $this->container->get('kernel')->getRootDir();
}
}
In your config.yml, define your new type:
myclass:
class: ...\MyClass
arguments: ["#service_container"]
You can read more about the service container in the docs.
The parameter kernel.root_dir points to the app directory. Normally to get to the root directory, I user kernel.root_dir/../
So in controller you can use $this->container->getParameter('kernel.root_dir')."/../"
In service definition you can use:
my_service:
class: \Path\to\class
arguments: [%kernel.root_dir%/../]
The best option is to declare tour class as a service in your services.yml file:
services:
myclass:
class: Your\Class\Namespace\MyClass
arguments: ["#service_container"]
and adapt yhe constructor of you class:
use Symfony\Component\DependencyInjection\ContainerInterface
class MyClass
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
}

Symfony2 global functions

For example i have algorithmic function, which calculates specific hash-code. Function itself is 300+ lines of code. I need to use that functions many times in many different controllers in my bundle. Where can i store my calculate_hash() to use it in my bundle ? Can i access it from other bundles ?
Can i also write global calculate_hash() which have access to entity manager ?
Didn't find my answer here.
In the Symfony2 world, this is clearly belonging to a service. Services are in fact normal classes that are tied to the dependency injection container. You can inject them the dependencies you need. For example, say your class where the function calculate_hash is located is AlgorithmicHelper. The service holds "global" functions. You define your class something like this:
namespace Acme\AcmeBundle\Helper;
// Correct use statements here ...
class AlgorithmicHelper {
private $entityManager;
public function __construct(EntityManager $entityManager) {
$this->entityManager = $entityManager;
}
public function calculate_hash() {
// Do what you need, $this->entityManager holds a reference to your entity manager
}
}
This class then needs to be made aware to symfony dependecy container. For this, you define you service in the app/config/config.yml files by adding a service section like this:
services:
acme.helper.algorithmic:
class: Acme\AcmeBundle\Helper\AlgorithmicHelper
arguments:
entityManager: "#doctrine.orm.entity_manager"
Just below the service, is the service id. It is used to retrieve your service in the controllers for example. After, you specify the class of the service and then, the arguments to pass to the constructor of the class. The # notation means pass a reference to the service with id doctrine.orm.entity_manager.
Then, in your controller, you do something like this to retrieve the service and used it:
$helper = $this->get('acme.helper.algorithmic');
$helper-> calculate_hash();
Note that the result of the call to $this->get('acme.helper.algorithmic') will always return the same instance of the helper. This means that, by default, service are unique. It is like having a singleton class.
For further details, I invite you to read the Symfony2 book. Check those links also
The service container section from Symfony2 book.
An answer I gave on accesing service outside controllers, here.
Hope it helps.
Regards,
Matt
Braian in comment asked for Symfony 3 answer, so here is one Symfony 3.3 (released May 2017):
1. The original class remains the same
namespace Acme\AcmeBundle\Helper;
use Doctrine\ORM\EntityManager;
final class AlgorithmicHelper
{
/**
* #var EntityManager
*/
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function calculateHash()
{
// Do what you need, $this->entityManager holds a reference to your entity manager
}
}
2. Service registration is much simpler
# app/config/services.yml
services:
_defaults: autowire # this enabled constructor autowiring for all registered services
Acme\AcmeBundle\Helper\AlgorithmicHelper: ~
3. Use constructor injection to get the service
use Acme\AcmeBundle\Helper\AlgorithmicHelper;
class SomeController
{
/**
* #var AlgorithmicHelper
*/
private $algorithmicHelper;
public function __construct(AlgorithmicHelper $algorithmicHelper)
{
$this->algorithmicHelper = $algorithmicHelper;
}
public function someAction()
{
// some code
$hash = $this->algorithmicHelper->calculateHash();
// some code
}
}
You can read about Symfony 3.3 dependency injection (in this case registering services in config and using it in controller) news in these 2 posts:
https://www.tomasvotruba.cz/blog/2017/05/07/how-to-refactor-to-new-dependency-injection-features-in-symfony-3-3/
https://symfony.com/blog/the-new-symfony-3-3-service-configuration-changes-explained

Categories