16

i am in the process of upgrading a large application to 4.2

and the $this->get(".....") from inside the controller is deprecated and one should use AutoWire instead.

i am running into the problem that i have 2 services, which are in fact from the same class (just diffrent constructor args).

services.yml

services:
  service.a:
    class: Namespace\MyClass
    arguments: [ "argument1" ]

  service.b:
    class: Namespace\MyClass
    arguments: [ "argument2" ]

controller:

public function demoAction() {
  $serviceA = $this->get("service.a");
  $serviceB = $this->get("service.b");
}

and the problematic result:

public function demoAction(MyClass $serviceA, MyClass $serviceB) {
}

we can use alias to service definitions like:

MyClass: '@service.a'

but i cannot use a virtual/fake class like (without an existing one):

MyPseudClass: '@service.b'

how do you handle cases like this in autowire mode?

i could create "pseudo" classes, that extend from the base, just to get different classnames, but that feels weird.

Helmut Januschka
  • 1,578
  • 3
  • 16
  • 34
  • You have some good answers. One approach I sometimes use is to just make two more classes MyClassA and MyClassB both extending from MyClass and otherwise empty. Then you can typehint and use autowire to avoid any services.yaml entry at all. – Cerad Nov 30 '18 at 13:10
  • 2
    yeah i know, tried it, works, but less code is best code! – Helmut Januschka Nov 30 '18 at 13:12

3 Answers3

20

Starting with 4.2, you can define named autowiring aliases. That should work:

services:
    Namespace\MyClass $serviceA: '@service.a'
    Namespace\MyClass $serviceB: '@service.b'

With Symfony 3.4 and 4.1, you can use bindings instead - but that's less specific as that doesn't take the type into account:

services:
    _defaults:
        bind:
            $serviceA: '@service.a'
            $serviceB: '@service.b'
Nicolas Grekas
  • 686
  • 5
  • 6
  • thx - that works, so i go with the alias way, looks legit and works. on some places i rely on the fact that services_dev.yml changes the class of some services, therefore the "type" is not stable, so i use the bind way on those, is the bind thing a future proof way? or will it be deprecated soon? – Helmut Januschka Nov 30 '18 at 11:59
  • I think you meant `services: _defaults: bind: Namespace\MyClass $serviceA: '@service.a' Namespace\MyClass $serviceB: '@service.b':` ? – medunes Mar 29 '19 at 16:53
4

Another options is to implement the Factory Pattern. This pattern will enable you to create a service based on the arguments provided.

# services.yml
service.a:
    class: App\MyClass
    factory: 'App\Factory\StaticMyClassFactory:createMyClass'
    arguments:
        - ['argument1']

service.b:
    class: App\MyClass
    factory: 'App\Factory\StaticMyClassFactory:createMyClass'
    arguments:
        - ['argument2']

And your StaticMyClassFactory would look like this

class StaticMyClassFactory
{  
    public static function createMyClass($argument)
    {
        // Return your class based on the argument passed
        $myClass = new MyClass($argument);

        return $myClass;
    }
}
Leander
  • 148
  • 1
  • 5
1

You can still use the "@servicename" in the service.yml files, and so wire them by name/ Here's an example where I have a couple of different Loggers being wired into a service constructor.

# App/Subscribers/WebhookLoggingListener.php file
public function __construct(
    LoggerInterface $logger, 
    LoggerInterface $mailgunLog
{ }

# services.yml
App\Subscribers\WebhookLoggingListener:
    arguments:
        $logger: "@logger"
        $mailgunLog: "@monolog.logger.mailgun"
    tags:
       - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest

You can also bind them to variable names (in the services: _defaults: at the head of a services.yaml file, but if they won't be reused, I consider it better to keep the configuration more localised).

Alister Bulman
  • 34,482
  • 9
  • 71
  • 110