Symfony 4. How to access the service from controller without dependency injection?

symfony inject service into controller
symfony get container in controller
symfony get container in service
symfony 4 get parameter in controller
symfony controller as service
symfony controller dependency injection
symfony/dependency injection
symfony 4 interface

I have several services: DieselCaseService, CarloanCaseService LvCaseService.

The controller decides which of services to get.

$type = $quickCheck["type"];
/**
 * @var $caseService \App\Service\Cases\CaseInterface
*/
$type = 'diesel; // for test purposes

$caseService = $this->get('case_service.' . $type);

The service aliases are declared like this:

case_service.diesel:
    alias: App\Service\Cases\DieselCaseService
    public: true
class DieselCaseService implements CaseInterface 
{
.
.
.
}

If I try to get the DieselCaseService, I get an error

Service "case_service.diesel" not found: even though it exists in the app's container, the container inside "App\Controller\Api\AccountController" is a smaller service locator that only knows about the "doctrine", "form.factory", "fos_rest.view_handler", "http_kernel", "parameter_bag", "request_stack", "router", "security.authorization_checker", "security.csrf.token_manager", "security.token_storage", "serializer", "session", "templating" and "twig" services. Try using dependency injection instead.

What can I do? I don't want to inject all of the services to the controller

For any "multiple instances of same type by key" situation, you can use autowired array.

1. Autodiscovery Services with App\ namespace
services:
    _defaults:
        autowire: true

    App\:
        resource: ../src
2. Require autowired array in Constructor
<?php

namespace App\Controller\Api;

use App\Service\Cases\DieselCaseService

final class AccountController
{
    /**
     * @var CaseInterface[]
     */
    private $cases;

    /**
     * @param CaseInterface[] $cases
     */
    public function __construct(array $cases)
    {
        foreach ($cases as $case) {
            $this->cases[$case->getName()] = $cases;
        }
    }

    public function someAction(): void
    {
        $dieselCase = $this->cases['diesel']; // @todo maybe add validation for exisiting key
        $dieselCase->anyMethod();
    }
}
3. Register compiler pass in Kernel

The autowired array functionality is not in Symfony core. It's possible thanks to compiler passes. You can write your own or use this one:

use Symplify\PackageBuilder\DependencyInjection\CompilerPass\AutowireArrayParameterCompilerPass;

final class AppKernel extends Kernel
{
    protected function build(ContainerBuilder $containerBuilder): void
    {
        $containerBuilder->addCompilerPass(new AutowireArrayParameterCompilerPass);
    }
}

That's it! :)

I use it on all my projects and it works like a charm.

Read more in post about autowired arrays I wrote.

Service Container (Symfony Docs), [Symfony\Component\DependencyInjection\Exception\RuntimeException] Unable to autowire argument of type "Acme\TransformerInterface" for the service "� In Symfony, a controller does not need to be registered as a service. But if you’re using the default services.yaml configuration, your controllers are already registered as services. This means you can use dependency injection like any other normal service.

Some rainy Sunday afternoon code. This question is basically a duplicate of several other questions but there are enough moving parts that I suppose it is worthwhile to provide some specific solutions.

Start by defining the cases just to make sure we are all on the same page:

interface CaseInterface { }
class DieselCaseService implements CaseInterface {}
class CarloanCaseService implements CaseInterface{}

The easiest way to answer the question is to add the case services directly to the controller's container:

class CaseController extends AbstractController
{
    public function action1()
    {
        $case = $this->get(DieselCaseService::class);

        return new Response(get_class($case));
    }
    // https://symfony.com/doc/current/service_container/service_subscribers_locators.html#including-services
    public static function getSubscribedServices()
    {
        return array_merge(parent::getSubscribedServices(), [
            // ...
            DieselCaseService::class => DieselCaseService::class,
            CarloanCaseService::class => CarloanCaseService::class,
        ]);
    }
}

The only thing I changed for your question is using the class names for service ids instead of something like 'case_service.diesel'. You can of course tweak the code to use your ids but class names are more standard. Note that there is no need for any entries in services.yaml for this to work.

There are a couple of issues with the above code which may or may not be a problem. The first is that only the one controller will have access to the case services. Which may be all you need. The second potential issue is that you need to explicitly list all your case services. Which again might be okay but it might be nice to automatically pick up services which implement the case interface.

It's not hard to do but it does require following all the steps.

Start be defining a case locator:

use Symfony\Component\DependencyInjection\ServiceLocator;

class CaseLocator extends ServiceLocator
{

}

Now we need some code to find all the case services and inject them into the locator. There are several approaches but perhaps the most straight forward is to use the Kernel class.

# src/Kernel.php
// Make the kernel a compiler pass by implementing the pass interface
class Kernel extends BaseKernel implements CompilerPassInterface 
{
    protected function build(ContainerBuilder $container)
    {
        // Tag all case interface classes for later use
        $container->registerForAutoconfiguration(CaseInterface::class)->addTag('case');
    }
    // and this is the actual compiler pass code
    public function process(ContainerBuilder $container)
    {
        // Add all the cases to the case locator
        $caseIds = [];
        foreach ($container->findTaggedServiceIds('case') as $id => $tags) {
            $caseIds[$id] = new Reference($id);
        }
        $caseLocator = $container->getDefinition(CaseLocator::class);
        $caseLocator->setArguments([$caseIds]);
    }

If you follow all of the above steps then you can inject your case locator into any controller (or other service) that happens to need it:

class CaseController extends AbstractController
{
    public function action2(CaseLocator $caseLocator)
    {
        $case = $caseLocator->get(CarloanCaseService::class);

        return new Response(get_class($case));
    }

And once again, there is no need to make any changes to your services.yaml for all this to work.

How to Define Controllers as Services (Symfony Docs), For example, suppose you want to inject the logger service, and decide to use setter-injection: namespace App\Util; class Rot13Transformer { private� The moment you start a Symfony app, your container already contains many services. These are like tools: waiting for you to take advantage of them. In your controller, you can "ask" for a service from the container by type-hinting an argument with the service's class or interface name. Want to log something?

I have created a wrapper-service

<?php
namespace App;

use Symfony\Component\DependencyInjection\ContainerInterface;

class ServiceFactory
{

    /** @var ContainerInterface */
    private $container;


    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    public function getService($alias)
    {
        return $this->container->get($alias);
    }

    /**
     * @param $alias
     * @return bool
     */
    public function hasService($alias)
    {
        return $this->container->has($alias);
    }
}

than I inject this service into controller

public function saveRegisterData(Request $request, AccountService $accountService, ServiceFactory $serviceFactory) 
{
.
.
.

    if (!$serviceFactory->hasService('case_service.'.$type)) {
        throw new \LogicException("no valid case_service found");
    }

    /**
    * @var $caseService \App\Service\Cases\CaseInterface
    */
    $caseService = $serviceFactory->getService('case_service.' . $type);

.
.
.
}

Defining Services Dependencies Automatically (Autowiring), They are used by some developers for very specific use cases, such as DDD (do. Defining controllers as services is not officially recommended by Symfony. the controllers because they don't have automatic access to the services or to the inject every single dependency needed by them;; The code of the controllers is � Our controller is a beautiful, boring service. I love boring things. This means that, if we need to access some other service from here, we need to "inject" it - either through the constructor or by autowiring it as an argument to the controller method - a special superpower of controllers that we'll talk about soon.

How to Define Controllers as Services (Symfony 3.0 Docs), Types of Injection: Making a class's dependencies explicit and requiring that they be injected You can specify what service you would like to inject into this in the service cannot work without it then injecting it via the constructor ensures it is present These advantages do mean that constructor injection is not suitable for � The Symfony dependency injection container is really powerful and is able to manage any kind of PHP class. Don't yell at me if you don't want to use a dependency injection container in your framework. If you don't like it, don't use it. It's your framework, not mine.

Types of Injection (Symfony Docs), For an introduction to Dependency Injection and service containers see You could then get your newsletter_manager service from the container like this:. For an introduction to Dependency Injection and service containers see Service Container. Installation¶ 1 $ composer require symfony/dependency-injection Note.

The DependencyInjection Component (Symfony Docs), A service container (or dependency injection container) is simply a PHP For example, suppose you have a simple PHP class that delivers email messages. Without a service container, you must manually create the object whenever you need Symfony controller where you can access the services of the container via the� That's why in Symfony 3.4 services and aliases will be private by default. This means that even if you manage to have the container, you will not be able to get() them anymore by default. Instead, you should use regular dependency injection.

Comments
  • This is a job for service subscribers. AbstractController is already a subscriber. You can add your own services as well. Scroll until your see the controller example here. There are several other approaches to this problem so read the docs. It is possible to make a CaseServiceLocator which will automatically contain all services which implement CaseInterface.
  • Instead of config programming (very bad practise as you can see), you could add getName() method to CaseInterface. That way you could easily get to any service without bounding it to a string value you type in config.
  • Und then only inject the CaseInterface into the Action? Or have I you misunderstood? Do you have an example?
  • @olek07 Into the constructor rather. I've added a more detailed example to the answer. If something is unclear, feel free to ask in comments under it.
  • From where does Symfony know, that the parameter $cases in the constructor means to inject all of the cases (DieselCaseService, CarloanCaseService LvCaseService)? And where is declared 'diesel' to be possible to be used here $this->cases['diesel'];?
  • From the compiler pass. The name si set here $this->cases[$case->getName()] = $cases; You need to extend your interface with getName() method, that would provide the key you'll use in controller to match.
  • Do I really need to do all services public?
  • I have read, that making of all services public is a bad practice. Moreover I have an own solution. Look my answer
  • It's hidden static in your code - very easy to use, but nearly impossible to get rid of lately. I wrote about bad consequences here. "Is your solution working without public: true?" Yes, I added it by accident, it's not relevant to the code.
  • Thanks a lot! It works!!! I have a little question left. I want to get the service by their aliases, like this $caseService = $caseLocator->get('case_service.' . $type) Actually it work only with the class name $case = $caseLocator->get(CarloanCaseService::class);
  • To be perfectly honest, in my opinion, tweaking the code to use aliases is something best left to the student.
  • Injecting the full container is something to be avoided as it gives global access to all your services and can make testing a bit more challenging. Take a look at my comment in your original question. The one with three up votes. Basically you want to start with CaseServiceFactory extends ServiceLocater and move on from there.
  • If I define the service locator like this: class DoingLocator extends ServiceLocator { protected $services = [ 'bar' => 'app.service.bar', 'baz' => 'app.service.baz' ]; public function locate($string) { return $this->get($this->services[$string]); } } I get the error: Invalid definition for service "App\DoingLocator": an array of references is expected as first argument when the "container.service_locator" tag is set.