Completed
Pull Request — master (#49)
by Luke
04:44 queued 02:20
created

Sequence::__invoke()   C

Complexity

Conditions 11
Paths 32

Size

Total Lines 35
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 0
Metric Value
cc 11
eloc 23
nc 32
nop 0
dl 0
loc 35
rs 5.2653
c 0
b 0
f 0
ccs 0
cts 0
cp 0
crap 132

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Nozavroni/Collections
4
 * Just another collections library for PHP5.6+.
5
 * @version   {version}
6
 * @copyright Copyright (c) 2017 Luke Visinoni <[email protected]>
7
 * @author    Luke Visinoni <[email protected]>
8
 * @license   https://github.com/deni-zen/csvelte/blob/master/LICENSE The MIT License (MIT)
9
 */
10
namespace Noz\Collection;
11
12
use BadMethodCallException;
13
use Noz\Traits\IsArrayable;
14
use OutOfRangeException;
15
use RuntimeException;
16
17
use Iterator;
18
use ArrayAccess;
19
use Countable;
20
use SplFixedArray;
21
use Traversable;
22
23
use Illuminate\Support\Str;
24
25
use Noz\Contracts\Arrayable;
26
use Noz\Contracts\Immutable;
27
use Noz\Contracts\Invokable;
28
use Noz\Contracts\Structure\Sequenceable;
29
30
use Noz\Traits\IsContainer;
31
use Noz\Traits\IsImmutable;
32
33
use function
34
    Noz\to_array,
35
    Noz\is_traversable;
36
37
class Sequence implements
38
    Sequenceable,
39
    ArrayAccess,
40
    Immutable,
41
    Countable,
42
    Arrayable,
43
    Invokable,
44
    Iterator
45
{
46
    use IsImmutable, IsContainer, IsArrayable;
47
48
    /**
49
     * Delimiter used to fetch slices.
50
     */
51
    const SLICE_DELIM = ':';
52
53
    /**
54
     * Fixed-size data storage array.
55
     *
56
     * @var SplFixedArray
57
     */
58
    private $data;
59
60
    /**
61
     * Sequence constructor.
62
     *
63
     * @param array|Traversable $data The data to sequence
0 ignored issues
show
Documentation introduced by
Should the type for parameter $data not be array|Traversable|null? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
64
     */
65
    public function __construct($data = null)
66
    {
67
        if (is_null($data)) {
68
            $data = [];
69
        }
70
        $this->setData($data);
71
    }
72
73
    /**
74
     * Invoke sequence.
75
     * A sequence is invokable as if it were a function. This allows some pretty useful functionality such as negative
76
     * indexing, sub-sequence selection, etc.
77
     *
78
     * @internal param int $offset The offset to return
79
     *
80
     * @return mixed
81
     *
82
     * @todo Put all the slice logic into a helper function or several
83
     */
84
    public function __invoke()
85
    {
86
        $args = func_get_args();
87
        if (count($args)) {
88
            $count = $this->count();
89
            $offset = array_pop($args);
90
            if (Str::contains($offset, static::SLICE_DELIM)) {
91
                // return slice
92
                list($start, $end) = explode(static::SLICE_DELIM, $offset, 2);
93
                if ($start == '') {
94
                    $start = 0;
95
                }
96
                if ($end == '') {
97
                    $end = $count - 1;
98
                }
99
                if (is_numeric($start) && is_numeric($end)) {
100
                    if ($start < 0) {
101
                        $start = $count - abs($start);
102
                    }
103
                    if ($end < 0) {
104
                        $end = $count - abs($end);
105
                    }
106
                    $length = $end - $start + 1;
107
                    return new static(array_slice($this->getData(), $start, $length));
108
                }
109
            }
110
            if (is_numeric($offset)) {
111
                if ($offset < 0) {
112
                    $offset = $count - abs($offset);
113
                }
114
                return $this[$offset];
115
            }
116
        }
117
        return $this->toArray();
118
    }
119
120
    /**
121
     * Set data in sequence.
122
     *
123
     * Any array or traversable structure passed in will be re-indexed numerically.
124
     *
125
     * @param Traversable|array $data The sequence data
126
     */
127
    private function setData($data)
128
    {
129
        if (!is_traversable($data)) {
130
            // @todo Maybe create an ImmutableException for this?
131
            throw new BadMethodCallException(sprintf(
132
                'Cannot %s, %s is immutable.',
133
                __METHOD__,
134
                __CLASS__
135
            ));
136
        }
137
        $data = array_values(to_array($data));
138
        $this->data = SplFixedArray::fromArray($data);
139
    }
140
141
    /**
142
     * Get data.
143
     *
144
     * Get the underlying data array.
145
     *
146
     * @return array
147
     */
148
    protected function getData()
149
    {
150
        return $this->data->toArray();
151
    }
152
153
    /**
154
     * Return the current element
155
     * @link  http://php.net/manual/en/iterator.current.php
156
     * @return mixed Can return any type.
157
     * @since 5.0.0
158
     */
159
    public function current()
160
    {
161
        return $this->data->current();
162
    }
163
164
    /**
165
     * Move forward to next element
166
     * @link  http://php.net/manual/en/iterator.next.php
167
     * @return void Any returned value is ignored.
168
     * @since 5.0.0
169
     */
170
    public function next()
171
    {
172
        $this->data->next();
173
    }
174
175
    /**
176
     * Return the key of the current element
177
     * @link  http://php.net/manual/en/iterator.key.php
178
     * @return mixed scalar on success, or null on failure.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use integer.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
179
     * @since 5.0.0
180
     */
181
    public function key()
182
    {
183
        return $this->data->key();
184
    }
185
186
    /**
187
     * Checks if current position is valid
188
     * @link  http://php.net/manual/en/iterator.valid.php
189
     * @return boolean The return value will be casted to boolean and then evaluated.
190
     * Returns true on success or false on failure.
191
     * @since 5.0.0
192
     */
193
    public function valid()
194
    {
195
        return $this->data->valid();
196
    }
197
198
    /**
199
     * Rewind the Iterator to the first element
200
     * @link  http://php.net/manual/en/iterator.rewind.php
201
     * @return void Any returned value is ignored.
202
     * @since 5.0.0
203
     */
204
    public function rewind()
205
    {
206
        $this->data->rewind();
207
    }
208
209
    /**
210
     * Count elements of an object
211
     * @link  http://php.net/manual/en/countable.count.php
212
     * @return int The custom count as an integer.
213
     * </p>
214
     * <p>
215
     * The return value is cast to an integer.
216
     * @since 5.1.0
217
     */
218
    public function count()
219
    {
220
        return $this->data->count();
221
    }
222
223
    public function offsetGet($offset)
224
    {
225
        if (Str::contains($offset, static::SLICE_DELIM)) {
226
            return $this($offset)->toArray();
227
        }
228
        if ($offset < 0) {
229
            $offset = $this->count() + $offset;
230
        }
231
        try {
232
            return $this->data->offsetGet($offset);
233
        } catch (RuntimeException $e) {
234
            throw new OutOfRangeException($e->getMessage());
235
        }
236
    }
237
238
    public function offsetSet($offset, $value)
239
    {
240
        // TODO: Implement offsetSet() method.
241
    }
242
243
    public function offsetUnset($offset)
244
    {
245
        // TODO: Implement offsetUnset() method.
246
    }
247
248
    public function offsetExists($offset)
249
    {
250
        return $this->data->offsetExists($offset);
251
    }
252
253
    /**
254
     * Prepend item to collection.
255
     * Prepend an item to this collection (in place).
256
     * @param mixed $item Item to prepend to collection
257
     * @return Sequence
258
     */
259
     public function prepend($item)
260
     {
261
         $arr = $this->getData();
262
         array_unshift($arr, $item);
263
         return new static($arr);
264
     }
265
266
    /**
267
     * Append item to collection.
268
     * Append an item to this collection (in place).
269
     * @param mixed $item Item to append to collection
270
     * @return Sequence
271
     */
272
    public function append($item)
273
    {
274
        $arr = $this->getData();
275
        array_push($arr, $item);
276
        return new static($arr);
277
    }
278
279
    public function fold(callable $funk, $initial = null)
280
    {
281
        $carry = $initial;
282
        foreach ($this->getData() as $key => $val) {
283
            $carry = $funk($carry, $val, $key);
284
        }
285
        return $carry;
286
    }
287
288
    /**
289
     * Is collection empty?
290
     * You may optionally pass in a callback which will determine if each of the items within the collection are empty.
291
     * If all items in the collection are empty according to this callback, this method will return true.
292
     *
293
     * @param callable $funk The callback
0 ignored issues
show
Documentation introduced by
Should the type for parameter $funk not be null|callable? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
294
     *
295
     * @return bool
296
     */
297
    public function isEmpty(callable $funk = null)
298
    {
299
        if (!is_null($funk)) {
300
            return $this->fold(function ($carry, $val) use ($funk) {
301
                return $carry && $funk($val);
302
            }, true);
303
        }
304
        return empty($this->data->toArray());
305
    }
306
307
    /**
308
     * Pipe collection through callback.
309
     * Passes entire collection to provided callback and returns the result.
310
     * @param callable $funk The callback funkshun
311
     * @return mixed
312
     */
313
    public function pipe(callable $funk)
314
    {
315
        return $funk($this);
316
    }
317
318
    /**
319
     * Does every item return true?
320
     * If callback is provided, this method will return true if all items in collection cause callback to return true.
321
     * Otherwise, it will return true if all items in the collection have a truthy value.
322
     * @param callable|null $funk The callback
323
     * @return bool
324
     */
325
    public function every(callable $funk = null)
326
    {
327
        return $this->fold(function($carry, $val, $key) use ($funk) {
328
            if (!$carry) {
329
                return false;
330
            }
331
            if (!is_null($funk)) {
332
                return $funk($val, $key);
333
            }
334
            return (bool) $val;
335
        }, true);
336
    }
337
338
    /**
339
     * Does every item return false?
340
     * This method is the exact opposite of "all".
341
     * @param callable|null $funk The callback
342
     * @return bool
343
     */
344
    public function none(callable $funk = null)
345
    {
346
        return !$this->fold(function($carry, $val, $key) use ($funk) {
347
            if ($carry) {
348
                return true;
349
            }
350
            if (!is_null($funk)) {
351
                return $funk($val, $key);
352
            }
353
            return (bool) $val;
354
        }, false);
355
    }
356
357
    public function first(callable $funk = null, $default = null)
358
    {
359
        if (is_null($funk) && $this->count()) {
360
            return $this[0];
361
        }
362
        foreach ($this as $key => $val) {
363
            if ($funk($val, $key)) {
364
                return $val;
365
            }
366
        }
367
        return $default;
368
    }
369
370
    public function last(callable $funk = null, $default = null)
371
    {
372
        return $this->reverse()->first($funk, $default);
373
    }
374
375
    public function reverse()
376
    {
377
        return new static(array_reverse($this->getData()));
378
    }
379
380
    /**
381
     * Return new sequence with the first item "bumped" off.
382
     * @return Sequenceable
0 ignored issues
show
Documentation introduced by
Should the return type not be Sequenceable|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
383
     */
384
    public function bump()
385
    {
386
        // TODO: Implement bump() method.
387
    }
388
389
    /**
390
     * Return new sequence with the last item "dropped" off.
391
     * @return Sequenceable
0 ignored issues
show
Documentation introduced by
Should the return type not be Sequenceable|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
392
     */
393
    public function drop()
394
    {
395
        // TODO: Implement drop() method.
396
    }
397
398
    /**
399
     * Get collection as a sequence.
400
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
401
     */
402
    public function toSeq()
403
    {
404
        // TODO: Implement toSeq() method.
405
    }
406
407
    /**
408
     * Get collection as a dictionary.
409
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
410
     */
411
    public function toDict()
412
    {
413
        // TODO: Implement toDict() method.
414
    }
415
416
    /**
417
     * Get collection as a set.
418
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
419
     */
420
    public function toSet()
421
    {
422
        // TODO: Implement toSet() method.
423
    }
424
425
    /**
426
     * Get collection as a map.
427
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
428
     */
429
    public function toMap()
430
    {
431
        // TODO: Implement toMap() method.
432
    }
433
434
    /**
435
     * Get collection as a list.
436
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
437
     */
438
    public function toList()
439
    {
440
        // TODO: Implement toList() method.
441
    }
442
}