Service locator vs Dependency injection
During designing application, we are making new classes. Classes have dependencies. In programming we distinguish two patterns for managing class’s dependencies:
- Dependency injection
- Service locator
Dependency injection
In the following code, class Service perfectly shows us its dependencies. We know that we have to inject Logger to the constructor to create an instance of this class.
<?php use Psr\Log\LoggerInterface; class Service { private $logger; public function __construct(LoggerInterface $em) { $this->logger = $logger; } public function doSomething() { $this->logger->info('...'); } }
Service locator
A simple example of using service locator is something like that:
<?php use Psr\Container\ContainerInterface; class Service { private $container; public function __construct(ContainerInterface $container) { $this->container = $container; } public function doSomething() { $this->container->get('logger')->info('...'); } }
Tell me now, what are dependencies of Service class? Yeah, of course, you don’t know. We can’t define it precisely. We only know, that class requires the Container, but from the container, it can get any dependency.
Service locator as anti-pattern
For reason, that service locator hides class’s dependencies is considered as an anti-pattern. Hidden dependencies are making testing code harder. We can’t in an easy way mock these dependencies.
Magic of Laravel
In Laravel, we have something called Facades. Many people are hating laravel for this. Facades give us the same problem as Service locator. They hide dependencies of class. If you look at the constructor of a class you don’t know what are dependencies. Also, you can’t mock them. We can say that facades are even worse, because we can access them everywhere. Facade in a template, no problem.
How it works?
Log::info('...');
We are seeing :: operator, so we can think that in class Log should be static method info(), nothing could be further from the truth. In fact every facade inherits from the base class magic method like following:
public static function __callStatic($method, $args) { $instance = static::getFacadeRoot(); if (! $instance) { throw new RuntimeException('A facade root has not been set.'); } return $instance->$method(...$args); }
It follows that it isn’t static call, but instance method call.
Tying with framework
Let’s compare two examples of code. First when we have code which is using dependency injection like following.
<?php final class Service implements ServiceInterface { private $logger; public function __construct(LoggerInterface $logger) { $this->logger = $logger; } public function doSomething(): void { $this->logger->info('...'); } }
Second with laravel’s facades.
<?php final class Service implements ServiceInterface { public function doSomething(): void { Log::info('...'); } }
In the first example, we can in easy way use code with another framework. Our class doesn’t know what framework we’re using, so it isn’t a problem. While in the second example, if we want to use it with another framework, we have to do some refactoring like removing laravel’s facades. So using laravel’s facades we are tying our code with laravel. It isn’t good practice.
Summary
If you want to write code easy testable and maintainable you should avoid using service locator and facades. Dependencies of the class have to be exposed, not hidden. Just use Dependency injection.
I agree with the fact that you can use your code in another context (not Laravel). This is also the reason why I do not like to use frameworks: changing a composer component is way easier than changing a framework and many websites live longer than frameworks. Finding a PHP developer is also easier than finding a Laravel developer.
But to defend Laravel, Facades are testable and mockable:
https://laravel.com/docs/5.8/mocking#mocking-facades
Furthermore, if you see “::” and think static, then this is just a habit, times change. “__callStatic” is a PHP feature, you might not like, might not use, but you have to accept it, it is part of the language and there are reasons for it.
After all, after getting your head around Facades, development speed and readability in Laravel might justify everything, but we cannot measure it 🙂
Using only the composer’s components isn’t better than using frameworks. In these two situations, you can write a code tightly coupled with the used package, no matter what it is, a framework or just a single component. I think that is stupid practice to search “Laravel developer”, companies should search for good developers, so they can use a proper solution for a specific problem.
Of course, every solution had pros and cons, I can agree with development speed but, readability isn’t good part in Laravel. I know that facades are testable and mockable, but when you don’t clearly see class’s dependencies you may not notice that refactoring is required.