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. Amaç

Daha iyi test edilebilir, bakım yapılabilir ve genişletilebilir kod elde etmek için gevşek bağlı bir mimari uygulamak. Hizmet Bulucu ve Bağımlılık Aktarımı (Dependency Injection) desenleri, Ters Denetim (Inverse of Control) deseninin bir uygulanımıdır.

4.1.2. Kullanım

ServiceLocator ile, belirli bir arayüz için bir hizmet kaydı yapabilirsiniz. Bu arayüzü kullanarak hizmete erişebilir ve hizmetin uygulanımını (implementation) bilmeksizin uygulama sınıflarında kullanabilirsiniz. ServiceLocator nesnesini önyüklemede (bootstrap) yapılandırabilir ve aktarım (injection) yapabilirsiniz.

4.1.3. UML Diyagramı

Alt ServiceLocator UML Diagram

4.1.4. Kod

Bu kodu Github üzerinde de bulabilirsiniz.

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. Test

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);
    }
}