yiisoft/di

Willl be Service Locator later in package?

andrew-svirin opened this issue · 18 comments

Hi,

I have some module that have group of similar services and I want to manipulate with these services by ServiceLocator.

Just for information: Will you have ServiceLocator later or I can make it myself for my needs?

vjik commented

We recommend use DI instead of service locator. See Use DI instead of service locator.

Also we planned make module container for configuration of modules.

@vjik this is different purpose.

I have some folder with 10 services those have same interface.
So I want navigate only by this list of services through service locator.

Does DI allow to do this? Because I do not need any another services. May be by some tag like it do Symfony?

I want pick each service from service locator to apply internal logic.

vjik commented

I have some folder with 10 services those have same interface.
So I want navigate only by this list of services through service locator.

You can configure in DI container each service separately:

[
	Service1::class => [ ... ],
	Service2::class => [ ... ],
	Service3::class => [ ... ],
	...
]

And use concrete service in your classes:

final class MyClass {
	public __construct(Service1 $service) {}
}

Does DI allow to do this? Because I do not need any another services. May be by some tag like it do Symfony?

DI support tags (see https://github.com/yiisoft/di#container-tags)

I understand how to use DI =) But it lightly different:
My directory with n-strategies:

startegies\Strategy1.php
startegies\StrategyN.php

Then my general service for example PatternRecognizer.php with next code:

class PatternRecognizer
{
  public function __copnstruct(StrategyLocator $sl)....

  public function recognize(string $content)
 {
   foreach($this->sl->generateService() as $strategy){
     $result = $strategy->applyTo(string $content);
     ...
   }

   return  $result;
 }

}

Or another console command class: StrategyCheckCommand that can accept input argument by Strategy alias s-1 or s-n and then find by locator the looking strategy for checking.

So I want navigate only by this list of services through service locator.

Does DI allow to do this? Because I do not need any another services. May be by some tag like it do Symfony?

Try the following way:

class StrategyResolver 
{
  public function __construct($strategy1, $strategy2, ...) 
  {
    $this->strategies = [
      's1' => $strategy1,
      's2' => $strategy2,
    ];
  }

  public function getStrategy(string $alias) 
  {
    return $this->strategies[$alias];
  }
}

But I suggest you to encapsulate your logic into "strategies":

class StrategyResolver 
{
  public function __construct($strategy1, $strategy2, ...) 
  {
    $this->strategies = [
      $strategy1,
      $strategy2,
    ];
  }

  public function do($context) 
  {
    foreach($strategies as $strategy) {
      if ($strategy->satisfied($context)) {
        return $strategy->do($context)
      }
    }

    throw new RuntimeException('No one strategy was satisfied to context');
  }
}

If you don't want to inject all services to work with only one you can make services lazy.

I hope when we add "autotagged" services by interface like the Symfony does, you can make that resolver lighter:

The resolver will be look like this:

class StrategyResolver 
{
  public function __construct(StrategyInterface ...$strategies) 
  {
    $this->strategies = $strategies
  }

  // ...
}

And the StrategyResolver will be configured like "give me all services with tag StrategyInterface and pass them into constructor".


Also you can add a tag to each service explicitly. Read the doc how to do it now.

In the case, when I will detect matched strategy I should break resolution of strategies and return result, so in fact it can be enough to instantiate only few strategies.
But StrategyResolver will instantiate all the strategies in constructor that will look very huge.
Over all there 10 strategies for the moment and it is not a limit.
So, on my opinion, to use here DI can solve the goal, but it will look not optimized. Thus I made very similar class with ServiceLocator from Yii2 for that case.
But do you find Service Locator pattern in Yii3 not useful in pure view?

But do you find Service Locator pattern in Yii3 not useful in pure view?

Yes. For majority of cases it's not a good idea. Leads to tight coupling so we'll avoid using/mentioning it.

Your case is a bit special so it may make sense for you since you know what you want to do and why.

I think it's a quite common use case. E.g. implementing command bus without service locator leads to inject whole container that leads to much more coupling

Ability to disable autowiring in container probably will solve the issue

If we touched autowiring, then: Disable autowiring of everything also can help to make some services private in scope of module. It is useful when vendors want to register only certain services or facades of their module and hide for internal usage some exclusive services.

@BoShurik isn't that usually a factory?

@andrew-svirin can't it be solved with composite containers?

@samdark It can be solved with factory but if handler is stateless (it's usually is) we can get benefits when using RR and swoole

I am not familiar with CompositeContainer. Do you have any reference with an example =) ?

@samdark thanks ) I will try to use this sub container, but I am not sure that it can navigate through each registered service in it.

Anyway if follow the rule "Principle_of_least_astonishment" then here much more fitting Service Locator =)

@samdark
Proposition only.
I made some scratch of service locator - https://github.com/andrew-svirin/yii3-service-locator
ServiceLocator is closed from instantiated by DI by protecting constructor. And added factory instead.
This tool can popularize typical approach for people do not invent a wheel.

@andrew-svirin yes, it could be implemented like that. Still, I don't think it should be part of the framework.