Completed
Push — master ( 3a155d...76db6e )
by Antonio Carlos
03:38
created

Coollection   F

Complexity

Total Complexity 73

Size/Duplication

Total Lines 641
Duplicated Lines 5.62 %

Coupling/Cohesion

Components 2
Dependencies 8

Test Coverage

Coverage 97.22%

Importance

Changes 0
Metric Value
dl 36
loc 641
ccs 175
cts 180
cp 0.9722
rs 2.519
c 0
b 0
f 0
wmc 73
lcom 2
cbo 8

36 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A __call() 0 8 2
A call() 0 13 1
A coollectizeItems() 18 18 4
A toArray() 0 6 2
A __get() 0 22 4
A __toArray() 18 18 4
A get() 0 15 3
B getArrayableItems() 0 20 8
A getArrayKey() 0 25 3
A snakeCase() 0 4 1
A stringCase() 0 6 1
A kebabCase() 0 4 1
A camelCase() 0 4 1
A getByPropertyName() 0 12 3
A runViaLaravelCollection() 0 14 2
A methodMustReturnArray() 0 4 1
A setRaiseExceptionOnNull() 0 4 1
A isArrayable() 0 9 6
A __wrap() 0 10 3
A offsetExists() 0 4 1
A offsetGet() 0 8 2
A offsetSet() 0 8 2
A offsetUnset() 0 4 1
A coollectizeCallbacks() 0 13 4
A coollectizeCallback() 0 6 1
A coollectizeCallbackForReduce() 0 6 1
A overwrite() 0 9 1
A toJson() 0 4 1
A jsonSerialize() 0 4 1
A count() 0 4 1
A getIterator() 0 4 1
A sortByKey() 0 8 1
A sortByKeysRecursive() 0 8 1
A getItems() 0 4 1
A __isset() 0 6 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Coollection 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Coollection, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PragmaRX\Coollection\Package;
4
5
use Closure;
6
use Countable;
7
use Exception;
8
use ArrayAccess;
9
use JsonSerializable;
10
use IteratorAggregate;
11
use IlluminateAgnostic\Str\Support\Str;
12
use IlluminateAgnostic\Arr\Support\Arr;
13
use IlluminateAgnostic\Collection\Support\Collection;
14
use IlluminateAgnostic\Collection\Support\Traits\Macroable;
15
use IlluminateAgnostic\Collection\Contracts\Support\Jsonable;
16
use IlluminateAgnostic\Collection\Contracts\Support\Arrayable;
17
use IlluminateAgnostic\Collection\Support\HigherOrderCollectionProxy;
18
use IlluminateAgnostic\Collection\Support\Collection as TightencoCollect;
19
20
class Coollection implements
21
    ArrayAccess,
22
    Arrayable,
23
    Countable,
24
    IteratorAggregate,
25
    Jsonable,
26
    JsonSerializable
27
{
28
    use Macroable {
29
        __call as __callMacro;
30
    }
31
32
    /**
33
     * Consants
34
     */
35
    const NOT_FOUND = '!__NOT__FOUND__!';
36
37
    /**
38
     * The items contained in the collection.
39
     *
40
     * @var array
41
     */
42
    protected $items;
43
44
    /**
45
     * Raise exception on null.
46
     *
47
     * @static boolean
48
     */
49
    public static $raiseExceptionOnNull = true;
50
51
    /**
52
     * The methods that can be proxied.
53
     *
54
     * @var array
55
     */
56
    protected static $proxies = [
57
        'average',
58
        'avg',
59
        'contains',
60
        'each',
61
        'every',
62
        'filter',
63
        'first',
64
        'flatMap',
65
        'keyBy',
66
        'map',
67
        'partition',
68
        'reject',
69
        'sortBy',
70
        'sortByDesc',
71
        'sum',
72
    ];
73
74
    /**
75
     * The methods that must return array.
76
     *
77
     * @var array
78
     */
79
    protected static $returnArray = ['toArray', 'jsonSerialize', 'unwrap'];
80
81
    /**
82
     * Cache __toArray results.
83
     *
84
     * @var array
85
     */
86
    protected $cache = [];
87
88
    /**
89
     * Create a new coollection.
90
     *
91
     * @param  mixed  $items
92
     */
93 120
    public function __construct($items = [])
94
    {
95 120
        $this->items = $this->getArrayableItems($items);
96 120
    }
97
98
    /**
99
     * Transfer calls to Illuminate\Collection.
100
     *
101
     * @param $name
102
     * @param $arguments
103
     * @return mixed|static
104
     */
105 120
    public function __call($name, $arguments)
106
    {
107 120
        if (static::hasMacro($name)) {
108 1
            return $this->__callMacro($name, $arguments);
109
        }
110
111 120
        return $this->call($name, $arguments);
112
    }
113
114
    /**
115
     * Transfer calls to Illuminate\Collection.
116
     *
117
     * @param $name
118
     * @param $arguments
119
     * @return mixed|static
120
     */
121 120
    public function call($name, $arguments = [])
122
    {
123
        return $this->runViaLaravelCollection(function ($collection) use (
124 120
            $name,
125 120
            $arguments
126
        ) {
127 120
            return call_user_func_array(
128 120
                [$collection, $name],
129 120
                $this->coollectizeCallbacks($this->__toArray($arguments), $name)
130
            );
131 120
        },
132 120
        $name);
133
    }
134
135
    /**
136
     * Get the collection of items as a plain array.
137
     *
138
     * @param $value
139
     * @return Coollection
140
     */
141 120 View Code Duplication
    public function coollectizeItems($value)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
142
    {
143 120
        $value = is_null($value) ? $this->items : $value;
144
145 120
        if (!$this->isArrayable($value)) {
146 102
            return $value;
147
        }
148
149
        $result = array_map(function ($value) {
150 120
            if ($this->isArrayable($value)) {
151 120
                return new static($value);
152
            }
153
154 120
            return $value;
155 120
        }, $this->getArrayableItems($value));
156
157 120
        return new static($result);
158
    }
159
160
    /**
161
     * Get the collection of items as a plain array.
162
     *
163
     * @return array
164
     */
165 120
    public function toArray()
166
    {
167
        return array_map(function ($value) {
168 120
            return $value instanceof Arrayable ? $value->toArray() : $value;
169 120
        }, $this->items);
170
    }
171
172
    /**
173
     * Dynamically access collection proxies.
174
     *
175
     * @param  string  $key
176
     * @return mixed|static
177
     *
178
     * @throws \Exception
179
     */
180 94
    public function __get($key)
181
    {
182 94
        if (($value = $this->getByPropertyName($key)) !== static::NOT_FOUND) {
183 91
            return $value;
184
        }
185
186 4
        if (!in_array($key, static::$proxies)) {
187 3
            if (static::$raiseExceptionOnNull) {
188 1
                throw new Exception(
189 1
                    "Property [{$key}] does not exist on this collection instance."
190
                );
191
            }
192
193 2
            return null;
194
        }
195
196
        return $this->runViaLaravelCollection(function ($collection) use (
197 1
            $key
198
        ) {
199 1
            return new HigherOrderCollectionProxy($collection, $key);
200 1
        });
201
    }
202
203
    /**
204
     * Recursive toArray().
205
     *
206
     * @param $value
207
     * @return array
208
     */
209 120 View Code Duplication
    public function __toArray($value = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
210
    {
211 120
        $value = is_null($value) ? $this->items : $value;
212
213 120
        if (!$this->isArrayable($value)) {
214 1
            return $value;
215
        }
216
217
        $result = array_map(function ($value) {
218 120
            if ($this->isArrayable($value)) {
219 96
                return $this->__toArray($value);
220
            }
221
222 120
            return $value;
223 120
        }, $this->getArrayableItems($value));
224
225 120
        return $result;
226
    }
227
228
    /**
229
     * Get an item from the collection by key.
230
     *
231
     * @param  mixed  $key
232
     * @param  mixed  $default
233
     * @return mixed
234
     */
235 4
    public function get($key, $default = null)
236
    {
237
        if (
238 4
            ($value = $this->call('get', [$key, static::NOT_FOUND])) ===
239 4
            static::NOT_FOUND
240
        ) {
241 2
            $value = Arr::get($this->items, $key, $default);
242
243 2
            if (is_array($value)) {
244
                $value = $this->__wrap($value);
245
            }
246
        }
247
248 4
        return $value;
249
    }
250
251
    /**
252
     * Results array of items from Collection or Arrayable.
253
     *
254
     * @param  mixed  $items
255
     * @return array
256
     */
257 120
    protected function getArrayableItems($items)
258
    {
259 120
        if (is_array($items)) {
260 120
            return $items;
261 120
        } elseif ($items instanceof self) {
262 120
            return $items->getItems();
263 120
        } elseif ($items instanceof Collection) {
264 120
            return $items->all();
265 3
        } elseif ($items instanceof Arrayable) {
266 1
            return $items->toArray();
267 3
        } elseif ($items instanceof Jsonable) {
268 1
            return json_decode($items->toJson(), true);
269 3
        } elseif ($items instanceof JsonSerializable) {
270 1
            return $items->jsonSerialize();
271 3
        } elseif ($items instanceof Traversable) {
0 ignored issues
show
Bug introduced by
The class PragmaRX\Coollection\Package\Traversable does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
272
            return iterator_to_array($items);
273
        }
274
275 3
        return (array) $items;
276
    }
277
278
    /**
279
     * Get an array as a key.
280
     *
281
     * @param $key
282
     * @return mixed|string
283
     */
284 94
    private function getArrayKey($key)
285
    {
286 94
        $data = $this->__toArray();
0 ignored issues
show
Unused Code introduced by
$data is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
287
288
        $cases = [
289 94
            $key,
290 94
            $this->snakeCase($key),
291 94
            lower($this->snakeCase($key)),
292 94
            $this->camelCase($key),
293 94
            $this->kebabCase($key),
294 94
            lower($this->kebabCase($key)),
295 94
            $this->stringCase($key),
296 94
            lower($this->stringCase($key)),
297 94
            lower($key),
298
        ];
299
300
        $data = $this->filter(function ($value, $key) use ($cases) {
0 ignored issues
show
Documentation Bug introduced by
The method filter does not exist on object<PragmaRX\Coollection\Package\Coollection>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
301 94
            return array_search($key, $cases) !== false ||
302 94
                array_search(lower($key), $cases) !== false;
303 94
        })
304 94
            ->keys()
305 94
            ->first();
306
307 94
        return is_string($data) ? $data : static::NOT_FOUND;
308
    }
309
310
    /**
311
     * Transform string to snake case.
312
     *
313
     * @param $string
314
     * @return string
315
     */
316 94
    public function snakeCase($string)
317
    {
318 94
        return Str::snake($string);
319
    }
320
321
    /**
322
     * Transform anything to string case.
323
     *
324
     * @param $string
325
     * @return string
326
     */
327 94
    public function stringCase($string)
328
    {
329 94
        $string = $this->snakeCase($string);
330
331 94
        return str_replace('_', ' ', $string);
332
    }
333
334
    /**
335
     * Transform anything to kebab case.
336
     *
337
     * @param $string
338
     * @return string
339
     */
340 94
    public function kebabCase($string)
341
    {
342 94
        return kebab_case($this->camelCase($string));
343
    }
344
345
    /**
346
     * Transform anything to camel case.
347
     *
348
     * @param $string
349
     * @return string
350
     */
351 94
    public function camelCase($string)
352
    {
353 94
        return camel_case($string);
354
    }
355
356
    /**
357
     * Get a property by name.
358
     *
359
     * @param $key
360
     * @return string|static
361
     */
362 94
    private function getByPropertyName($key)
363
    {
364 94
        if (($key = $this->getArrayKey($key)) !== static::NOT_FOUND) {
365 91
            if (is_array($this->items[$key])) {
366 27
                return $this->__wrap($this->items[$key]);
367
            }
368
369 91
            return $this->items[$key];
370
        }
371
372 4
        return static::NOT_FOUND;
373
    }
374
375
    /**
376
     * Execute a closure via Laravel's Collection
377
     *
378
     * @param $closure
379
     * @param null $method
380
     * @return mixed
381
     */
382 120
    private function runViaLaravelCollection($closure, $method = null)
383
    {
384 120
        $collection = new TightencoCollect($this->items);
385
386 120
        $result = $closure($collection);
387
388 120
        if (!$this->methodMustReturnArray($method)) {
389 120
            $result = $this->coollectizeItems($result);
390
        }
391
392 120
        $this->items = $collection->all();
393
394 120
        return $result;
395
    }
396
397
    /**
398
     * Does the method must return an array?
399
     *
400
     * @param $method
401
     * @return bool
402
     */
403 120
    public function methodMustReturnArray($method)
404
    {
405 120
        return in_array($method, static::$returnArray);
406
    }
407
408
    /**
409
     * Raise exception on null setter.
410
     *
411
     * @param bool $raiseExceptionOnNull
412
     */
413 3
    public static function setRaiseExceptionOnNull(bool $raiseExceptionOnNull)
414
    {
415 3
        self::$raiseExceptionOnNull = $raiseExceptionOnNull;
416 3
    }
417
418
    /**
419
     * Check if value is arrayable
420
     *
421
     * @param  mixed  $items
422
     * @return bool
423
     */
424 120
    protected function isArrayable($items)
425
    {
426 120
        return is_array($items) ||
427 120
            $items instanceof self ||
428 120
            $items instanceof Arrayable ||
429 120
            $items instanceof Jsonable ||
430 120
            $items instanceof JsonSerializable ||
431 120
            $items instanceof Traversable;
0 ignored issues
show
Bug introduced by
The class PragmaRX\Coollection\Package\Traversable does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
432
    }
433
434
    /**
435
     * Wrap on static if the value is arrayable.
436
     *
437
     * @param $value
438
     * @return static
439
     */
440 100
    protected function __wrap($value)
441
    {
442 100
        if (is_object($value)) {
443 91
            return $value;
444
        }
445
446 99
        return $this->isArrayable($value)
447 39
            ? new static($this->wrap($value)->toArray())
0 ignored issues
show
Documentation Bug introduced by
The method wrap does not exist on object<PragmaRX\Coollection\Package\Coollection>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
448 99
            : $value;
449
    }
450
451
    /**
452
     * Determine if an item exists at an offset.
453
     *
454
     * @param  mixed  $key
455
     * @return bool
456
     */
457 4
    public function offsetExists($key)
458
    {
459 4
        return array_key_exists($key, $this->items);
460
    }
461
462
    /**
463
     * Get an item at a given offset.
464
     *
465
     * @param  mixed  $key
466
     * @return mixed
467
     */
468 13
    public function offsetGet($key)
469
    {
470 13
        if (!isset($this->items[$key])) {
471
            return null;
472
        }
473
474 13
        return $this->items[$key];
475
    }
476
477
    /**
478
     * Set the item at a given offset.
479
     *
480
     * @param  mixed  $key
481
     * @param  mixed  $value
482
     * @return void
483
     */
484 3
    public function offsetSet($key, $value)
485
    {
486 3
        if (is_null($key)) {
487 2
            $this->items[] = $value;
488
        } else {
489 2
            $this->items[$key] = $value;
490
        }
491 3
    }
492
493
    /**
494
     * Unset the item at a given offset.
495
     *
496
     * @param  string  $key
497
     * @return void
498
     */
499 2
    public function offsetUnset($key)
500
    {
501 2
        unset($this->items[$key]);
502 2
    }
503
504
    /**
505
     * @param $items
506
     * @return array|Coollection
507
     * @internal param $originalCallback
508
     */
509 120
    public function coollectizeCallbacks($items, $method)
510
    {
511 120
        foreach ($items as $key => $item) {
512 120
            if ($item instanceof Closure) {
513 99
                $items[$key] =
514 99
                    $method === 'reduce'
515 1
                        ? $this->coollectizeCallbackForReduce($item)
516 99
                        : $this->coollectizeCallback($item);
517
            }
518
        }
519
520 120
        return $items;
521
    }
522
523
    /**
524
     * @param $originalCallback
525
     * @return callable
526
     */
527 99
    public function coollectizeCallback(callable $originalCallback = null)
528
    {
529
        return function ($value = null, $key = null) use ($originalCallback) {
530 98
            return $originalCallback($this->__wrap($value), $key);
531 99
        };
532
    }
533
534
    /**
535
     * @param $originalCallback
536
     * @return callable
537
     */
538 1
    public function coollectizeCallbackForReduce(callable $originalCallback)
539
    {
540
        return function ($carry, $item) use ($originalCallback) {
541 1
            return $originalCallback($carry, $this->__wrap($item));
542 1
        };
543
    }
544
545
    /**
546
     * Overwrite the original array with the
547
     *
548
     * @param $overwrite
549
     * @return Coollection
550
     */
551 1
    public function overwrite($overwrite)
552
    {
553 1
        $this->items = array_replace_recursive(
554 1
            $this->items,
555 1
            $this->getArrayableItems($overwrite)
556
        );
557
558 1
        return $this;
559
    }
560
561
    /**
562
     * Convert the object to its JSON representation.
563
     *
564
     * @param  int $options
565
     * @return string
566
     */
567 1
    public function toJson($options = 0)
568
    {
569 1
        return $this->call('toJson', [$options]);
570
    }
571
572
    /**
573
     * Specify data which should be serialized to JSON
574
     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
575
     * @return mixed data which can be serialized by <b>json_encode</b>,
576
     * which is a value of any type other than a resource.
577
     * @since 5.4.0
578
     */
579 1
    public function jsonSerialize()
580
    {
581 1
        return $this->call('jsonSerialize');
582
    }
583
584
    /**
585
     * Count elements of an object
586
     * @link http://php.net/manual/en/countable.count.php
587
     * @return int The custom count as an integer.
588
     * </p>
589
     * <p>
590
     * The return value is cast to an integer.
591
     * @since 5.1.0
592
     */
593 12
    public function count()
594
    {
595 12
        return $this->call('count');
596
    }
597
598
    /**
599
     * Retrieve an external iterator
600
     * @link http://php.net/manual/en/iteratoraggregate.getiterator.php
601
     * @return Traversable An instance of an object implementing <b>Iterator</b> or
602
     * <b>Traversable</b>
603
     * @since 5.0.0
604
     */
605 2
    public function getIterator()
606
    {
607 2
        return $this->call('getIterator');
608
    }
609
610
    /**
611
     * Sort by key.
612
     *
613
     * @return Coollection
614
     */
615 1
    public function sortByKey()
616
    {
617 1
        $items = $this->items;
618
619 1
        ksort($items);
620
621 1
        return $this->__wrap($items);
622
    }
623
624
    /**
625
     * Recursively Sort by key.
626
     *
627
     * @return Coollection
628
     */
629 2
    public function sortByKeysRecursive()
630
    {
631 2
        $items = $this->toArray();
632
633 2
        array_sort_by_keys_recursive($items);
634
635 2
        return $this->__wrap($items);
636
    }
637
638
    /**
639
     * Get raw items.
640
     *
641
     * @return array
642
     */
643 120
    public function getItems()
644
    {
645 120
        return $this->items;
646
    }
647
648
    /**
649
     * Dynamically check a property exists on the underlying object.
650
     *
651
     * @param  mixed  $name
652
     * @return bool
653
     */
654
    public function __isset($name)
655
    {
656
        return isset($this->items[$name]);
657
658
        return false;
0 ignored issues
show
Unused Code introduced by
return false; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
659
    }
660
}
661