Passed
Push — 1.x ( 1cbeeb...e7c4f5 )
by Ulises Jeremias
02:16
created

Map::__isset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 1
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\Interfaces\Allocated as AllocatedInterface;
15
use Mbh\Traits\SquaredCapacity;
16
use Traversable;
17
use ArrayAccess;
18
use IteratorAggregate;
19
use OutOfBoundsException;
20
use OutOfRangeException;
21
use UnderflowException;
22
23
/**
24
 * A Map is a sequential collection of key-value pairs, almost identical to an
25
 * array used in a similar context. Keys can be any type, but must be unique.
26
 *
27
 * @package structures
28
 * @author Ulises Jeremias Cornejo Fandos <[email protected]>
29
 */
30
class Map implements AllocatedInterface, ArrayAccess, CollectionInterface, IteratorAggregate
31
{
32
    use Traits\Collection;
33
    use Traits\Functional;
34
    use SquaredCapacity;
35
36
    const MIN_CAPACITY = 8.0;
37
38
    /**
39
     * @var FixedArray internal array to store pairs
40
     */
41
    private $pairs;
42
43
    /**
44
     * Creates a new instance.
45
     *
46
     * @param array|Traversable $pairs
47
     */
48
    public function __construct($pairs = [])
49
    {
50
        $this->pairs = FixedArray::empty();
51
52
        $this->putAll($pairs);
53
    }
54
55
    /**
56
     * @throws OutOfBoundsException
57
     * @param string $name
58
     * @return mixed
59
     */
60
    public function __get($name)
61
    {
62
        return $this->offsetGet($name);
63
    }
64
    /**
65
     * @param string $name
66
     * @return bool
67
     */
68
    public function __isset($name)
69
    {
70
        return $this->offsetExists($name);
71
    }
72
    public function __set($name, $value)
73
    {
74
        $this->offsetSet($name, $value);
75
    }
76
    public function __unset($name)
77
    {
78
        $this->offsetUnset($name);
79
    }
80
81
    /**
82
     * You should use this if you want to convert a n object into a map
83
     *
84
     * @param object $object
85
     * @return Map
86
     */
87
    public static function fromObject($object)
88
    {
89
        $payloadValue = get_object_vars($object);
90
        return static::fromArray($payloadValue);
0 ignored issues
show
Bug introduced by
The method fromArray() does not exist on Mbh\Collection\Map. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

90
        return static::/** @scrutinizer ignore-call */ fromArray($payloadValue);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
91
    }
92
93
    /**
94
     * @inheritDoc
95
     */
96
    public function clear()
97
    {
98
        $this->pairs->clear();
99
        $this->capacity = self::MIN_CAPACITY;
100
    }
101
102
    /**
103
     * @inheritDoc
104
     */
105
    public function count(): int
106
    {
107
        return count($this->pairs);
108
    }
109
110
    /**
111
     * Completely removes a pair from the internal array by position. It is
112
     * important to remove it from the array and not just use 'unset'.
113
     */
114
    private function delete(int $position)
115
    {
116
        $pair = $this->pairs->remove($position);
117
118
        $this->checkCapacity();
119
        return $pair->value;
120
    }
121
122
    /**
123
     * Return the first Pair from the Map
124
     *
125
     * @return Pair
126
     *
127
     * @throws UnderflowException
128
     */
129
    public function first(): Pair
130
    {
131
        if ($this->isEmpty()) {
132
            throw new UnderflowException();
133
        }
134
135
        return $this->pairs->first();
136
    }
137
138
    /**
139
     * Returns the value associated with a key, or an optional default if the
140
     * key is not associated with a value.
141
     *
142
     * @param mixed $key
143
     * @param mixed $default
144
     *
145
     * @return mixed The associated value or fallback default if provided.
146
     *
147
     * @throws OutOfBoundsException if no default was provided and the key is
148
     *                               not associated with a value.
149
     */
150
    public function get($key, $default = null)
151
    {
152
        if (($pair = $this->lookupKey($key))) {
153
            return $pair->value;
154
        }
155
156
        // Check if a default was provided.
157
        if (func_num_args() === 1) {
158
            throw new OutOfBoundsException();
159
        }
160
161
        return $default;
162
    }
163
164
    /**
165
     * Returns whether an association a given key exists.
166
     *
167
     * @param mixed $key
168
     *
169
     * @return bool
170
     */
171
    public function hasKey($key): bool
172
    {
173
        return $this->lookupKey($key) !== null;
174
    }
175
176
    /**
177
     * Returns whether an association for a given value exists.
178
     *
179
     * @param mixed $value
180
     *
181
     * @return bool
182
     */
183
    public function hasValue($value): bool
184
    {
185
        return $this->lookupValue($value) !== null;
186
    }
187
188
    /**
189
     * Returns a set of all the keys in the map.
190
     *
191
     * @return Set
192
     */
193
    public function keys(): Set
194
    {
195
        return new Set($this->pairs->map(function ($pair) {
196
            return $pair->key;
197
        }));
198
    }
199
200
    /**
201
     * Determines whether two keys are equal.
202
     *
203
     * @param mixed $a
204
     * @param mixed $b
205
     *
206
     * @return bool
207
     */
208
    private function keysAreEqual($a, $b): bool
209
    {
210
        if (is_object($a) && $a instanceof HashableInterface) {
211
            return get_class($a) === get_class($b) && $a->equals($b);
212
        }
213
214
        return $a === $b;
215
    }
216
217
    /**
218
     * Return the last Pair from the Map
219
     *
220
     * @return Pair
221
     *
222
     * @throws UnderflowException
223
     */
224
    public function last(): Pair
225
    {
226
        if ($this->isEmpty()) {
227
            throw new UnderflowException();
228
        }
229
230
        return $this->pairs->last();
231
    }
232
233
234
    /**
235
     * Attempts to look up a key in the table.
236
     *
237
     * @param $key
238
     *
239
     * @return Pair|null
240
     */
241
    private function lookupKey($key)
242
    {
243
        foreach ($this->pairs as $pair) {
244
            if ($this->keysAreEqual($pair->key, $key)) {
245
                return $pair;
246
            }
247
        }
248
    }
249
250
    /**
251
     * Attempts to look up a key in the table.
252
     *
253
     * @param $value
254
     *
255
     * @return Pair|null
256
     */
257
    private function lookupValue($value)
258
    {
259
        foreach ($this->pairs as $pair) {
260
            if ($pair->value === $value) {
261
                return $pair;
262
            }
263
        }
264
    }
265
266
    /**
267
     * Returns a sequence of pairs representing all associations.
268
     *
269
     * @return SequenceableInterface
270
     */
271
    public function pairs(): SequenceableInterface
272
    {
273
        return $this->pairs->map(function ($pair) {
274
            return $pair->copy();
275
        });
276
    }
277
278
    /**
279
     * Associates a key with a value, replacing a previous association if there
280
     * was one.
281
     *
282
     * @param mixed $key
283
     * @param mixed $value
284
     */
285
    public function put($key, $value)
286
    {
287
        $pair = $this->lookupKey($key);
288
        if ($pair) {
289
            $pair->value = $value;
290
        } else {
291
            $this->checkCapacity();
292
            $this->pairs[] = new Pair($key, $value);
293
        }
294
    }
295
296
    /**
297
     * Creates associations for all keys and corresponding values of either an
298
     * array or iterable object.
299
     *
300
     * @param Traversable|array $values
301
     */
302
    public function putAll($values)
303
    {
304
        foreach ($values as $key => $value) {
305
            $this->put($key, $value);
306
        }
307
    }
308
309
    /**
310
     * Removes a key's association from the map and returns the associated value
311
     * or a provided default if provided.
312
     *
313
     * @param mixed $key
314
     * @param mixed $default
315
     *
316
     * @return mixed The associated value or fallback default if provided.
317
     *
318
     * @throws OutOfBoundsException if no default was provided and the key is
319
     *                               not associated with a value.
320
     */
321
    public function remove($key, $default = null)
322
    {
323
        foreach ($this->pairs as $position => $pair) {
324
            if ($this->keysAreEqual($pair->key, $key)) {
325
                return $this->delete($position);
326
            }
327
        }
328
329
        // Check if a default was provided
330
        if (func_num_args() === 1) {
331
            throw new OutOfBoundsException();
332
        }
333
334
        return $default;
335
    }
336
337
    /**
338
     * Return the pair at a specified position in the Map
339
     *
340
     * @param int $position
341
     *
342
     * @return Pair
343
     *
344
     * @throws OutOfRangeException
345
     */
346
    public function skip(int $position): Pair
347
    {
348
        if ($position < 0 || $position >= count($this->pairs)) {
349
            throw new OutOfRangeException();
350
        }
351
352
        return $this->pairs[$position]->copy();
353
    }
354
355
    /**
356
     * @inheritDoc
357
     */
358
    public function toArray(): array
359
    {
360
        $array = [];
361
        foreach ($this->pairs as $pair) {
362
            $array[$pair->key] = $pair->value;
363
        }
364
365
        return $array;
366
    }
367
368
    /**
369
     * Returns a sequence of all the associated values in the Map.
370
     *
371
     * @return SequenceableInterface
372
     */
373
    public function values(): SequenceableInterface
374
    {
375
        return $this->pairs->map(function ($pair) {
376
            return $pair->value;
377
        });
378
    }
379
380
    /**
381
     * @inheritDoc
382
     */
383
    public function getIterator()
384
    {
385
        foreach ($this->pairs as $pair) {
386
            yield $pair->key => $pair->value;
387
        }
388
    }
389
390
    /**
391
     * Returns a representation to be used for var_dump and print_r.
392
     */
393
    public function __debugInfo()
394
    {
395
        return $this->pairs()->toArray();
396
    }
397
398
    /**
399
     * @inheritdoc
400
     */
401
    public function offsetSet($offset, $value)
402
    {
403
        $this->put($offset, $value);
404
    }
405
406
    /**
407
     * @inheritdoc
408
     *
409
     * @throws OutOfBoundsException
410
     */
411
    public function &offsetGet($offset)
412
    {
413
        $pair = $this->lookupKey($offset);
414
        if ($pair) {
415
            return $pair->value;
416
        }
417
418
        throw new OutOfBoundsException();
419
    }
420
421
    /**
422
     * @inheritdoc
423
     */
424
    public function offsetUnset($offset)
425
    {
426
        $this->remove($offset, null);
427
    }
428
429
    /**
430
     * @inheritdoc
431
     */
432
    public function offsetExists($offset)
433
    {
434
        return $this->get($offset, null) !== null;
435
    }
436
}
437