Completed
Push — master ( 874053...fdc6df )
by Antonio Carlos
03:51
created

Coollection::jsonSerialize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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 ArrayAccess, Arrayable, Countable, IteratorAggregate, Jsonable, JsonSerializable
21
{
22
    use Macroable {
23
        __call as __callMacro;
24
    }
25
26
    /**
27
     * Consants
28
     */
29
    const NOT_FOUND = '!__NOT__FOUND__!';
30
31
    /**
32
     * The items contained in the collection.
33
     *
34
     * @var array
35
     */
36
    protected $items;
37
38
    /**
39
     * Raise exception on null.
40
     *
41
     * @static boolean
42
     */
43
    public static $raiseExceptionOnNull = true;
44
45
    /**
46
     * The methods that can be proxied.
47
     *
48
     * @var array
49
     */
50
    protected static $proxies = [
51
        'average', 'avg', 'contains', 'each', 'every', 'filter', 'first', 'flatMap',
52
        'keyBy', 'map', 'partition', 'reject', 'sortBy', 'sortByDesc', 'sum',
53
    ];
54
55
    /**
56
     * The methods that must return array.
57
     *
58
     * @var array
59
     */
60
    protected static $returnArray = [
61
        'toArray', 'jsonSerialize', 'unwrap',
62
    ];
63
64
    /**
65
     * Cache __toArray results.
66
     *
67
     * @var array
68
     */
69
    protected $cache = [];
70
71
    /**
72
     * Create a new coollection.
73
     *
74
     * @param  mixed  $items
75
     */
76 120
    public function __construct($items = [])
77
    {
78 120
        $this->items = $this->getArrayableItems($items);
79 120
    }
80
81
    /**
82
     * Transfer calls to Illuminate\Collection.
83
     *
84
     * @param $name
85
     * @param $arguments
86
     * @return mixed|static
87
     */
88 120
    public function __call($name, $arguments)
89
    {
90 120
        if (static::hasMacro($name)) {
91 1
            return $this->__callMacro($name, $arguments);
92
        }
93
94 120
        return $this->call($name, $arguments);
95
    }
96
97
    /**
98
     * Transfer calls to Illuminate\Collection.
99
     *
100
     * @param $name
101
     * @param $arguments
102
     * @return mixed|static
103
     */
104 120
    public function call($name, $arguments = [])
105
    {
106
        return $this->runViaLaravelCollection(function ($collection) use ($name, $arguments) {
107 120
            return call_user_func_array(
108 120
                [$collection, $name],
109 120
                $this->coollectizeCallbacks($this->__toArray($arguments), $name)
110
            );
111 120
        }, $name);
112
    }
113
114
    /**
115
     * Get the collection of items as a plain array.
116
     *
117
     * @param $value
118
     * @return Coollection
119
     */
120 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...
121
    {
122 120
        $value = is_null($value) ? $this->items : $value;
123
124 120
        if (! $this->isArrayable($value)) {
125 102
            return $value;
126
        }
127
128
        $result = array_map(function ($value) {
129 120
            if ($this->isArrayable($value)) {
130 120
                return new static($value);
131
            }
132
133 120
            return $value;
134 120
        }, $this->getArrayableItems($value));
135
136 120
        return new static($result);
137
    }
138
139
    /**
140
     * Get the collection of items as a plain array.
141
     *
142
     * @return array
143
     */
144 120
    public function toArray()
145
    {
146
        return array_map(function ($value) {
147 120
            return $value instanceof Arrayable ? $value->toArray() : $value;
148 120
        }, $this->items);
149
    }
150
151
    /**
152
     * Dynamically access collection proxies.
153
     *
154
     * @param  string  $key
155
     * @return mixed|static
156
     *
157
     * @throws \Exception
158
     */
159 94
    public function __get($key)
160
    {
161 94
        if (($value = $this->getByPropertyName($key)) !== static::NOT_FOUND) {
162 91
            return $value;
163
        }
164
165 4
        if (!in_array($key, static::$proxies)) {
166 3
            if (static::$raiseExceptionOnNull) {
167 1
                throw new Exception("Property [{$key}] does not exist on this collection instance.");
168
            }
169
170 2
            return null;
171
        }
172
173
        return $this->runViaLaravelCollection(function ($collection) use ($key) {
174 1
            return new HigherOrderCollectionProxy($collection, $key);
175 1
        });
176
    }
177
178
    /**
179
     * Recursive toArray().
180
     *
181
     * @param $value
182
     * @return array
183
     */
184 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...
185
    {
186 120
        $value = is_null($value) ? $this->items : $value;
187
188 120
        if (! $this->isArrayable($value)) {
189 1
            return $value;
190
        }
191
192
        $result = array_map(function ($value) {
193 120
            if ($this->isArrayable($value)) {
194 96
                return $this->__toArray($value);
195
            }
196
197 120
            return $value;
198 120
        }, $this->getArrayableItems($value));
199
200 120
        return $result;
201
    }
202
203
    /**
204
     * Get an item from the collection by key.
205
     *
206
     * @param  mixed  $key
207
     * @param  mixed  $default
208
     * @return mixed
209
     */
210 4
    public function get($key, $default = null)
211
    {
212 4
        if (($value = $this->call('get', [$key, static::NOT_FOUND])) === static::NOT_FOUND) {
213 2
            $value = Arr::get($this->items, $key, $default);
214
215 2
            if (is_array($value)) {
216
                $value = $this->__wrap($value);
217
            }
218
        }
219
220 4
        return $value;
221
    }
222
223
    /**
224
     * Results array of items from Collection or Arrayable.
225
     *
226
     * @param  mixed  $items
227
     * @return array
228
     */
229 120
    protected function getArrayableItems($items)
230
    {
231 120
        if (is_array($items)) {
232 120
            return $items;
233 120
        } elseif ($items instanceof self) {
234 120
            return $items->getItems();
235 120
        } elseif ($items instanceof Collection) {
236 120
            return $items->all();
237 3
        } elseif ($items instanceof Arrayable) {
238 1
            return $items->toArray();
239 3
        } elseif ($items instanceof Jsonable) {
240 1
            return json_decode($items->toJson(), true);
241 3
        } elseif ($items instanceof JsonSerializable) {
242 1
            return $items->jsonSerialize();
243 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...
244
            return iterator_to_array($items);
245
        }
246
247 3
        return (array) $items;
248
    }
249
250
    /**
251
     * Get an array as a key.
252
     *
253
     * @param $key
254
     * @return mixed|string
255
     */
256 94
    private function getArrayKey($key)
257
    {
258 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...
259
260
        $cases = [
261 94
            $key,
262 94
            $this->snakeCase($key),
263 94
            lower($this->snakeCase($key)),
264 94
            $this->camelCase($key),
265 94
            $this->kebabCase($key),
266 94
            lower($this->kebabCase($key)),
267 94
            $this->stringCase($key),
268 94
            lower($this->stringCase($key)),
269 94
            lower($key),
270
        ];
271
272
        $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...
273 94
            return array_search($key, $cases) !== false || array_search(lower($key), $cases) !== false;
274 94
        })->keys()->first();
275
276 94
        return is_string($data) ? $data : static::NOT_FOUND;
277
    }
278
279
    /**
280
     * Transform string to snake case.
281
     *
282
     * @param $string
283
     * @return string
284
     */
285 94
    public function snakeCase($string)
286
    {
287 94
        return Str::snake($string);
288
    }
289
290
    /**
291
     * Transform anything to string case.
292
     *
293
     * @param $string
294
     * @return string
295
     */
296 94
    public function stringCase($string)
297
    {
298 94
        $string = $this->snakeCase($string);
299
300 94
        return str_replace('_', ' ', $string);
301
    }
302
303
    /**
304
     * Transform anything to kebab case.
305
     *
306
     * @param $string
307
     * @return string
308
     */
309 94
    public function kebabCase($string)
310
    {
311 94
        return kebab_case(
312 94
            $this->camelCase($string)
313
        );
314
    }
315
316
    /**
317
     * Transform anything to camel case.
318
     *
319
     * @param $string
320
     * @return string
321
     */
322 94
    public function camelCase($string)
323
    {
324 94
        return camel_case($string);
325
    }
326
327
    /**
328
     * Get a property by name.
329
     *
330
     * @param $key
331
     * @return string|static
332
     */
333 94
    private function getByPropertyName($key)
334
    {
335 94
        if (($key = $this->getArrayKey($key)) !== static::NOT_FOUND) {
336 91
            if (is_array($this->items[$key])) {
337 27
                return $this->__wrap($this->items[$key]);
338
            }
339
340 91
            return $this->items[$key];
341
        }
342
343 4
        return static::NOT_FOUND;
344
    }
345
346
    /**
347
     * Execute a closure via Laravel's Collection
348
     *
349
     * @param $closure
350
     * @param null $method
351
     * @return mixed
352
     */
353 120
    private function runViaLaravelCollection($closure, $method = null)
354
    {
355 120
        $collection = new TightencoCollect($this->items);
356
357 120
        $result = $closure($collection);
358
359 120
        if (! $this->methodMustReturnArray($method)) {
360 120
            $result = $this->coollectizeItems($result);
361
        }
362
363 120
        $this->items = $collection->all();
364
365 120
        return $result;
366
    }
367
368
    /**
369
     * Does the method must return an array?
370
     *
371
     * @param $method
372
     * @return bool
373
     */
374 120
    public function methodMustReturnArray($method)
375
    {
376 120
        return in_array($method, static::$returnArray);
377
    }
378
379
    /**
380
     * Raise exception on null setter.
381
     *
382
     * @param bool $raiseExceptionOnNull
383
     */
384 3
    public static function setRaiseExceptionOnNull(bool $raiseExceptionOnNull)
385
    {
386 3
        self::$raiseExceptionOnNull = $raiseExceptionOnNull;
387 3
    }
388
389
    /**
390
     * Check if value is arrayable
391
     *
392
     * @param  mixed  $items
393
     * @return bool
394
     */
395 120
    protected function isArrayable($items)
396
    {
397
        return
398 120
            is_array($items) ||
399 120
            $items instanceof self ||
400 120
            $items instanceof Arrayable ||
401 120
            $items instanceof Jsonable ||
402 120
            $items instanceof JsonSerializable ||
403 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...
404
            ;
405
    }
406
407
    /**
408
     * Wrap on static if the value is arrayable.
409
     *
410
     * @param $value
411
     * @return static
412
     */
413 100
    protected function __wrap($value)
414
    {
415 100
        if (is_object($value)) {
416 91
            return $value;
417
        }
418
419 99
        return $this->isArrayable($value)
420 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...
421 99
            : $value;
422
    }
423
424
    /**
425
     * Determine if an item exists at an offset.
426
     *
427
     * @param  mixed  $key
428
     * @return bool
429
     */
430 4
    public function offsetExists($key)
431
    {
432 4
        return array_key_exists($key, $this->items);
433
    }
434
435
    /**
436
     * Get an item at a given offset.
437
     *
438
     * @param  mixed  $key
439
     * @return mixed
440
     */
441 13
    public function offsetGet($key)
442
    {
443 13
        if (! isset($this->items[$key])) {
444
            return null;
445
        }
446
447 13
        return $this->items[$key];
448
    }
449
450
    /**
451
     * Set the item at a given offset.
452
     *
453
     * @param  mixed  $key
454
     * @param  mixed  $value
455
     * @return void
456
     */
457 3
    public function offsetSet($key, $value)
458
    {
459 3
        if (is_null($key)) {
460 2
            $this->items[] = $value;
461
        } else {
462 2
            $this->items[$key] = $value;
463
        }
464 3
    }
465
466
    /**
467
     * Unset the item at a given offset.
468
     *
469
     * @param  string  $key
470
     * @return void
471
     */
472 2
    public function offsetUnset($key)
473
    {
474 2
        unset($this->items[$key]);
475 2
    }
476
477
    /**
478
     * @param $items
479
     * @return array|Coollection
480
     * @internal param $originalCallback
481
     */
482 120
    public function coollectizeCallbacks($items, $method)
483
    {
484 120
        foreach ($items as $key => $item) {
485 120
            if ($item instanceof Closure) {
486 99
                $items[$key] = $method === 'reduce'
487 1
                    ? $this->coollectizeCallbackForReduce($item)
488 120
                    : $this->coollectizeCallback($item)
489
                ;
490
            }
491
        }
492
493 120
        return $items;
494
    }
495
496
    /**
497
     * @param $originalCallback
498
     * @return callable
499
     */
500 99
    public function coollectizeCallback(callable $originalCallback = null)
501
    {
502
        return function ($value = null, $key = null) use ($originalCallback) {
503 98
            return $originalCallback(
504 98
                $this->__wrap($value), $key
505
            );
506 99
        };
507
    }
508
509
    /**
510
     * @param $originalCallback
511
     * @return callable
512
     */
513 1
    public function coollectizeCallbackForReduce(callable $originalCallback)
514
    {
515
        return function ($carry, $item) use ($originalCallback) {
516 1
            return $originalCallback(
517 1
                $carry,
518 1
                $this->__wrap($item)
519
            );
520 1
        };
521
    }
522
523
    /**
524
     * Overwrite the original array with the
525
     *
526
     * @param $overwrite
527
     * @return Coollection
528
     */
529 1
    public function overwrite($overwrite)
530
    {
531 1
        $this->items = array_replace_recursive($this->items, $this->getArrayableItems($overwrite));
532
533 1
        return $this;
534
    }
535
536
    /**
537
     * Convert the object to its JSON representation.
538
     *
539
     * @param  int $options
540
     * @return string
541
     */
542 1
    public function toJson($options = 0)
543
    {
544 1
        return $this->call('toJson', [$options]);
545
    }
546
547
    /**
548
     * Specify data which should be serialized to JSON
549
     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
550
     * @return mixed data which can be serialized by <b>json_encode</b>,
551
     * which is a value of any type other than a resource.
552
     * @since 5.4.0
553
     */
554 1
    public function jsonSerialize()
555
    {
556 1
        return $this->call('jsonSerialize');
557
    }
558
559
    /**
560
     * Count elements of an object
561
     * @link http://php.net/manual/en/countable.count.php
562
     * @return int The custom count as an integer.
563
     * </p>
564
     * <p>
565
     * The return value is cast to an integer.
566
     * @since 5.1.0
567
     */
568 12
    public function count()
569
    {
570 12
        return $this->call('count');
571
    }
572
573
    /**
574
     * Retrieve an external iterator
575
     * @link http://php.net/manual/en/iteratoraggregate.getiterator.php
576
     * @return Traversable An instance of an object implementing <b>Iterator</b> or
577
     * <b>Traversable</b>
578
     * @since 5.0.0
579
     */
580 2
    public function getIterator()
581
    {
582 2
        return $this->call('getIterator');
583
    }
584
585
    /**
586
     * Sort by key.
587
     *
588
     * @return Coollection
589
     */
590 1
    public function sortByKey()
591
    {
592 1
        $items = $this->items;
593
594 1
        ksort($items);
595
596 1
        return $this->__wrap($items);
597
    }
598
599
    /**
600
     * Recursively Sort by key.
601
     *
602
     * @return Coollection
603
     */
604 2
    public function sortByKeysRecursive()
605
    {
606 2
        $items = $this->toArray();
607
608 2
        array_sort_by_keys_recursive($items);
609
610 2
        return $this->__wrap($items);
611
    }
612
613
    /**
614
     * Get raw items.
615
     *
616
     * @return array
617
     */
618 120
    public function getItems()
619
    {
620 120
        return $this->items;
621
    }
622
}
623