Map   B
last analyzed

Complexity

Total Complexity 50

Size/Duplication

Total Lines 404
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 50
eloc 80
dl 0
loc 404
rs 8.4
c 0
b 0
f 0

31 Methods

Rating   Name   Duplication   Size   Complexity  
A hasValue() 0 3 1
A hasKey() 0 3 1
A keys() 0 4 1
A pairs() 0 4 1
A __debugInfo() 0 3 1
A putAll() 0 4 2
A first() 0 4 1
A offsetExists() 0 3 1
A __get() 0 3 1
A fromObject() 0 4 1
A clear() 0 4 1
A getIterator() 0 4 2
A offsetSet() 0 3 1
A values() 0 4 1
A __isset() 0 3 1
A count() 0 3 1
A __set() 0 3 1
A offsetUnset() 0 3 1
A __unset() 0 3 1
A last() 0 4 1
A remove() 0 14 4
A skip() 0 7 3
A delete() 0 6 1
A lookupKey() 0 5 3
A offsetGet() 0 8 2
A get() 0 12 3
A toArray() 0 8 2
A lookupValue() 0 5 3
A put() 0 8 2
A __construct() 0 5 1
A keysAreEqual() 0 7 4

How to fix   Complexity   

Complex Class

Complex classes like Map 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 Map, and based on these observations, apply Extract Interface, too.

1
<?php namespace Mbh\Collection;
2
3
/**
4
 * MBHFramework
5
 *
6
 * @link      https://github.com/MBHFramework/mbh-framework
7
 * @copyright Copyright (c) 2017 Ulises Jeremias Cornejo Fandos
8
 * @license   https://github.com/MBHFramework/mbh-framework/blob/master/LICENSE (MIT License)
9
 */
10
11
use Mbh\Collection\Interfaces\Collection as CollectionInterface;
12
use Mbh\Collection\Interfaces\Hashable as HashableInterface;
13
use Mbh\Collection\Interfaces\Sequenceable as SequenceableInterface;
14
use Mbh\Collection\Internal\PriorityNode;
15
use Mbh\Interfaces\Allocated as AllocatedInterface;
16
use Mbh\Traits\SquaredCapacity;
17
use Mbh\Traits\EmptyGuard;
18
use Traversable;
19
use ArrayAccess;
20
use IteratorAggregate;
21
use OutOfBoundsException;
22
use OutOfRangeException;
23
use UnderflowException;
24
25
/**
26
 * A Map is a sequential collection of key-value pairs, almost identical to an
27
 * array used in a similar context. Keys can be any type, but must be unique.
28
 *
29
 * @package structures
30
 * @author Ulises Jeremias Cornejo Fandos <[email protected]>
31
 */
32
class Map implements AllocatedInterface, ArrayAccess, CollectionInterface, IteratorAggregate
33
{
34
    use Traits\Collection;
35
    use Traits\Functional;
36
    use Traits\Builder;
37
    use SquaredCapacity;
38
    use EmptyGuard;
39
40
    const MIN_CAPACITY = 8.0;
41
42
    /**
43
     * @var FixedArray internal array to store pairs
44
     */
45
    private $pairs;
46
47
    /**
48
     * Creates a new instance.
49
     *
50
     * @param array|Traversable $pairs
51
     */
52
    public function __construct($pairs = [])
53
    {
54
        $this->pairs = FixedArray::empty();
55
56
        $this->putAll($pairs);
57
    }
58
59
    /**
60
     * @throws OutOfBoundsException
61
     * @param string $name
62
     * @return mixed
63
     */
64
    public function __get($name)
65
    {
66
        return $this->offsetGet($name);
67
    }
68
69
    /**
70
     * @param string $name
71
     * @return bool
72
     */
73
    public function __isset($name)
74
    {
75
        return $this->offsetExists($name);
76
    }
77
78
    public function __set($name, $value)
79
    {
80
        $this->offsetSet($name, $value);
81
    }
82
83
    public function __unset($name)
84
    {
85
        $this->offsetUnset($name);
86
    }
87
88
    /**
89
     * You should use this if you want to convert a n object into a map
90
     *
91
     * @param object $object
92
     * @return Map
93
     */
94
    public static function fromObject($object)
95
    {
96
        $payloadValue = get_object_vars($object);
97
        return static::fromArray($payloadValue);
98
    }
99
100
    /**
101
     * @inheritDoc
102
     */
103
    public function clear()
104
    {
105
        $this->pairs->clear();
106
        $this->capacity = self::MIN_CAPACITY;
107
    }
108
109
    /**
110
     * @inheritDoc
111
     */
112
    public function count(): int
113
    {
114
        return count($this->pairs);
115
    }
116
117
    /**
118
     * Completely removes a pair from the internal array by position. It is
119
     * important to remove it from the array and not just use 'unset'.
120
     */
121
    private function delete(int $position)
122
    {
123
        $pair = $this->pairs->remove($position);
124
125
        $this->checkCapacity();
126
        return $pair->value;
127
    }
128
129
    /**
130
     * Return the first Pair from the Map
131
     *
132
     * @return Pair
133
     *
134
     * @throws UnderflowException
135
     */
136
    public function first(): Pair
137
    {
138
        $this->emptyGuard(__METHOD__);
139
        return $this->pairs->first();
140
    }
141
142
    /**
143
     * Returns the value associated with a key, or an optional default if the
144
     * key is not associated with a value.
145
     *
146
     * @param mixed $key
147
     * @param mixed $default
148
     *
149
     * @return mixed The associated value or fallback default if provided.
150
     *
151
     * @throws OutOfBoundsException if no default was provided and the key is
152
     *                               not associated with a value.
153
     */
154
    public function get($key, $default = null)
155
    {
156
        if (($pair = $this->lookupKey($key))) {
157
            return $pair->value;
158
        }
159
160
        // Check if a default was provided.
161
        if (func_num_args() === 1) {
162
            throw new OutOfBoundsException();
163
        }
164
165
        return $default;
166
    }
167
168
    /**
169
     * Returns whether an association a given key exists.
170
     *
171
     * @param mixed $key
172
     *
173
     * @return bool
174
     */
175
    public function hasKey($key): bool
176
    {
177
        return $this->lookupKey($key) !== null;
178
    }
179
180
    /**
181
     * Returns whether an association for a given value exists.
182
     *
183
     * @param mixed $value
184
     *
185
     * @return bool
186
     */
187
    public function hasValue($value): bool
188
    {
189
        return $this->lookupValue($value) !== null;
190
    }
191
192
    /**
193
     * Returns a set of all the keys in the map.
194
     *
195
     * @return Set
196
     */
197
    public function keys(): Set
198
    {
199
        return new Set($this->pairs->map(function($pair) {
200
            return $pair->key;
201
        }));
202
    }
203
204
    /**
205
     * Determines whether two keys are equal.
206
     *
207
     * @param mixed $a
208
     * @param mixed $b
209
     *
210
     * @return bool
211
     */
212
    private function keysAreEqual($a, $b): bool
213
    {
214
        if (is_object($a) && $a instanceof HashableInterface) {
215
            return get_class($a) === get_class($b) && $a->equals($b);
216
        }
217
218
        return $a === $b;
219
    }
220
221
    /**
222
     * Return the last Pair from the Map
223
     *
224
     * @return Pair
225
     *
226
     * @throws UnderflowException
227
     */
228
    public function last(): Pair
229
    {
230
        $this->emptyGuard(__METHOD__);
231
        return $this->pairs->last();
232
    }
233
234
235
    /**
236
     * Attempts to look up a key in the table.
237
     *
238
     * @param $key
239
     *
240
     * @return Pair|null
241
     */
242
    private function lookupKey($key)
243
    {
244
        foreach ($this->pairs as $pair) {
245
            if ($this->keysAreEqual($pair->key, $key)) {
246
                return $pair;
247
            }
248
        }
249
    }
250
251
    /**
252
     * Attempts to look up a key in the table.
253
     *
254
     * @param $value
255
     *
256
     * @return Pair|null
257
     */
258
    private function lookupValue($value)
259
    {
260
        foreach ($this->pairs as $pair) {
261
            if ($pair->value === $value) {
262
                return $pair;
263
            }
264
        }
265
    }
266
267
    /**
268
     * Returns a sequence of pairs representing all associations.
269
     *
270
     * @return SequenceableInterface
271
     */
272
    public function pairs(): SequenceableInterface
273
    {
274
        return $this->pairs->map(function($pair) {
275
            return $pair->copy();
276
        });
277
    }
278
279
    /**
280
     * Associates a key with a value, replacing a previous association if there
281
     * was one.
282
     *
283
     * @param mixed $key
284
     * @param mixed $value
285
     */
286
    public function put($key, $value)
287
    {
288
        $pair = $this->lookupKey($key);
289
        if ($pair) {
290
            $pair->value = $value;
291
        } else {
292
            $this->checkCapacity();
293
            $this->pairs[] = new Pair($key, $value);
294
        }
295
    }
296
297
    /**
298
     * Creates associations for all keys and corresponding values of either an
299
     * array or iterable object.
300
     *
301
     * @param Traversable|array $values
302
     */
303
    public function putAll($values)
304
    {
305
        foreach ($values as $key => $value) {
306
            $this->put($key, $value);
307
        }
308
    }
309
310
    /**
311
     * Removes a key's association from the map and returns the associated value
312
     * or a provided default if provided.
313
     *
314
     * @param mixed $key
315
     * @param mixed $default
316
     *
317
     * @return mixed The associated value or fallback default if provided.
318
     *
319
     * @throws OutOfBoundsException if no default was provided and the key is
320
     *                               not associated with a value.
321
     */
322
    public function remove($key, $default = null)
323
    {
324
        foreach ($this->pairs as $position => $pair) {
325
            if ($this->keysAreEqual($pair->key, $key)) {
326
                return $this->delete($position);
327
            }
328
        }
329
330
        // Check if a default was provided
331
        if (func_num_args() === 1) {
332
            throw new OutOfBoundsException();
333
        }
334
335
        return $default;
336
    }
337
338
    /**
339
     * Return the pair at a specified position in the Map
340
     *
341
     * @param int $position
342
     *
343
     * @return Pair
344
     *
345
     * @throws OutOfRangeException
346
     */
347
    public function skip(int $position): Pair
348
    {
349
        if ($position < 0 || $position >= count($this->pairs)) {
350
            throw new OutOfRangeException();
351
        }
352
353
        return $this->pairs[$position]->copy();
354
    }
355
356
    /**
357
     * @inheritDoc
358
     */
359
    public function toArray(): array
360
    {
361
        $array = [];
362
        foreach ($this->pairs as $pair) {
363
            $array[$pair->key] = $pair->value;
364
        }
365
366
        return $array;
367
    }
368
369
    /**
370
     * Returns a sequence of all the associated values in the Map.
371
     *
372
     * @return SequenceableInterface
373
     */
374
    public function values(): SequenceableInterface
375
    {
376
        return $this->pairs->map(function($pair) {
377
            return $pair->value;
378
        });
379
    }
380
381
    /**
382
     * @inheritDoc
383
     */
384
    public function getIterator()
385
    {
386
        foreach ($this->pairs as $pair) {
387
            yield $pair->key => $pair->value;
388
        }
389
    }
390
391
    /**
392
     * Returns a representation to be used for var_dump and print_r.
393
     */
394
    public function __debugInfo()
395
    {
396
        return $this->pairs()->toArray();
397
    }
398
399
    /**
400
     * @inheritdoc
401
     */
402
    public function offsetSet($offset, $value)
403
    {
404
        $this->put($offset, $value);
405
    }
406
407
    /**
408
     * @inheritdoc
409
     *
410
     * @throws OutOfBoundsException
411
     */
412
    public function &offsetGet($offset)
413
    {
414
        $pair = $this->lookupKey($offset);
415
        if ($pair) {
416
            return $pair->value;
417
        }
418
419
        throw new OutOfBoundsException();
420
    }
421
422
    /**
423
     * @inheritdoc
424
     */
425
    public function offsetUnset($offset)
426
    {
427
        $this->remove($offset, null);
428
    }
429
430
    /**
431
     * @inheritdoc
432
     */
433
    public function offsetExists($offset)
434
    {
435
        return $this->get($offset, null) !== null;
436
    }
437
}
438