4.1. Service Locator

CECI EST CONSIDÉRÉ COMME UN ANTI-PATTERN !

Le Service Locator est considéré par certains comme un anti-pattern. Il viole le principe d’inversion des dépendances. Le Service Locator cache les dépendances des classes au lieu de les exposer comme vous le feriez en utilisant l’injection de dépendances. En cas de modification de ces dépendances, vous risquez de casser la fonctionnalité des classes qui les utilisent, ce qui rend votre système difficile à maintenir.

4.1.1. Rôle

Mettre en œuvre une architecture faiblement couplée afin d’obtenir un code plus facile à tester, à maintenir et à étendre. Le modèle DI et le modèle Service Locator sont une mise en œuvre du modèle Inverse of Control.

4.1.2. Rôle

Avec ServiceLocator, vous pouvez enregistrer un service pour une interface donnée. En utilisant l’interface, vous pouvez récupérer le service et l’utiliser dans les classes de l’application sans connaître son implémentation. Vous pouvez configurer et injecter l’objet Service Locator au démarrage.

4.1.3. Diagramme UML

Alt ServiceLocator UML Diagram

4.1.4. Code

Vous pouvez également trouver ce code sur 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. Test

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}