Answer the question
In order to leave comments, you need to log in
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
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.
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.
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
if($userData = $request->get('user_data')) {
$db = $serviceLocator->get('db')->insert($userData);
} else {
die("user data empty");
}
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");
}
}
}
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");
}
}
}
Didn't find what you were looking for?
Ask your questionAsk a Question
731 491 924 answers to any question