Collection::offsetGet()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Flying\Struct\Property;
4
5
use Flying\Struct\Common\ComplexPropertyInterface;
6
use Flying\Struct\Exception;
7
8
/**
9
 * Basic implementation of collection of elements as structure property
10
 * Code of this class is partially taken from Doctrine\Common\Collections\Collection
11
 * with respect to authors of original code
12
 */
13
class Collection extends Property implements ComplexPropertyInterface, \IteratorAggregate
14
{
15
    /**
16
     * Collection elements
17
     *
18
     * @var array
19
     */
20
    private $elements = [];
21
    /**
22
     * Cached value of "allowed" configuration option
23
     *
24
     * @var array
25
     */
26
    private $allowed;
27
28
    /**
29
     * {@inheritdoc}
30
     * @return array
31
     */
32 1
    public function getValue()
33
    {
34 1
        return $this->elements;
35
    }
36
37
    /**
38
     * {@inheritdoc}
39
     * @throws \InvalidArgumentException
40
     */
41 46
    public function setValue($value)
42
    {
43 46
        if (!is_array($value)) {
44 6
            if (is_object($value) && method_exists($value, 'toArray')) {
45 1
                $value = $value->toArray();
46 1
            } else {
47 5
                throw new \InvalidArgumentException('Only array values are accepted for collections');
48
            }
49 1
        }
50 41
        $elements = [];
51 41
        foreach ((array)$value as $k => $v) {
52 38
            if ($this->normalize($v, $k)) {
53 38
                $elements[$k] = $v;
54 38
            }
55 41
        }
56 41
        if (count($value) && (!count($elements))) {
57
            // There is no valid elements into given value
58 8
            $this->onInvalidValue($value);
59 8
            return false;
60
        }
61
62 41
        $this->elements = $elements;
63 41
        $this->onChange();
64 41
        return true;
65
    }
66
67
    /**
68
     * Normalize given value to make it compatible with property requirements
69
     *
70
     * @param mixed $value      Given property value (passed by reference)
71
     * @param int|string $key   OPTIONAL Key for given value in a case if multiple values are given
72
     * @return boolean          TRUE if value can be accepted, FALSE otherwise
73
     */
74 96
    protected function normalize(&$value, $key = null)
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
75
    {
76 96
        if (!parent::normalize($value)) {
77 1
            return false;
78
        }
79 96
        $allowed = $this->allowed;
80 96
        if (is_callable($allowed) && (!$allowed($value))) {
81 2
            return false;
82
        }
83 96
        if (is_string($allowed) && ((!is_object($value)) || (!$value instanceof $allowed))) {
84 2
            return false;
85
        }
86 95
        if (is_array($allowed) && (!is_callable($allowed)) && (!in_array($value, $allowed, true))) {
87 22
            return false;
88
        }
89 90
        return true;
90
    }
91
92
    /**
93
     * Invalid value setting handler
94
     *
95
     * @param mixed $value    Invalid value given to property
96
     * @param int|string $key OPTIONAL Key of this value
97
     * @return void
98
     */
99 25
    protected function onInvalidValue($value, $key = null)
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
100
    {
101
102 25
    }
103
104
    /**
105
     * Toggle given element in collection.
106
     * Adds element in collection if it is missed, removes if it is available
107
     *
108
     * @param mixed $element The element to toggle.
109
     * @return void
110
     * @throws \RuntimeException
111
     */
112 7
    public function toggle($element)
113
    {
114 7
        if ($this->normalize($element)) {
115
            // contains() and other methods are not used here
116
            // to avoid performance penalty from multiple calls to normalize()
117 7
            if (in_array($element, $this->elements, true)) {
118
                // Copy from removeElement()
119 4
                $changed = false;
120 View Code Duplication
                do {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
121 4
                    $key = array_search($element, $this->elements, true);
122 4
                    if ($key !== false) {
123 4
                        unset($this->elements[$key]);
124 4
                        $changed = true;
125 4
                    }
126 4
                } while ($key !== false);
127 4
                if ($changed) {
128 4
                    $this->onChange();
129 4
                }
130 4
            } else {
131
                // Copy from add()
132 4
                $this->elements[] = $element;
133 4
                $this->onChange();
134
            }
135 7
        } else {
136 2
            $this->onInvalidValue($element);
137
        }
138 7
    }
139
140
    /**
141
     * Removes the specified element from the collection, if it is found.
142
     *
143
     * @param mixed $element The element to remove.
144
     * @return boolean          TRUE if this collection contained the specified element, FALSE otherwise.
145
     * @throws \RuntimeException
146
     */
147 6
    public function removeElement($element)
148
    {
149 6
        $changed = false;
150 6
        if (!$this->normalize($element)) {
151 2
            $this->onInvalidValue($element);
152 2
            return $changed;
153
        }
154 View Code Duplication
        do {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
155 6
            $key = array_search($element, $this->elements, true);
156 6
            if ($key !== false) {
157 4
                unset($this->elements[$key]);
158 4
                $changed = true;
159 4
            }
160 6
        } while ($key !== false);
161 6
        if ($changed) {
162 4
            $this->onChange();
163 4
        }
164 6
        return $changed;
165
    }
166
167
    /**
168
     * Checks whether an element is contained in the collection.
169
     *
170
     * @param mixed $element The element to search for.
171
     * @return boolean
172
     */
173 4
    public function contains($element)
174
    {
175 4
        if (!$this->normalize($element)) {
176 1
            return false;
177
        }
178 4
        return in_array($element, $this->elements, true);
179
    }
180
181
    /**
182
     * Gets the index/key of a given element.
183
     *
184
     * @param mixed $element The element to search for.
185
     * @return int|string|boolean   The key/index of the element or FALSE if the element was not found.
186
     */
187 4
    public function indexOf($element)
188
    {
189 4
        if (!$this->normalize($element)) {
190 1
            return false;
191
        }
192 4
        return array_search($element, $this->elements, true);
193
    }
194
195
    /**
196
     * Gets all keys/indices of the collection.
197
     *
198
     * @return array
199
     */
200 1
    public function getKeys()
201
    {
202 1
        return array_keys($this->elements);
203
    }
204
205
    /**
206
     * Gets all values of the collection.
207
     *
208
     * @return array
209
     */
210 3
    public function getValues()
211
    {
212 3
        return array_values($this->elements);
213
    }
214
215
    /**
216
     * Checks whether the collection is empty.
217
     *
218
     * @return boolean
219
     */
220 2
    public function isEmpty()
221
    {
222 2
        return !$this->elements;
223
    }
224
225
    /**
226
     * Clears the collection, removing all elements.
227
     *
228
     * @return void
229
     * @throws \RuntimeException
230
     */
231 4
    public function clear()
232
    {
233 4
        $this->elements = [];
234 4
        $this->onChange();
235 4
    }
236
237
    /**
238
     * {@inheritdoc}
239
     */
240 20
    public function count()
241
    {
242 20
        return count($this->elements);
243
    }
244
245
    /**
246
     * {@inheritdoc}
247
     */
248 2
    public function getIterator()
249
    {
250 2
        return new \ArrayIterator($this->elements);
251
    }
252
253
    /**
254
     * {@inheritDoc}
255
     */
256 4
    public function offsetExists($offset)
257
    {
258 4
        return $this->containsKey($offset);
259
    }
260
261
    /**
262
     * Checks whether the collection contains an element with the specified key/index.
263
     *
264
     * @param string|integer $key The key/index to check for.
265
     * @return boolean
266
     */
267 8
    public function containsKey($key)
268
    {
269 8
        return array_key_exists($key, $this->elements) || isset($this->elements[$key]);
270
    }
271
272
    /**
273
     * {@inheritDoc}
274
     */
275 2
    public function offsetGet($offset)
276
    {
277 2
        return $this->get($offset);
278
    }
279
280
    /**
281
     * Gets the element at the specified key/index.
282
     *
283
     * @param string|integer $key The key/index of the element to retrieve.
284
     * @return mixed
285
     */
286 3
    public function get($key)
287
    {
288 3
        if (array_key_exists($key, $this->elements)) {
289 3
            return $this->elements[$key];
290
        }
291 1
        return null;
292
    }
293
294
    /**
295
     * {@inheritDoc}
296
     * @throws \RuntimeException
297
     */
298 9
    public function offsetSet($offset, $value)
299
    {
300 9
        if ($offset !== null) {
301 4
            $this->set($offset, $value);
302 4
        } else {
303 6
            $this->add($value);
304
        }
305 9
    }
306
307
    /**
308
     * Sets an element in the collection at the specified key/index.
309
     *
310
     * @param string|integer $key The key/index of the element to set.
311
     * @param mixed $element      The element to set.
312
     * @return void
313
     * @throws \RuntimeException
314
     */
315 8
    public function set($key, $element)
316
    {
317 8
        if ($this->normalize($element)) {
318 8
            $this->elements[$key] = $element;
319 8
            $this->onChange();
320 8
        } else {
321 2
            $this->onInvalidValue($element, $key);
322
        }
323 8
    }
324
325
    /**
326
     * Adds an element at the end of the collection.
327
     *
328
     * @param mixed $element The element to add.
329
     * @return void
330
     * @throws \RuntimeException
331
     */
332 26
    public function add($element)
333
    {
334 26
        if ($this->normalize($element)) {
335 20
            $this->elements[] = $element;
336 20
            $this->onChange();
337 20
        } else {
338 14
            $this->onInvalidValue($element);
339
        }
340 26
    }
341
342
    /**
343
     * {@inheritDoc}
344
     * @throws \RuntimeException
345
     */
346 6
    public function offsetUnset($offset)
347
    {
348 6
        $this->remove($offset);
349 6
    }
350
351
    /**
352
     * Removes the element at the specified index from the collection.
353
     *
354
     * @param string|integer $key The kex/index of the element to remove.
355
     * @return mixed                The removed element or NULL, if the collection did not contain the element.
356
     * @throws \RuntimeException
357
     */
358 12
    public function remove($key)
359
    {
360 12
        if (array_key_exists($key, $this->elements) || isset($this->elements[$key])) {
361 7
            $removed = $this->elements[$key];
362 7
            unset($this->elements[$key]);
363 7
            $this->onChange();
364 7
            return $removed;
365
        }
366 8
        return null;
367
    }
368
369
    /**
370
     * {@inheritdoc}
371
     */
372 22
    public function toArray()
373
    {
374 22
        return $this->elements;
375
    }
376
377
    /**
378
     * {@inheritdoc}
379
     * @throws \InvalidArgumentException
380
     */
381 108
    public function validateConfig($name, &$value)
382
    {
383
        switch ($name) {
384 108 View Code Duplication
            case 'default':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
385 33
                if (!is_array($value)) {
386 7
                    if (is_object($value) && method_exists($value, 'toArray')) {
387 1
                        $value = $value->toArray();
388 1
                    } else {
389 6
                        throw new \InvalidArgumentException('Only arrays are accepted as default values for collection properties');
390
                    }
391 1
                }
392 27
                break;
393 97
            case 'allowed':
394 65
                $valid = false;
395 65
                if (($value === null) || is_callable($value)) {
396
                    // Explicitly defined validator or empty validator
397 4
                    $valid = true;
398 65
                } elseif (is_array($value) && (count($value) === 1) && isset($value[0]) && is_string($value[0])) {
399
                    // This is probably validator defined through annotation's "allowed" parameter
400 3
                    $v = $value[0];
401 3
                    if (class_exists($v)) {
402
                        // Class name for validation
403 1
                        $value = $v;
404 1
                        $valid = true;
405 3 View Code Duplication
                    } elseif (method_exists($this, $v)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
406
                        // Name of validation method
407
                        $value = [$this, $v];
408
                        $valid = true;
409
                    } else {
410
                        // Explicitly given list of valid values
411 2
                        $valid = true;
412
                    }
413 61
                } elseif (is_string($value)) {
414 5
                    if (class_exists($value)) {
415
                        // Explicitly given class name for validation
416 3
                        $valid = true;
417 5 View Code Duplication
                    } elseif (method_exists($this, $value)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
418
                        // Explicitly given name of validation method
419 1
                        $value = [$this, $value];
420 1
                        $valid = true;
421 1
                    }
422 58
                } /** @noinspection NotOptimalIfConditionsInspection */ elseif (is_array($value)) {
423
                    // Explicitly given list of valid values
424 49
                    $valid = true;
425 49
                }
426 65
                if (!$valid) {
427 5
                    throw new \InvalidArgumentException('Unable to recognize given validator for collection');
428
                }
429 60
                break;
430 73
            default:
431 73
                return parent::validateConfig($name, $value);
432 73
        }
433 67
        return true;
434
    }
435
436
    /**
437
     * {@inheritdoc}
438
     */
439 95
    public function reset()
440
    {
441
        // No change notification should be made for reset,
442
        // property value should be set to its default
443 95
        $flag = $this->skipNotify;
444 95
        $this->skipNotify = true;
445
        /** @var array $default */
446 95
        $default = (array)$this->getConfig('default');
447 95
        foreach ($default as $k => &$v) {
448 25
            if (!$this->normalize($v, $k)) {
449
                throw new Exception('Default value for property class ' . get_class($this) . ' is not acceptable for property validation rules');
450
            }
451 95
        }
452 95
        unset($v);
453 95
        $this->elements = $default;
454 95
        $this->skipNotify = $flag;
455 95
    }
456
457
    /**
458
     * {@inheritdoc}
459
     */
460 1
    protected function initConfig()
461
    {
462 1
        parent::initConfig();
463 1
        $this->mergeConfig([
464 1
            'default' => [], // Default value for collection
465 1
            'allowed' => null, // Either list of allowed values for collection elements
466
            // or callable to test if element is allowed to be in collection
467 1
        ]);
468 1
    }
469
470
    /**
471
     * {@inheritdoc}
472
     */
473 67
    protected function onConfigChange($name, $value)
474
    {
475
        /** @noinspection DegradedSwitchInspection */
476
        switch ($name) {
477 67
            case 'allowed':
478 60
                $this->allowed = $value;
479 60
                break;
480 28
            default:
481 28
                parent::onConfigChange($name, $value);
482 28
                break;
483 28
        }
484 67
    }
485
}
486