Passed
Pull Request — master (#24)
by Jesús
02:51
created

AbstractTypedArray::guardInstanceMap()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 5
nc 2
nop 1
dl 0
loc 8
ccs 5
cts 5
cp 1
crap 4
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace TypedArrays;
6
7
use ArrayObject;
8
use TypedArrays\Exceptions\ImmutabilityException;
9
use TypedArrays\Exceptions\InvalidSetupException;
10
use TypedArrays\Exceptions\InvalidTypeException;
11
use TypedArrays\Exceptions\ListException;
12
use TypedArrays\Exceptions\MapException;
13
14
abstract class AbstractTypedArray extends ArrayObject
15
{
16
    protected const SCALAR_BOOLEAN = 'boolean';
17
    protected const SCALAR_INTEGER = 'integer';
18
    protected const SCALAR_DOUBLE = 'double';
19
    protected const SCALAR_STRING = 'string';
20
21
    protected const COLLECTION_TYPE_ARRAY = 'array';
22
    protected const COLLECTION_TYPE_LIST = 'list';
23
    protected const COLLECTION_TYPE_MAP = 'map';
24
25
    private const POSSIBLE_SCALARS = [
26
        self::SCALAR_BOOLEAN,
27
        self::SCALAR_INTEGER,
28
        self::SCALAR_DOUBLE,
29
        self::SCALAR_STRING,
30
    ];
31
32
    private const POSSIBLE_COLLECTION_TYPES = [
33
        self::COLLECTION_TYPE_ARRAY,
34
        self::COLLECTION_TYPE_LIST,
35
        self::COLLECTION_TYPE_MAP,
36
    ];
37
38
    abstract protected function typeToEnforce(): string;
39
40 82
    protected function collectionType(): string
41
    {
42 82
        return self::COLLECTION_TYPE_ARRAY;
43
    }
44
45 73
    protected function isMutable(): bool
46
    {
47 73
        return true;
48
    }
49
50
    protected function isNullAllowed(): bool
51
    {
52
        return false;
53
    }
54
55
    /**
56
     * @throws InvalidSetupException
57
     * @throws InvalidTypeException
58
     * @throws ListException
59
     * @throws MapException
60
     */
61 296
    public function __construct(array $input = [])
62
    {
63 296
        $this->guardChildCollectionType();
64 296
        $this->guardChildTypeToEnforce();
65
66 294
        $this->guardInstanceTypeToEnforce($input);
67 173
        $this->guardInstanceList($input);
68 164
        $this->guardInstanceMap($input);
69
70 155
        parent::__construct($input);
71 155
    }
72
73
    /**
74
     * @param mixed $key
75
     * @param mixed $value
76
     *
77
     * @throws InvalidTypeException
78
     * @throws ImmutabilityException
79
     * @throws ListException
80
     * @throws MapException
81
     */
82 86
    public function offsetSet($key, $value): void
83
    {
84 86
        $this->guardMutability();
85 73
        $this->guardOffsetSetList($key);
86 68
        $this->guardOffsetSetMap($key);
87 63
        $this->guardOffsetSetType($value);
88
89 15
        parent::offsetSet($key, $value);
90 15
    }
91
92
    /**
93
     * @param mixed $key
94
     *
95
     * @throws ImmutabilityException
96
     */
97 13
    public function offsetUnset($key): void
98
    {
99 13
        $this->guardMutability();
100
101
        parent::offsetUnset($key);
102
    }
103
104
    /**
105
     * @throws InvalidTypeException
106
     */
107 296
    private function guardChildCollectionType(): void
108
    {
109 296
        if (!in_array($this->collectionType(), self::POSSIBLE_COLLECTION_TYPES)) {
110
            throw InvalidSetupException::forCollectionType($this->collectionType());
111
        }
112 296
    }
113
114
    /**
115
     * @throws InvalidSetupException
116
     */
117 296
    private function guardChildTypeToEnforce(): void
118
    {
119
        if (
120 296
            !$this->checkForValidClass()
121 296
            && !$this->checkForScalar()
122
        ) {
123 2
            throw InvalidSetupException::forEnforceType($this->typeToEnforce());
124
        }
125 294
    }
126
127 296
    private function checkForValidClass(): bool
128
    {
129 296
        return class_exists($this->typeToEnforce())
130 296
            || interface_exists($this->typeToEnforce());
131
    }
132
133 282
    private function checkForScalar(): bool
134
    {
135 282
        return in_array($this->typeToEnforce(), self::POSSIBLE_SCALARS);
136
    }
137
138
    /**
139
     * @throws InvalidTypeException
140
     */
141 294
    private function guardInstanceTypeToEnforce(array $input): void
142
    {
143 294
        array_map(function ($item): void {
144 236
            if (!$this->checkType($item)) {
145 121
                throw InvalidTypeException::onInstantiate(static::class, static::getType($item), $this->typeToEnforce());
146
            }
147 294
        }, $input);
148 173
    }
149
150
    /**
151
     * @param mixed $variable
152
     */
153 284
    private function checkType($variable): bool
154
    {
155 284
        if (is_null($variable)) {
156 2
            return $this->isNullAllowed();
157
        }
158
159 284
        if (is_object($variable)) {
160 50
            return get_class($variable) === $this->typeToEnforce()
161 50
                || is_subclass_of($variable, $this->typeToEnforce());
162
        }
163
164 235
        return static::getType($variable) === $this->typeToEnforce();
165
    }
166
167
    /**
168
     * @param mixed $variable
169
     */
170 272
    private static function getType($variable): string
171
    {
172 272
        return is_object($variable)
173 36
            ? get_class($variable)
174 272
            : gettype($variable);
175
    }
176
177
    /**
178
     * @throws ListException
179
     */
180 173
    private function guardInstanceList(array $input): void
181
    {
182
        if (
183 173
            $this->collectionType() === self::COLLECTION_TYPE_LIST
184 173
            && array_values($input) !== $input
185
        ) {
186 9
            throw ListException::keysNotAllowed();
187
        }
188 164
    }
189
190
    /**
191
     * @throws MapException
192
     */
193 164
    private function guardInstanceMap(array $input): void
194
    {
195
        if (
196 164
            !empty($input)
197 164
            && $this->collectionType() === self::COLLECTION_TYPE_MAP
198 164
            && array_values($input) === $input
199
        ) {
200 9
            throw MapException::keysRequired();
201
        }
202 155
    }
203
204
    /**
205
     * @throws ImmutabilityException
206
     */
207 99
    private function guardMutability(): void
208
    {
209 99
        if (!$this->isMutable()) {
210 26
            throw new ImmutabilityException();
211
        }
212 73
    }
213
214
    /**
215
     * @param mixed $key
216
     *
217
     * @throws ListException
218
     */
219 73
    private function guardOffsetSetList($key): void
220
    {
221
        if (
222 73
            isset($key)
223 73
            && $this->collectionType() === self::COLLECTION_TYPE_LIST
224 73
            && !isset($this[$key])
225
        ) {
226 5
            throw ListException::keysNotAllowed();
227
        }
228 68
    }
229
230
    /**
231
     * @param mixed $key
232
     *
233
     * @throws MapException
234
     */
235 68
    private function guardOffsetSetMap($key): void
236
    {
237
        if (
238 68
            !isset($key)
239 68
            && $this->collectionType() === self::COLLECTION_TYPE_MAP
240
        ) {
241 5
            throw MapException::keysRequired();
242
        }
243 63
    }
244
245
    /**
246
     * @param mixed $value
247
     *
248
     * @throws InvalidTypeException
249
     */
250 63
    private function guardOffsetSetType($value): void
251
    {
252 63
        if (!$this->checkType($value)) {
253 48
            throw InvalidTypeException::onAdd(static::class, static::getType($value), $this->typeToEnforce());
254
        }
255 15
    }
256
}
257