2.11. Rejestr (Registry)

2.11.1. Przeznaczenie

Pozwala zaimplementować centralny magazyn często używanych obiektów w aplikacji. Zwykle jest implementowany przy użyciu klasy abstrakcyjnej posiadającej tylko statyczne metody lub przy użyciu wzorca Singleton. Zwróć uwagę, że Rejestr wprowadza globalny stan, czego powinno się unikać za wszelką cenę! Zamiast Rejestru powinieneś zaimplementować wzorzec Wstrzykiwania zależności.

2.11.2. Diagram UML

Alt Registry UML Diagram

2.11.3. Kod

Ten kod znajdziesz również na GitHub.

Registry.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Structural\Registry;
 6
 7use InvalidArgumentException;
 8
 9abstract class Registry
10{
11    public const LOGGER = 'logger';
12
13    /**
14     * this introduces global state in your application which can not be mocked up for testing
15     * and is therefor considered an anti-pattern! Use dependency injection instead!
16     *
17     * @var Service[]
18     */
19    private static array $services = [];
20
21    private static array $allowedKeys = [
22        self::LOGGER,
23    ];
24
25    final public static function set(string $key, Service $value)
26    {
27        if (!in_array($key, self::$allowedKeys)) {
28            throw new InvalidArgumentException('Invalid key given');
29        }
30
31        self::$services[$key] = $value;
32    }
33
34    final public static function get(string $key): Service
35    {
36        if (!in_array($key, self::$allowedKeys) || !isset(self::$services[$key])) {
37            throw new InvalidArgumentException('Invalid key given');
38        }
39
40        return self::$services[$key];
41    }
42}

Service.php

1<?php
2
3namespace DesignPatterns\Structural\Registry;
4
5class Service
6{
7}

2.11.4. Testy

Tests/RegistryTest.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Structural\Registry\Tests;
 6
 7use InvalidArgumentException;
 8use DesignPatterns\Structural\Registry\Registry;
 9use DesignPatterns\Structural\Registry\Service;
10use PHPUnit\Framework\TestCase;
11
12class RegistryTest extends TestCase
13{
14    private Service $service;
15
16    protected function setUp(): void
17    {
18        $this->service = $this->getMockBuilder(Service::class)->getMock();
19    }
20
21    public function testSetAndGetLogger()
22    {
23        Registry::set(Registry::LOGGER, $this->service);
24
25        $this->assertSame($this->service, Registry::get(Registry::LOGGER));
26    }
27
28    public function testThrowsExceptionWhenTryingToSetInvalidKey()
29    {
30        $this->expectException(InvalidArgumentException::class);
31
32        Registry::set('foobar', $this->service);
33    }
34
35    /**
36     * notice @runInSeparateProcess here: without it, a previous test might have set it already and
37     * testing would not be possible. That's why you should implement Dependency Injection where an
38     * injected class may easily be replaced by a mockup
39     *
40     * @runInSeparateProcess
41     */
42    public function testThrowsExceptionWhenTryingToGetNotSetKey()
43    {
44        $this->expectException(InvalidArgumentException::class);
45
46        Registry::get(Registry::LOGGER);
47    }
48}