Passed
Push — master ( bcd72f...c2231d )
by Jesse
02:08
created

IdentityMap::removeThe()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
c 0
b 0
f 0
rs 9.4285
cc 3
eloc 10
nc 3
nop 1
1
<?php
2
declare(strict_types=1);
3
4
namespace Stratadox\IdentityMap;
5
6
use function assert as makeSureThat;
7
use function get_class as theClassOfThe;
8
use function is_object as itIsAn;
9
use function spl_object_id as theInstanceIdOf;
10
11
/**
12
 * Contains objects by class and id.
13
 *
14
 * @author Stratadox
15
 */
16
final class IdentityMap implements MapsObjectsByIdentity
17
{
18
    private $map;
19
    private $reverseMap;
20
21
    private function __construct(array $map, array $reverseMap)
22
    {
23
        $this->map = $map;
24
        $this->reverseMap = $reverseMap;
25
    }
26
27
    /**
28
     * Produces a new identity map that contains the objects.
29
     *
30
     * @param object[] $objects      The objects to add, as [id => object]
31
     * @return MapsObjectsByIdentity The map of objects.
32
     */
33
    public static function with(array $objects): MapsObjectsByIdentity
34
    {
35
        $map = [];
36
        $reverse = [];
37
        foreach ($objects as $id => $object) {
38
            $map = IdentityMap::addTo($map, (string) $id, $object);
39
            $reverse[theInstanceIdOf($object)] = (string) $id;
40
        }
41
        return new self($map, $reverse);
42
    }
43
44
    /**
45
     * Produces an empty identity map.
46
     *
47
     * @return MapsObjectsByIdentity The empty map of objects.
48
     */
49
    public static function startEmpty(): MapsObjectsByIdentity
50
    {
51
        return new self([], []);
52
    }
53
54
    /** @inheritdoc */
55
    public function has(string $class, string $id): bool
56
    {
57
        return isset($this->map[$class][$id]);
58
    }
59
60
    /** @inheritdoc */
61
    public function hasThe(object $object): bool
62
    {
63
        return isset($this->reverseMap[theInstanceIdOf($object)]);
64
    }
65
66
    /** @inheritdoc */
67
    public function get(string $class, string $id): object
68
    {
69
        $this->mustHave($class, $id);
70
        return $this->map[$class][$id];
71
    }
72
73
    /** @inheritdoc */
74
    public function add(string $id, object $object): MapsObjectsByIdentity
75
    {
76
        $class = theClassOfThe($object);
77
        $this->mayNotAlreadyHave($class, $id);
78
        $new = clone $this;
79
        $new->map[$class][$id] = $object;
80
        $new->reverseMap[theInstanceIdOf($object)] = $id;
81
        return $new;
82
    }
83
84
    /** @inheritdoc */
85
    public function remove(string $class, string $id): MapsObjectsByIdentity
86
    {
87
        $this->mustHave($class, $id);
88
        $reverse = $this->reverseMap;
89
        $map = $this->map;
90
        unset(
91
            $reverse[theInstanceIdOf($map[$class][$id])],
92
            $map[$class][$id]
93
        );
94
        return new IdentityMap($map, $reverse);
95
    }
96
97
    /** @inheritdoc */
98
    public function removeThe(string $class): MapsObjectsByIdentity
99
    {
100
        $map = $this->map;
101
        if (!isset($map[$class])) {
102
            return $this;
103
        }
104
        $reverse = $this->reverseMap;
105
        foreach ($map[$class] as $object) {
106
            makeSureThat(itIsAn($object));
107
            unset($reverse[theInstanceIdOf($object)]);
108
        }
109
        unset($map[$class]);
110
        return new IdentityMap($map, $reverse);
111
    }
112
113
    /** @inheritdoc */
114
    public function idOf(object $object): string
115
    {
116
        if (!isset($this->reverseMap[theInstanceIdOf($object)])) {
117
            throw IdentityNotFound::forThe($object);
118
        }
119
        return $this->reverseMap[theInstanceIdOf($object)];
120
    }
121
122
    /**
123
     * Asserts that the object of the class with this id is present in the map.
124
     *
125
     * @param string $class The class of the object to check for.
126
     * @param string $id    The identity of the object, unique per class.
127
     * @throws NoSuchObject When there is no object with this id in the map.
128
     */
129
    private function mustHave(string $class, string $id): void
130
    {
131
        if ($this->has($class, $id)) {
132
            return;
133
        }
134
        throw IdentityNotFound::requesting($class, $id);
135
    }
136
137
    /**
138
     * Asserts that the object of the class with this id is not already there.
139
     *
140
     * @param string $class The class of the object to check for.
141
     * @param string $id    The identity of the object, unique per class.
142
     * @throws AlreadyThere When there is already an object with this id.
143
     */
144
    private function mayNotAlreadyHave(string $class, string $id): void
145
    {
146
        if ($this->has($class, $id)) {
147
            throw DuplicationDetected::in($class, $id);
148
        }
149
    }
150
151
    /**
152
     * Adds the object to the map, returning the new map.
153
     *
154
     * @param array  $map    The original map.
155
     * @param string $id     The id of the object to add.
156
     * @param object $object The object instance to add.
157
     * @return array         A new map that includes the new object.
158
     */
159
    private static function addTo(array $map, string $id, object $object): array
160
    {
161
        $map[theClassOfThe($object)][$id] = $object;
162
        return $map;
163
    }
164
}
165