TypedMap   A
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 266
Duplicated Lines 0 %

Test Coverage

Coverage 97.86%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 36
eloc 111
c 2
b 0
f 0
dl 0
loc 266
ccs 137
cts 140
cp 0.9786
rs 9.52

28 Methods

Rating   Name   Duplication   Size   Complexity  
A get() 0 13 4
A reduce() 0 4 1
A keys() 0 4 1
A without() 0 7 1
A find() 0 5 1
A getValidTypes() 0 3 1
A assertValidMap() 0 6 1
A isEmpty() 0 4 1
A filter() 0 6 1
A init() 0 13 2
A last() 0 4 1
A diff() 0 5 1
A with() 0 8 1
A count() 0 4 1
A has() 0 5 1
A first() 0 4 1
A unwrap() 0 4 1
A merge() 0 7 1
A getIterator() 0 5 1
A map() 0 6 1
A intersect() 0 5 1
A search() 0 9 3
A empty() 0 6 1
A assertInitialized() 0 3 1
A assertValidType() 0 19 3
A __clone() 0 6 1
A __get() 0 3 1
A assertValidKey() 0 3 1
1
<?php declare(strict_types=1);
2
/**
3
 * This file is part of the daikon-cqrs/data-structure project.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
namespace Daikon\DataStructure;
10
11
use Daikon\Interop\Assert;
12
use Daikon\Interop\Assertion;
13
use Ds\Map;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Daikon\DataStructure\Map. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
14
15
abstract class TypedMap implements TypedMapInterface
16
{
17
    protected Map $compositeMap;
18
19
    protected array $validTypes = [];
20
21 81
    protected function init(iterable $objects, array $validTypes): void
22
    {
23 81
        Assertion::false(isset($this->compositeMap), 'Cannot reinitialize map.');
24 81
        Assertion::minCount($validTypes, 1, 'No valid types specified.');
25 81
        Assert::thatAll($validTypes, 'Invalid map types.')->string()->notEmpty();
26
27 81
        $this->validTypes = $validTypes;
28 81
        $this->compositeMap = new Map;
29
30 81
        foreach ($objects as $key => $object) {
31 66
            $this->assertValidKey($key);
32 62
            $this->assertValidType($object);
33 62
            $this->compositeMap->put($key, clone $object);
34
        }
35 77
    }
36
37
    /** @return static */
38 2
    public function empty(): self
39
    {
40 2
        $this->assertInitialized();
41 2
        $copy = clone $this;
42 2
        $copy->compositeMap->clear();
43 2
        return $copy;
44
    }
45
46 2
    public function keys(): array
47
    {
48 2
        $this->assertInitialized();
49 2
        return $this->compositeMap->keys()->toArray();
50
    }
51
52 22
    public function has(string $key): bool
53
    {
54 22
        $this->assertInitialized();
55 22
        $this->assertValidKey($key);
56 22
        return $this->compositeMap->hasKey($key);
57
    }
58
59 16
    public function get(string $key, $default = null): ?object
60
    {
61 16
        $this->assertInitialized();
62 16
        $this->assertValidKey($key);
63 16
        if (func_num_args() === 1) {
64 10
            Assertion::satisfy($key, [$this, 'has'], "Key '$key' not found and no default provided.");
65 6
            return clone $this->compositeMap->get($key);
66
        }
67 6
        if (!is_null($default)) {
68 4
            $this->assertValidType($default);
69
        }
70 4
        $object = $this->compositeMap->get($key, $default);
71 4
        return is_null($object) ? null : clone $object;
72
    }
73
74
    /** @return static */
75 4
    public function with(string $key, object $object): self
76
    {
77 4
        $this->assertInitialized();
78 4
        $this->assertValidKey($key);
79 4
        $this->assertValidType($object);
80 2
        $copy = clone $this;
81 2
        $copy->compositeMap->put($key, clone $object);
82 2
        return $copy;
83
    }
84
85
    /** @return static */
86 4
    public function without(string $key): self
87
    {
88 4
        $this->assertInitialized();
89 4
        Assertion::satisfy($key, [$this, 'has'], "Key '$key' not found.");
90 2
        $copy = clone $this;
91 2
        $copy->compositeMap->remove($key);
92 2
        return $copy;
93
    }
94
95
    /**
96
     * Note that this does not do a strict equality check because all objects are immutable so it's
97
     * unlikely that you will request a reference to an internal object. If you require more specific
98
     * matching use search(), filter(), unwrap object, or iterate.
99
     */
100 2
    public function find(object $object)
101
    {
102 2
        $this->assertInitialized();
103 2
        $this->assertValidType($object);
104 2
        return array_search($object, $this->compositeMap->toArray(), false);
105
    }
106
107 2
    public function first(): object
108
    {
109 2
        $this->assertInitialized();
110 2
        return clone $this->compositeMap->first()->value;
111
    }
112
113 2
    public function last(): object
114
    {
115 2
        $this->assertInitialized();
116 2
        return clone $this->compositeMap->last()->value;
117
    }
118
119 4
    public function isEmpty(): bool
120
    {
121 4
        $this->assertInitialized();
122 4
        return $this->compositeMap->isEmpty();
123
    }
124
125
    /**
126
     * @param static $map
127
     * @return static
128
     */
129 4
    public function merge($map): self
130
    {
131 4
        $this->assertInitialized();
132 4
        $this->assertValidMap($map);
133 2
        $copy = clone $this;
134 2
        $copy->compositeMap = $copy->compositeMap->merge(clone $map);
135 2
        return $copy;
136
    }
137
138
    /**
139
     * @param static $map
140
     * @return static
141
     */
142 4
    public function intersect($map): self
143
    {
144 4
        $this->assertInitialized();
145 4
        $this->assertValidMap($map);
146 2
        return $this->filter(fn(string $key): bool => $map->has($key));
147
    }
148
149
    /**
150
     * @param static $map
151
     * @return static
152
     */
153 4
    public function diff($map): self
154
    {
155 4
        $this->assertInitialized();
156 4
        $this->assertValidMap($map);
157 2
        return $this->filter(fn(string $key): bool => !$map->has($key));
158
    }
159
160
    /** @return static */
161 8
    public function filter(callable $predicate): self
162
    {
163 8
        $this->assertInitialized();
164 8
        $copy = clone $this;
165 8
        $copy->compositeMap = $copy->compositeMap->filter($predicate);
166 8
        return $copy;
167
    }
168
169 2
    public function search(callable $predicate)
170
    {
171 2
        $this->assertInitialized();
172 2
        foreach ($this as $key => $object) {
173 2
            if ($predicate($object) === true) {
174 2
                return $key;
175
            }
176
        }
177
        return false;
178
    }
179
180
    /** @return static */
181 2
    public function map(callable $predicate): self
182
    {
183 2
        $this->assertInitialized();
184 2
        $copy = clone $this;
185 2
        $copy->compositeMap->apply($predicate);
186 2
        return $copy;
187
    }
188
189 2
    public function reduce(callable $predicate, $initial = null)
190
    {
191 2
        $this->assertInitialized();
192 2
        return $this->compositeMap->reduce($predicate, $initial);
193
    }
194
195 6
    public function count(): int
196
    {
197 6
        $this->assertInitialized();
198 6
        return $this->compositeMap->count();
199
    }
200
201 4
    public function getValidTypes(): array
202
    {
203 4
        return $this->validTypes;
204
    }
205
206
    /**
207
     * This function does not clone the internal objects because you may want to access
208
     * them specifically for some reason.
209
     */
210 30
    public function unwrap(): array
211
    {
212 30
        $this->assertInitialized();
213 30
        return $this->compositeMap->toArray();
214
    }
215
216
    /** @psalm-suppress ImplementedReturnTypeMismatch */
217 8
    public function getIterator(): Map
218
    {
219 8
        $this->assertInitialized();
220 8
        $copy = clone $this;
221 8
        return $copy->compositeMap;
222
    }
223
224
    /** @param mixed $map */
225 12
    protected function assertValidMap($map): void
226
    {
227 12
        Assertion::isInstanceOf(
228 12
            $map,
229 12
            static::class,
230 12
            sprintf("Map operation must be on same type as '%s'.", static::class)
231
        );
232 6
    }
233
234
    /** @psalm-suppress RedundantPropertyInitializationCheck */
235 70
    protected function assertInitialized(): void
236
    {
237 70
        Assertion::true(isset($this->compositeMap), 'Map is not initialized.');
238 70
    }
239
240
    /** @param mixed $key */
241 68
    protected function assertValidKey($key): void
242
    {
243 68
        Assert::that($key, 'Key must be a valid string.')->string()->notEmpty();
244 64
    }
245
246
    /** @param mixed $object */
247 62
    protected function assertValidType($object): void
248
    {
249 62
        Assert::thatAll(
250 62
            $this->validTypes,
251 62
            sprintf("Object types specified in '%s' must be valid class or interface names.", static::class)
252 62
        )->string()
253 62
        ->notEmpty();
254
255 62
        Assertion::true(
256
            array_reduce(
257 62
                $this->validTypes,
258 62
                fn(bool $carry, string $type): bool => $carry || is_a($object, $type, true),
259 62
                false
260
            ),
261
            sprintf(
262 62
                "Invalid object type given to '%s', expected one of [%s] but was given '%s'.",
263 62
                static::class,
264 62
                implode(', ', $this->validTypes),
265 62
                is_object($object) ? get_class($object) : @gettype($object)
266
            )
267
        );
268 62
    }
269
270 8
    public function __get(string $key): ?object
271
    {
272 8
        return $this->get($key);
273
    }
274
275 24
    public function __clone()
276
    {
277 24
        $this->assertInitialized();
278 24
        $this->compositeMap = new Map(array_map(
279 24
            fn(object $object): object => clone $object,
280 24
            $this->compositeMap->toArray()
281
        ));
282 24
    }
283
}
284