Passed
Branch master (610227)
by Mr
13:36
created

TypedMapTrait::set()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 2
crap 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 Assert\Assert;
12
use Ds\Map;
13
use RuntimeException;
14
15
trait TypedMapTrait
16
{
17
    private Map $compositeMap;
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

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