Service Locator vs Dependency Injection.

Dec 13, 2023Architecture patterns
featured-image

Service Locator is a design pattern used in software development. It acts as a central registry or directory that keeps track of different services or objects in a system. When a part of the code needs a particular service or object, it asks the service locator to provide it.

In simpler terms, it's like a big list or map that holds information about various tools or things your code might need, and when your code wants something specific, it asks this list to get it.

final class CategoryController extends Controller
{
    public function actionIndex(): string
    {
        $db = Yii::$app->db;
    }
}
As we can see in the previous example, if I need to access the db component, i simply use the Service Locator and request the object. This works perfectly fine, but what could be wrong is that the controller will have hidden dependencies, which are sometimes hard to test, less readable, and error-prone.

Now, Yii2 not only provides the Service Locator, which when used properly, is very helpful. In some places, like views, using the Service Locator might be fine. 

However, for controllers, actions, or services, it's better to use Dependency Injection.

Dependency Injection is a design pattern that allows us to eliminate hard-coded dependencies and make our applications loosely coupled, extendable, and maintainable. We use a di container to manage our classes and their dependencies. When a class needs another class to perform its duties, the di container injects that dependency. This is the core idea behind Dependency Injection.

<?php

declare(strict_types=1);

namespace Yii\Blog\UseCase\Category\Register;

use Yii;
use yii\base\Action;
use Yii\Blog\ActiveRecord\Category;
use Yii\Blog\BlogModule;
use Yii\Blog\UseCase\Category\CategoryEvent;
use Yii\Blog\UseCase\Category\CategoryService;
use Yii\Blog\Widget\Seo\SeoForm;
use Yii\Blog\Widget\Seo\SeoService;
use Yii\CoreLibrary\Validator\AjaxValidator;
use yii\db\Connection;
use yii\web\Controller;
use yii\web\Request;
use yii\web\Response;

final class RegisterAction extends Action
{
    public function __construct(
        string $id,
        Controller $controller,
        private readonly AjaxValidator $ajaxValidator,
        private readonly BlogModule $blogModule,
        private readonly Connection $db,
        private readonly CategoryService $categoryService,
        private readonly RegisterService $registerService,
        private readonly Request $request,
        private readonly SeoService $seoService,
        array $config = []
    ) {
        parent::__construct($id, $controller, $config);
    }

    public function run(string $id = null): string|Response
    {
        $categoryForm = new $this->controller->formModelClass($this->blogModule, $this->id);
        $this->ajaxValidator->validate($categoryForm);
        $registerEvent = new CategoryEvent($id);
        $this->trigger(CategoryEvent::BEFORE_REGISTER, $registerEvent);
        $seoForm = new SeoForm();
        if (
            $categoryForm->load($this->request->post()) &&
            $seoForm->load($this->request->post()) &&
            $categoryForm->validate() &&
            $seoForm->validate() &&
            $this->registerService->run($categoryForm, $id) &&
            $this->seoService->run($seoForm, Category::class, $categoryForm->id)
        ) {
            $this->trigger(CategoryEvent::AFTER_REGISTER, $registerEvent);
            return $this->controller->redirect(['category/index']);
        }
        return $this->controller->render(
            '_form',
            [
                'blogModule' => $this->blogModule,
                'buttonTitle' => Yii::t('yii.blog', 'Register'),
                'formModel' => $categoryForm,
                'id' => $id,
                'imageFile' => '',
                'nodeTree' => $this->categoryService->buildNodeTree(),
                'seoForm' => $seoForm,
                'title' => Yii::t('yii.blog', 'Register category'),
            ],
        );
    }
}

Let's see how it works in Yii2 all the classes we request in the constructor are automatically instantiated via auto-wiring. In the previous example, the advantage of using Dependency Injection is that we can clearly see the classes required for its operation in an explicit way. This allows for more readable, maintainable, and easily testable code.

Remember, the key benefit of DI is the ability to swap dependencies without changing the class that uses them. It’s also worth noting that DI is not about how those dependencies are instantiated, but rather how they’re passed to the client class.

Happy coding!