4.1. Service Locator

DIES WIRD ALS EIN ANTI-MUSTER ANGESEHEN!

Der Service Locator gilt für manche Menschen als Anti-Muster. Es verstößt gegen das Prinzip der Dependency Inversion. Service Locator verbirgt Klassenabhängigkeiten, anstatt sie aufzudecken, wie Sie es mit der Dependency Injection tun würden. Im Falle von Änderungen dieser Abhängigkeiten besteht die Gefahr, dass die Funktionalität der Klassen, die diese Abhängigkeiten verwenden, beeinträchtigt wird, was die Wartung Ihres Systems erschwert.

4.1.1. Zweck

Um eine lose gekoppelte Architektur zu erhalten, die testbar, wartbar und erweiterbar ist. Sowohl das Dependency Injection, als auch das Service Locator Pattern sind Implementierungen des Inverse Of Control Patterns.

4.1.2. Verwendung

Mit dem Service Locator kann ein Service anhand eines Interfaces registriert werden. Mit dem Interface kann dann ein Service erhalten und verwendet werden, ohne dass die Anwendung die genaue Implementierung kennen muss. Der Service Locator selbst kann im Bootstrapping der Anwendung konfiguriert und injiziert werden.

4.1.3. UML Diagramm

Alt ServiceLocator UML Diagram

4.1.4. Code

Du findest den Code auch auf GitHub

Service.php

1
2
3
4
5
6
7
8
<?php

namespace DesignPatterns\More\ServiceLocator;

interface Service
{

}

ServiceLocator.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<?php

declare(strict_types=1);

namespace DesignPatterns\More\ServiceLocator;

use OutOfRangeException;
use InvalidArgumentException;

class ServiceLocator
{
    /**
     * @var string[][]
     */
    private array $services = [];

    /**
     * @var Service[]
     */
    private array $instantiated = [];

    public function addInstance(string $class, Service $service)
    {
        $this->instantiated[$class] = $service;
    }

    public function addClass(string $class, array $params)
    {
        $this->services[$class] = $params;
    }

    public function has(string $interface): bool
    {
        return isset($this->services[$interface]) || isset($this->instantiated[$interface]);
    }

    public function get(string $class): Service
    {
        if (isset($this->instantiated[$class])) {
            return $this->instantiated[$class];
        }

        $args = $this->services[$class];

        switch (count($args)) {
            case 0:
                $object = new $class();
                break;
            case 1:
                $object = new $class($args[0]);
                break;
            case 2:
                $object = new $class($args[0], $args[1]);
                break;
            case 3:
                $object = new $class($args[0], $args[1], $args[2]);
                break;
            default:
                throw new OutOfRangeException('Too many arguments given');
        }

        if (!$object instanceof Service) {
            throw new InvalidArgumentException('Could not register service: is no instance of Service');
        }

        $this->instantiated[$class] = $object;

        return $object;
    }
}

LogService.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php

declare(strict_types=1);

namespace DesignPatterns\More\ServiceLocator;

class LogService implements Service
{

}

4.1.5. Теst

Tests/ServiceLocatorTest.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

declare(strict_types=1);

namespace DesignPatterns\More\ServiceLocator\Tests;

use DesignPatterns\More\ServiceLocator\LogService;
use DesignPatterns\More\ServiceLocator\ServiceLocator;
use PHPUnit\Framework\TestCase;

class ServiceLocatorTest extends TestCase
{
    private ServiceLocator $serviceLocator;

    public function setUp(): void
    {
        $this->serviceLocator = new ServiceLocator();
    }

    public function testHasServices()
    {
        $this->serviceLocator->addInstance(LogService::class, new LogService());

        $this->assertTrue($this->serviceLocator->has(LogService::class));
        $this->assertFalse($this->serviceLocator->has(self::class));
    }

    public function testGetWillInstantiateLogServiceIfNoInstanceHasBeenCreatedYet()
    {
        $this->serviceLocator->addClass(LogService::class, []);
        $logger = $this->serviceLocator->get(LogService::class);

        $this->assertInstanceOf(LogService::class, $logger);
    }
}