4

I'm refactoring old application in PHP.

I'm trying to use Symfony dependency injection component to inject services into controllers (or other services), but I don't know how to achieve this, because symphony documentation is more prepared for using framework, than framework components.

I already have my own Kernel, Container that contains all services and controllers (controllers are registered as services already). My controllers extending AbstractController from symfony/frameworkbundle. So the only thing I can do now is:

Get service from container by $this->container->get('service_id'), but if service in constructor will have class as parameter

public function __constructor(SomeClass $someClass)

then I'm getting this exception:

The "App\V2\Service\TestService" 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.

If I change configuration to make all services public, then:

Too few arguments to function APP\V2\Service\TestService::__construct(), 0 passed and exactly 1 expected

I prepare a gist, to have better view what I'm talking about: https://gist.github.com/miedzwin/49bac1cc1d5270d3ba1bfcf700abf864

Can someone help me a bit with DI implementation using Symfony components (not Symfony framework)? Good working example would be enough. Or just please put your remarks to my gist, I try to fix this.

Stephan Vierkant
  • 9,674
  • 8
  • 61
  • 97
miedzwin
  • 71
  • 1
  • 3
  • How does `APP\V2\Service\T estService` look like? Where do you register it? – Tomas Votruba Sep 09 '18 at 17:11
  • 1
    Also, you can simplify your config like this: https://gist.github.com/TomasVotruba/09af5a9452abaa91b64c15b740d74f2e/revisions#diff-445ee136220060a93c1410f85d1e7e15 – Tomas Votruba Sep 09 '18 at 17:14
  • 1
    @TomášVotruba I didn't register the service, it should be registered automatically, because `autowire: true`. I updated the gist, so you can check how TestService.php looks. – miedzwin Sep 10 '18 at 05:52
  • 1
    Okay, I made changes as you did in gist (ControllerResolver and ArgumentResolver should be public now, otherwise I cannot use them in Framework class). It looks better now, but I have another issues now: 1. If in service configuration in defaults `public: true`, then services that have in constructor scalar type arguments (string, int, array) cannot be autowired. `Cannot autowire service "APP\V2\Service\API": argument "$facebookUserId" of method "__construct()" has no type-hint, you should configure its value explicitly.` – miedzwin Sep 10 '18 at 06:45
  • 1
    2 If `public: false` - `Controller "APP\V2\Controller\API\SocialNetworkController::loginAction()" requires that you provide a value for the "$testService" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.` So, looks like service still isn't injected. – miedzwin Sep 10 '18 at 06:46
  • Great feedback! You just gave me important elements. I'll try to composer a propper answer to your situation. – Tomas Votruba Sep 10 '18 at 08:52
  • @TomášVotruba I created repo: https://github.com/miedzwin/legacy-framework You can check how it works right now. If you have some time of course. Maybe you will find faster what I made wrong. Thank you. – miedzwin Sep 21 '18 at 13:29
  • Okay, I ask in few places how to handle it and solution was - own controller resolver. Anyone who can check how I done this - check repo from previous message and review code at tag v0.1 – miedzwin Oct 23 '18 at 13:44

2 Answers2

3

Symfony 4/5 Autowiring way

From your question and comments I think all you need to do is fix autowiring.

Symfony 4-way is simple: autowire all services and parameters, no manual setup (if possible).

To apply that to your example, this would be the best config to fit your needs and Symfony 4:

services:
    _defaults:
        # pass service dependencies to constructors by default
        autowire: true

        # add known tags (for commands, event subscribers etc) by default
        autoconfigure: true
        
        # to make using tests, bin files and another simpler
        public: true

        # autowiring of string/array/int parameters to constructors
        # this fixes cases like "argument "$facebookUserId" of method "__construct()" has no type-hint, you should configure its value explicitly"
        bind:
            # $constructorVariableName: %parameter% in config
            $facebookUserId: '%facebook_user_id%'

    APP\V2\:
        resource: '../src/app/V2/*'
        exclude: '../src/app/V2/{Script, Trait}'

    # symfony services - you still have to setup 3rd paryt services manually
    Symfony\Component\DependencyInjection\ParameterBag\ContainerBag:
        arguments:
            - '@service_container'
    # ...

Where to Continue Reading

Tomas Votruba
  • 23,240
  • 9
  • 79
  • 115
-1

https://symfony.com/doc/current/service_container.html#fetching-and-using-services

You need to change default configuration for your service to make them publics :

services:
    _defaults:
        public: true

But a more elegant way to access services is to inject them in controller actions :

public function myAction(Request $request, TestService $service)
Benito103e
  • 360
  • 3
  • 12