Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Willl be Service Locator later in package? #255

Closed
andrew-svirin opened this issue Sep 5, 2021 · 18 comments
Closed

Willl be Service Locator later in package? #255

andrew-svirin opened this issue Sep 5, 2021 · 18 comments

Comments

@andrew-svirin
Copy link

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
Copy link
Member

vjik commented Sep 5, 2021

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 vjik closed this as completed Sep 5, 2021
@andrew-svirin
Copy link
Author

@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
Copy link
Member

vjik commented Sep 5, 2021

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)

@andrew-svirin
Copy link
Author

andrew-svirin commented Sep 5, 2021

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.

@vjik vjik reopened this Sep 5, 2021
@xepozz
Copy link
Contributor

xepozz commented Sep 5, 2021

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.

@andrew-svirin
Copy link
Author

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?

@samdark
Copy link
Member

samdark commented Sep 6, 2021

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.

@samdark samdark closed this as completed Sep 6, 2021
@BoShurik
Copy link

BoShurik commented Sep 6, 2021

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

@BoShurik
Copy link

BoShurik commented Sep 6, 2021

Ability to disable autowiring in container probably will solve the issue

@andrew-svirin
Copy link
Author

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.

@samdark
Copy link
Member

samdark commented Sep 6, 2021

@BoShurik isn't that usually a factory?

@samdark
Copy link
Member

samdark commented Sep 6, 2021

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

@BoShurik
Copy link

BoShurik commented Sep 6, 2021

@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

@andrew-svirin
Copy link
Author

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

@samdark
Copy link
Member

samdark commented Sep 6, 2021

@andrew-svirin
Copy link
Author

@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 =)

@andrew-svirin
Copy link
Author

@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.

@samdark
Copy link
Member

samdark commented Sep 9, 2021

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants