4.1. Service Locator

THIS IS CONSIDERED TO BE AN ANTI-PATTERN!

Service Locator is considered for some people an anti-pattern. It violates the Dependency Inversion principle. Service Locator hides class’ dependencies instead of exposing them as you would do using the Dependency Injection. In case of changes of those dependencies you risk to break the functionality of classes which are using them, making your system difficult to maintain.

4.1.1. Doel

Om een ​​losjes gekoppelde architectuur te implementeren om beter testbare, onderhoudbare en uitbreidbare code te krijgen. DI-patroon en Service Locator-patroon zijn een implementatie van het Inverse of Control-patroon.

4.1.2. Gebruik

Met `` ServiceLocator ‘’ kunt u een service registreren voor een bepaalde interface. Door de interface te gebruiken, kunt u de service ophalen en gebruiken in de klassen van de applicatie zonder de implementatie ervan te kennen. U kunt het Service Locator-object configureren en injecteren op bootstrap.

4.1.3. UML Diagram

Alt ServiceLocator UML Diagram

4.1.4. Code

Je kan deze broncode terugvinden op 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. Теst

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}