Completed
Push — master ( 13f15a...042b97 )
by Hiraku
02:14
created

Collection::repeat()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 4
nc 2
nop 2
1
<?php
2
namespace Spindle;
3
4
class Collection implements \IteratorAggregate
5
{
6
    private $ops = [];
7
    private $seed;
8
    private $is_array;
9
    private $vars = [];
10
    private $fn_cnt = 0;
11
12
    /**
13
     * @return \Spindle\Collection
14
     */
15
    public static function from($iterable)
16
    {
17
        return new static($iterable);
18
    }
19
20
    /**
21
     * Generator-based range()
22
     * @param int|string $start
23
     * @param int|string $end
24
     * @param int $step
25
     * @return \Spindle\Collection
26
     */
27
    public static function range($start, $end, $step = 1)
28
    {
29
        return new static(self::xrange($start, $end, $step));
0 ignored issues
show
Documentation introduced by
self::xrange($start, $end, $step) is of type object<Generator>, but the function expects a object<Spindle\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
30
    }
31
32
    private static function xrange($start, $end, $step = 1)
33
    {
34
        $_i = $start;
35
        while ($_i <= $end) {
36
            yield $_i;
37
            for ($j = $step; $j > 0; --$j) {
38
                ++$_i;
39
            }
40
        }
41
    }
42
43
    /**
44
     * Generator-based repeat()
45
     * @param mixed $elem
46
     * @param int $count
47
     */
48
    public static function repeat($elem, $count)
49
    {
50
        if (!is_int($count) || $count < 0) {
51
            throw new \InvalidArgumentException('$count must be int >= 0. given: ' . gettype($count));
52
        }
53
        return new static(self::xrepeat($elem, $count));
0 ignored issues
show
Documentation introduced by
self::xrepeat($elem, $count) is of type object<Generator>, but the function expects a object<Spindle\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
54
    }
55
56
    private static function xrepeat($elem, $count)
57
    {
58
        while ($count--) {
59
            yield $elem;
60
        }
61
    }
62
63
    /**
64
     * @param iterable $seed
65
     */
66
    public function __construct($seed)
67
    {
68
        if (!is_array($seed) && !is_object($seed)) {
69
            throw new \InvalidArgumentException('$seed should be iterable, given ' . gettype($seed));
70
        }
71
        $this->is_array = is_array($seed);
72
        $this->seed = $seed;
73
    }
74
75
    /**
76
     * @param string|callable $fn '$_ > 100'
77
     * @return $this
78
     */
79 View Code Duplication
    public function filter($fn)
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...
80
    {
81
        $this->is_array = false;
82
        if (is_callable($fn)) {
83
            $fn_name = '_fn' . $this->fn_cnt++;
84
            $this->vars[$fn_name] = $fn;
85
            $this->ops[] = 'if (!$' . $fn_name . '($_)) continue;';
86
        } else {
87
            $this->ops[] = 'if (!(' . $fn . ')) continue;';
88
        }
89
        return $this;
90
    }
91
92
    /**
93
     * @param string|callable $fn '$_ * 2'
94
     * @return $this
95
     */
96 View Code Duplication
    public function map($fn)
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...
97
    {
98
        $this->is_array = false;
99
        if (is_callable($fn)) {
100
            $fn_name = '_fn' . $this->fn_cnt++;
101
            $this->vars[$fn_name] = $fn;
102
            $this->ops[] = '$_ = $' . $fn_name . '($_);';
103
        } else {
104
            $this->ops[] = '$_ = ' . $fn . ';';
105
        }
106
        return $this;
107
    }
108
109
    /**
110
     * @param string[] column
111
     * @return $this
112
     */
113
    public function column(array $columns)
114
    {
115
        $this->is_array = false;
116
        $defs = [];
117
        foreach ($columns as $key) {
118
            $exported = var_export($key, 1);
119
            $defs[] = "$exported => \$_[$exported]";
120
        }
121
        $this->ops[] = '$_ = [' . implode(',', $defs) . '];';
122
        return $this;
123
    }
124
125
    /**
126
     * @param int $offset
127
     * @param ?int $length
0 ignored issues
show
Documentation introduced by
The doc-type ?int could not be parsed: Unknown type name "?int" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
128
     * @return $this
129
     */
130
    public function slice($offset, $length = null)
131
    {
132
        if ($offset < 0) {
133
            return new $this(array_slice($this->toArray(), $offset, $length));
134
        }
135
        $this->ops[] = 'if ($_i < ' . $offset . ') continue;';
136
        if ($length !== null) {
137
            $this->ops[] = 'if ($_i >= ' . ($offset + $length) . ') break;';
138
        }
139
        return $this;
140
    }
141
142
    /**
143
     * @param int $size
144
     * @return $this
145
     */
146
    public function chunk($size)
147
    {
148
        return new $this(array_chunk($this->toArray(), $size));
149
    }
150
151
    /**
152
     * @return \Spindle\Collection (new instance)
153
     */
154
    public function unique()
155
    {
156
        return new $this(array_unique($this->toArray()));
157
    }
158
159
    /**
160
     * @return \Spindle\Collection (new instance)
161
     */
162
    public function flip()
163
    {
164
        $this->is_array = false;
165
        $this->ops[] = 'list($_key, $_) = array($_, $_key);';
166
        return $this;
167
    }
168
169
    /**
170
     * @param string|callable $fn '$_carry + $_'
171
     * @param mixed $initial
172
     * @return mixed
173
     */
174
    public function reduce($fn, $initial = null)
175
    {
176
        $ops = $this->ops;
177
        $this->vars['_carry'] = $initial;
178
        if (is_callable($fn)) {
179
            $fn_name = '_fn' . $this->fn_cnt++;
180
            $this->vars[$fn_name] = $fn;
181
            $ops[] = '$_carry = $' . $fn_name . '($_, $_carry);';
182
        } else {
183
            $ops[] = '$_carry = ' . $fn . ';';
184
        }
185
        $after = '$_result = $_carry;';
186
        return self::evaluate($this->seed, $this->vars, $this->compile($ops), '', $after);
187
    }
188
189
    /**
190
     * @return int|float
191
     */
192 View Code Duplication
    public function sum()
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...
193
    {
194
        $ops = $this->ops;
195
        $before = '$_result = 0;';
196
        $ops[] = '$_result += $_;';
197
198
        return self::evaluate($this->seed, $this->vars, $this->compile($ops), $before, '');
199
    }
200
201
    /**
202
     * @return int|float
203
     */
204 View Code Duplication
    public function product()
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...
205
    {
206
        $ops = $this->ops;
207
        $before = '$_result = 1;';
208
        $ops[] = '$_result *= $_;';
209
210
        return self::evaluate($this->seed, $this->vars, $this->compile($ops), $before, '');
211
    }
212
213
    /**
214
     * @return \Spindle\Collection (new instance)
215
     */
216
    public function usort(callable $cmp)
217
    {
218
        $array = $this->toArray();
219
        usort($array, $cmp);
220
        return new $this($array);
221
    }
222
223
    /**
224
     * @return \Spindle\Collection (new instance)
225
     */
226
    public function rsort($sort_flags = \SORT_REGULAR)
227
    {
228
        $array = $this->toArray();
229
        rsort($array, $sort_flags);
230
        return new $this($array);
231
    }
232
233
    /**
234
     * @return \Spindle\Collection (new instance)
235
     */
236
    public function sort($sort_flags = \SORT_REGULAR)
237
    {
238
        $array = $this->toArray();
239
        sort($array, $sort_flags);
240
        return new $this($array);
241
    }
242
243
    /**
244
     * @return \Generator
245
     */
246 View Code Duplication
    public function getIterator()
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...
247
    {
248
        $ops = $this->ops;
249
        $ops[] = 'yield $_key => $_;';
250
        $gen = self::evaluate(
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $gen is correct as self::evaluate($this->se...() use($_seed){', '};') (which targets Spindle\Collection::evaluate()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
251
            $this->seed,
252
            $this->vars,
253
            $this->compile($ops),
254
            '$_result = static function() use($_seed){',
255
            '};'
256
        );
257
        return $gen();
258
    }
259
260
    /**
261
     * @return array
262
     */
263
    public function toArray()
264
    {
265
        if ($this->is_array) {
266
            return $this->seed;
267
        }
268
        $ops = $this->ops;
269
        $ops[] = '$_result[$_key] = $_;';
270
        return self::evaluate(
271
            $this->seed,
272
            $this->vars,
273
            $this->compile($ops),
274
            '$_result = [];',
275
            ''
276
        );
277
    }
278
279
    /**
280
     * @return $this
281
     */
282
    public function dump()
283
    {
284
        var_dump($this);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($this); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
285
        return $this;
286
    }
287
288
    /**
289
     * @return $this
290
     */
291
    public function assignTo(&$var = null)
292
    {
293
        $var = new $this($this->toArray());
294
        return $var;
295
    }
296
297
    /**
298
     * @return $this
299
     */
300
    public function assignArrayTo(&$var = null)
301
    {
302
        $var = $this->toArray();
303
        return new $this($var);
304
    }
305
306
    /**
307
     * @return string
308
     */
309
    public function __toString()
310
    {
311
        return implode("\n", [
312
            static::class,
313
            ' array-mode:' . (int)$this->is_array,
314
            " codes:\n  " . implode("\n  ", $this->ops)
315
        ]);
316
    }
317
318
    /**
319
     * @return array
320
     */
321
    public function __debugInfo()
322
    {
323
        if (is_array($this->seed)) {
324
            $cnt = count($this->seed);
325
            if ($cnt === 0) {
326
                $seed = "empty array()";
327
            } else {
328
                $first = gettype(current($this->seed));
329
                $seed = "array($first, ...($cnt items))";
330
            }
331
        } else {
332
            $seed = get_class($this->seed);
333
        }
334
        return [
335
            'seed' => $seed,
336
            'code' => $this->compile($this->ops),
337
        ];
338
    }
339
340
    private static function evaluate($_seed, $_vars, $_code, $_before, $_after)
0 ignored issues
show
Unused Code introduced by
The parameter $_seed 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...
341
    {
342
        $_result = null;
343
        extract($_vars);
344
        eval("$_before \n $_code \n $_after");
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
345
        return $_result;
346
    }
347
348
    private static function compile($ops)
349
    {
350
        return '$_i = 0; foreach ($_seed as $_key => $_) {'
351
            . '++$_i;'
352
            . implode("\n", $ops)
353
            . '}';
354
    }
355
}
356