Passed
Push — master ( 572884...88a5e5 )
by Chema
01:20 queued 11s
created

AbstractTypedArray   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 243
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 82
dl 0
loc 243
ccs 90
cts 90
cp 1
rs 9.1199
c 2
b 0
f 0
wmc 41

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A guardChildTypeToEnforce() 0 7 3
A collectionType() 0 3 1
A guardOffsetSetList() 0 8 4
A isMutable() 0 3 1
A checkType() 0 12 4
A guardInstanceTypeToEnforce() 0 8 3
A guardMutability() 0 4 2
A guardOffsetSetMap() 0 7 3
A checkForValidClass() 0 4 2
A guardOffsetSetType() 0 7 2
A checkForScalar() 0 3 1
A offsetSet() 0 8 1
A offsetUnset() 0 5 1
A guardInstanceList() 0 7 3
A guardInstanceMap() 0 8 4
A getType() 0 5 2
A guardChildCollectionType() 0 4 2
A isNullAllowed() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like AbstractTypedArray often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractTypedArray, and based on these observations, apply Extract Interface, too.

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