4.1. Локатор на служби

** ТОВА СЕ СЧИТА ЗА АНТИ-ШАБЛОН (anti-pattern)! **

Някои хора считат локатор на услуги за анти-модел (anti-pattern). Той нарушава принципа на инверсия на зависимостите. Локатор на услуги скрива зависимостите на класа, вместо да ги излага, както бихте направили, като използвате инжектирането на зависимост. В случай на промени в тези зависимости рискувате да нарушите функционалността на класовете, които ги използват, което прави вашата система трудна за поддръжка.

4.1.1. Предназначение

Да приложите свободно свързана архитектура (loosely coupled architecture), за да получите по-добре тестваем, поддържаем и разширяем код. Моделът DI и шаблонът Локатор на служби са изпълнение на Inverse of Control модел.

4.1.2. Исползване

С ServiceLocator можете да регистрирате служба за даден интерфейс. С помощта на интерфейса можете да я извлечете и да я използвате в класовете на приложението, без да знаете изпълнението й. Можете да конфигурирате и инжектирате Service Locator обект в bootstrap.

4.1.3. UML Диаграма

Alt ServiceLocator UML Diagram

4.1.4. Код

Можете също да намерите този код в GitHub

Service.php

1<?php
2
3namespace DesignPatterns\More\ServiceLocator;
4
5interface Service
6{
7}

ServiceLocator.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\More\ServiceLocator;
 6
 7use OutOfRangeException;
 8use InvalidArgumentException;
 9
10class ServiceLocator
11{
12    /**
13     * @var string[][]
14     */
15    private array $services = [];
16
17    /**
18     * @var Service[]
19     */
20    private array $instantiated = [];
21
22    public function addInstance(string $class, Service $service)
23    {
24        $this->instantiated[$class] = $service;
25    }
26
27    public function addClass(string $class, array $params)
28    {
29        $this->services[$class] = $params;
30    }
31
32    public function has(string $interface): bool
33    {
34        return isset($this->services[$interface]) || isset($this->instantiated[$interface]);
35    }
36
37    public function get(string $class): Service
38    {
39        if (isset($this->instantiated[$class])) {
40            return $this->instantiated[$class];
41        }
42
43        $object = new $class(...$this->services[$class]);
44
45        if (!$object instanceof Service) {
46            throw new InvalidArgumentException('Could not register service: is no instance of Service');
47        }
48
49        $this->instantiated[$class] = $object;
50
51        return $object;
52    }
53}

LogService.php

1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\More\ServiceLocator;
6
7class LogService implements Service
8{
9}

4.1.5. Тест

Tests/ServiceLocatorTest.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\More\ServiceLocator\Tests;
 6
 7use DesignPatterns\More\ServiceLocator\LogService;
 8use DesignPatterns\More\ServiceLocator\ServiceLocator;
 9use PHPUnit\Framework\TestCase;
10
11class ServiceLocatorTest extends TestCase
12{
13    private ServiceLocator $serviceLocator;
14
15    public function setUp(): void
16    {
17        $this->serviceLocator = new ServiceLocator();
18    }
19
20    public function testHasServices()
21    {
22        $this->serviceLocator->addInstance(LogService::class, new LogService());
23
24        $this->assertTrue($this->serviceLocator->has(LogService::class));
25        $this->assertFalse($this->serviceLocator->has(self::class));
26    }
27
28    public function testGetWillInstantiateLogServiceIfNoInstanceHasBeenCreatedYet()
29    {
30        $this->serviceLocator->addClass(LogService::class, []);
31        $logger = $this->serviceLocator->get(LogService::class);
32
33        $this->assertInstanceOf(LogService::class, $logger);
34    }
35}