R
R
recomperx2020-09-27 07:13:03
PHP
recomperx, 2020-09-27 07:13:03

Why is Service Locator evil and what to use instead?

I read many articles about the fact that Service Locator is an anti-pattern. But it is used everywhere in PHP, in particular, in both Yii2 and Laravel, it underlies everything.

Help with 2 questions.
1. Did I understand correctly what is the problem with Service Locator?

As I understand it, the problem is that when we get an object something like this:
$db = $serviceLocator->get('db');
In fact, anything can be there, since at the language level we are not protected in any way with such a call, there can be any object that implements any interface, etc. And this is bad, since, for example, we are in some We call the 'mailer' service in some package for some framework, expecting to receive a copy of one mailer there, and our package was installed on another application that uses another mailer and everything stopped working. It seems like we should somehow explicitly check at the input that we will now receive an object of such and such a class or implementing such and such an interface.

2. What then should be used instead of Service Locator? At the same time, in terms of configuration, everything should be as centralized and collapsible as it is usually in frameworks with Service Locator. For example, we have services db, mailer, logger and many others. And their configs are centralized in config/app.php. How to get the db service in one line and use it, if not through the Service Locator?

Answer the question

In order to leave comments, you need to log in

3 answer(s)
F
FanatPHP, 2020-09-27
@recomperx

All these terrible words - they are actually always about the same thing - about connectivity. When you hardcode inside a class a call to a specific service, you are tightly attached to it. And to change the service to another, you will be forced to change the class code. Okay, changed. And right there in another place where the same class was used, something broke ! And now what? Make two classes that differ by one line? Of course not. And how then to use the same class for processing different incoming data (or the same data, but in different ways)? Make its behavior changeable. That is, to make those tools that he uses changeable - i.e. his dependencies.
Therefore, all dependencies are usually passed through the constructor (and therefore are calleddependency injection. )
Thus, we can change the behavior of the class without changing its code

But here we must understand that all this works only with the correct application of OOP . Or rather, just when using OOP. Because 98% of the "OOP" code that is written in PHP is pure proceduralism, even if it is wrapped in classes and methods. If your class method is a wall of code that you stupidly transferred from a file included in your favorite sloppy spaghetti, then this is not OOP. This is the same procedure, side view. And you will not feel the meaning of using dependency injection. Of course, you will use it, but as a cargo cult - because it was written on the toaster for you.
But when your code starts to become really objective - then it will become clearer to the rhinestone.

Similar to a service locator is a service or DI container. Used manually , it is the same service locator. Therefore, it should never be called manually - which is forbidden in Symphony's controllers - but only to automatically create classes . In the IEC, you have a lot of objects created automatically - entities, controllers. And so that when you automatically create an instance of a class, you have all the required services on hand - and you need a container.
Accordingly, the answer to the question "what to use?" very simple:
- when manually creating an instance of an object, transfer all dependencies to it through the constructor, and not receive it "out of thin air" in the code.
- when automatically creating an instance of an object, use dependency injection container
In this sense, it is very useful to master Symphony - a strict framework in which there is no locator service and in which it is forbidden to use the container directly.

D
dmitriy, 2020-10-01
@dmitriylanets

Main disadvantages:
- As a result, between the class and its client there is a formal or informal "contract", which is expressed in the form of preconditions (requirements for the client) and postconditions (guarantees of the performance of work). However, if a class accepts an instance of a service locator, or worse, uses a global locator, then this contract, or rather the requirements that the client of the class needs to fulfill, become unclear
- When our class uses a service locator, the stability of the class becomes undefined. Our class, in theory, can use anything, so changing any class (or interface) in our project can affect an arbitrary number of classes and modules
- The worst thing about the Service Locator is that it gives the appearance of good design. With us, no one knows about specific classes, everyone is tied to interfaces, everything is “normally” tested and “expanded”. But when you try to use your code in a different context, or when someone tries to reuse it, you will realize with horror that you have a wild "logical" coupling that you did not know about
- For me, the clarity and understandability of the class interface is more important number of constructor parameters . I do not rule out that there are cases when the service locator is the least evil, but in any case I would try to minimize its use.

I
Ivan Bogomolov, 2020-10-01
@kraso4niy

1. You understood everything correctly. The difficulty is that inside it may not be the structure that you expect (expected)
But this does not mean that it is bad. This problem can be solved by validation. In your example, it is enough to check for instanceOf. Then the code will be correct. And symfony did it even better and added type strictness for the service locator via service subscription.
To understand when to use a service locator and when to inject, follow the link https://symfony.com/doc/current/service_container/... and read.
There is an explanation:

Sometimes, a service needs access to several other services without being sure that all of them will actually be used. In those cases, you may want the instantiation of the services to be lazy. However, that's not possible using the explicit dependency injection since services are not all meant to be lazy

It can be explained with an example: let's say you have a class with a method, and inside the method, only in some cases you need to make an entry in the database (let's say the call to the service in the form get-> ('db')).
For example:
if($userData = $request->get('user_data')) {
    $db = $serviceLocator->get('db')->insert($userData);
} else {
   die("user data empty");
}

If you do it through an injection, then $db will always be initialized, for example, if you inject it through a constructor or method, then an object of your class will always run the $db initialization code under any conditions and spend resources on it. An example through injection (here the type of injection is through a method argument, the same can be done through a constructor or setter)
class MyClass {
   // $db будет инициализрован, но используется только в 1 случае для if
  // если $db тяжёлый сервис и инициализируется долго это проблема!
   function httpResponse(Request $request, Database $db) {
      
       if($userData = $request->get('user_data')) {
          $db->insert($userData);
       } else {
          die("user data empty");
       }
   }
}

And in the case of the $db locator service, it will be initialized only in the condition when user_data arrives. Thus, if your controller uses 15 services (but this is bad code!), And at the same time only 1 of them is needed, it is better to use the service locator.
class MyClass {
   // В таком случае рекомендуется воспользоваться сервис локатором! 
   function httpResponse(Request $request, Database $db, Servive1 $s1, Service2 $s2) {
      
       if($userData = $request->get('user_data')) {
          $db->insert($userData);
          if ($db->lastId()) {
               $s1->makeSuccess();
               if($s1->isSuccess()) {
                   $s2->commit();
               }
          }
       } else {
          $s2->rollback();
          die("user data empty");
       }
   }
}

This explains why the service locator is considered an anti-pattern. If your class uses 15 services, and all of them are used selectively, then you should probably change the architectural approach, refactor, decompose the class.
2. The formulation of the question is incorrect. It is definitely impossible to answer your question. In some cases, the service locator is more convenient, purely from a technical point of view. In some cases this is an anti-pattern, in others it is a matter of accepted norms and code organization within the team.
PS:
You can use a service locator and there is nothing to worry about, especially when you understand what it is and what it is for.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question