Passed
Push — features/47-laravelmethods ( be9cc3...eb62da )
by Luke
02:31
created

functions.php ➔ get_range_start_end()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5.0488

Importance

Changes 0
Metric Value
cc 5
eloc 14
nc 9
nop 2
dl 0
loc 21
ccs 14
cts 16
cp 0.875
crap 5.0488
rs 8.7624
c 0
b 0
f 0
1
<?php
2
/*
3
 * Nozavroni/Collections
4
 * Just another collections library for PHP5.6+.
5
 *
6
 * @copyright Copyright (c) 2016 Luke Visinoni <[email protected]>
7
 * @author    Luke Visinoni <[email protected]>
8
 * @license   https://github.com/nozavroni/collections/blob/master/LICENSE The MIT License (MIT)
9
 */
10
namespace Noz;
11
12
use Closure;
13
use Illuminate\Support\Str;
14
use InvalidArgumentException;
15
use Iterator;
16
use Noz\Collection\Collection;
17
use Noz\Collection\Sequence;
18
use Noz\Contracts\CollectionInterface;
19
use RuntimeException;
20
use Traversable;
21
22
/**
23
 * Collection factory.
24
 *
25
 * Simply an alias to (new Collection($in)). Allows for a little more concise and
26
 * simpler instantiation of a collection. Also I plan to eventually support
27
 * additional input types that will make this function more flexible and forgiving
28
 * than simply instantiating a Collection object, but for now the two are identical.
29
 *
30
 * @param array|Iterator $data Either an array or an iterator of data
31
 *
32
 * @return CollectionInterface
0 ignored issues
show
Documentation introduced by
Should the return type not be Illuminate\Support\Collection?

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...
33
 */
34
function collect($data = null)
35
{
36 42
    return Collection::factory($data);
37
}
38
39
/**
40
 * Invoke a callable and return result.
41
 *
42
 * Pass in a callable followed by whatever arguments you want passed to
43
 * it and this function will invoke it with your arguments and return
44
 * the result.
45
 *
46
 * @param callable $callback The callback function to invoke
47
 * @param array ...$args     The args to pass to your callable
48
 *
49
 * @return mixed The result of your invoked callable
50
 */
51
function invoke(callable $callback, ...$args)
52
{
53 7
    return call_user_func($callback, ...$args);
54
}
55
56
/**
57
 * Underscore function.
58
59
 * This function is meant to work sort of like jQuery's "$()". It is a contextual catch-all type function. It works
60
 * as a short-hand alias for invoke, collect, and with.
61
62
 * @param callable|mixed    $in
63
 * @param mixed ...         $_
64
 *
65
 * @return mixed|CollectionInterface
66
 */
67
function _($in, ...$args)
68
{
69 3
    if (is_callable($in)) {
70 3
        return invoke($in, ...$args);
71
    }
72
    if (is_traversable($in)) {
73
        return collect($in);
74
    }
75
    return $in;
76
}
77
78
/**
79
 * Determine if data is traversable.
80
 *
81
 * Pass in any variable and this function will tell you whether or not it
82
 * is traversable. Basically this just means that it is either an array or an iterator.
83
 * This function was written simply because I was tired of if statements that checked
84
 * whether a variable was an array or a descendant of \Iterator. So I wrote this guy.
85
 *
86
 * @param mixed $data The variable to determine traversability
87
 *
88
 * @return bool True if $input is an array or an Iterator
89
 */
90
function is_traversable($data)
91
{
92 95
    return is_array($data) || $data instanceof Traversable;
93
}
94
95
/**
96
 * Can data be converted to an array?
97
 *
98
 * @param mixed $data The data to check
99
 *
100
 * @return bool
101
 */
102
function is_arrayable($data)
103
{
104 71
    if (!is_array($data)) {
105 48
        if (is_object($data)) {
106
            return (
107 5
                method_exists($data, 'toArray') ||
108
                $data instanceof Traversable
109 5
            );
110
        }
111 46
        return false;
112
    }
113 41
    return true;
114
}
115
116
/**
117
 * Convert any traversable to an array.
118
 *
119
 * @todo I'm not sure if this function is necessary or not. Does iterator_to_array do everything this can do?
120
 *
121
 * @param Traversable $data Traversable data
122
 *
123
 * @return array
124
 */
125
function traversable_to_array(Traversable $data)
126
{
127
    $arr = [];
128
    foreach ($data as $key => $val) {
129
        $arr[$key] = $val;
130
    }
131
    return $arr;
132
}
133
134
/**
135
 * Convert data to an array.
136
 *
137
 * Accepts any kind of data and converts it to an array. If strict mode is on, only data that returns true from
138
 * is_arrayable() will be converted to an array. Anything else will cause an InvalidArgumentException to be thrown.
139
140
 * @param mixed $data   Data to convert to array
141
 * @param bool  $strict Whether to use strict mode
142
143
 * @return array
144
 *
145
 * @throws InvalidArgumentException
146
 */
147
function to_array($data, $strict = true)
148
{
149 41
    if (is_arrayable($data)) {
150 41
        if (is_array($data)) {
151 40
            return $data;
152
        }
153
        // this is what makes toArray() work recursively
154
        // it must stay right where it is do not move it
155 4
        if (method_exists($data, 'toArray')) {
156 3
            return $data->toArray();
157
        }
158 3
        if ($data instanceof Iterator) {
159 3
            return iterator_to_array($data);
160
        }
161
        // @todo I don't think this will EVER be called...
162
        if ($data instanceof Traversable) {
163
            return traversable_to_array($data);
164
        }
165
    }
166 1
    if ($strict) {
167
        throw new InvalidArgumentException(sprintf(
168
            'Invalid argument for "%s". Cannot convert "%s" to an array.',
169
            __FUNCTION__,
170
            typeof($data)
171
        ));
172
    }
173 1
    if (is_object($data)) {
174 1
        $values = [];
175 1
        foreach ($data as $key => $val) {
176 1
            $values[$key] = $val;
177 1
        }
178 1
        return $values;
179
    }
180 1
    if (is_null($data)) {
181 1
        return [];
182
    }
183 1
    return [$data];
184
}
185
186
/**
187
 * Get object count.
188
 *
189
 * This function will accept any data and attempt to return its count.
190
 *
191
 * @param mixed $data The data to count
192
 *
193
 * @return int
194
 */
195
function get_count($data)
196
{
197 7
    if (is_null($data)) {
198 3
        return $data;
199
    }
200
201 6
    if (is_array($data)) {
202 1
        return count($data);
203
    }
204
205 6
    if (is_numeric($data)) {
206 5
        return (int) $data;
207
    }
208
209 3
    if ($data === '') {
210
        return 0;
211
    }
212
213 3
    if ($data === 0) {
214
        return 0;
215
    }
216
217 3
    if (is_object($data)) {
218 3
        if (method_exists($data, 'count')) {
219 1
            $count = $data->count();
220 3
        } elseif (method_exists($data, '__toString')) {
221 3
            $count = (string) $data;
222 3
            $count = (int) $count;
223 3
        } elseif (is_traversable($data)) {
224
            $count = 0;
225
            foreach ($data as $item) {
226
                $count++;
227
            }
228
            return (int) $count;
229
        }
230 3
        if (isset($count)) {
231 3
            return (int) $count;
232
        }
233
    }
234
235
    if (is_numeric($data)) {
236
        return (int) $data;
237
    }
238
239
    throw new RuntimeException('Cannot convert to int.');
240
}
241
242
/**
243
 * Normalize offset to positive integer.
244
 *
245
 * Provided with the requested offset, whether it be a string, an integer (positive or negative), or some type of
246
 * object, this function will normalize it to a positive integer offset or, failing that, it will throw an exception.
247
 * A negative offset will require either the traversable that is being indexed or its total count in order to normalize
248
249
 * @param int|mixed $offset The offset to normalize
250
 * @param int|array|traversable $count  Either the traversable count, or the traversable itself.
251
252
 * @return int
253
254
 * @throws RuntimeException If offset cannot be normalized
255
 * @throws InvalidArgumentException If offset is negative and count is not provided
256
 */
257
function normalize_offset($offset, $count = null)
258
{
259 8
    if (is_object($offset) && method_exists($offset, '__toString')) {
260 1
        $offset = (string) $offset;
261 1
    }
262
263 8
    if (!is_numeric($offset)) {
264 2
        throw new RuntimeException('Invalid offset.');
265
    }
266
267 6
    $count = get_count($count);
268
269 6
    if ($offset < 0) {
270 5
        if (is_null($count)) {
271 1
            throw new InvalidArgumentException('Cannot normalize negative offset without a total count.');
272
        }
273 4
        $offset += $count;
274 4
    }
275 5
    return (int) $offset;
276
}
277
278
/**
279
 * Get range start and end from string.
280
 *
281
 * Provided a string in the format of "start:end" and the total items in a collection, this function will return an
282
 * array in the form of [start, length].
283
 *
284
 * @param string $range The range to get (in the format of "start:end"
285
 * @param int|array|traversable $count  Either the traversable count, or the traversable itself.
286
 *
287
 * @return array [start, length]
288
 */
289
function get_range_start_end($range, $count = null)
290
{
291 4
    $count = get_count($count);
292 4
    if (Str::contains($range, Sequence::SLICE_DELIM)) {
293
        // return slice as a new sequence
294 4
        list($start, $end) = explode(Sequence::SLICE_DELIM, $range, 2);
295 4
        if ($start == '') {
296 4
            $start = 0;
297 4
        }
298 4
        if ($end == '') {
299 4
            $end = $count - 1;
300 4
        }
301 4
        $start = normalize_offset($start, $count);
302 4
        $end = normalize_offset($end, $count) + 1;
303 4
        if ($end > $start) {
304 4
            $length = $end - $start;
305 4
            return [$start, $length];
306
        }
307
    }
308
    throw new RuntimeException('Invalid index range/offset.');
309
}
310
311
/**
312
 * Get data type.
313
 *
314
 * Inspects data to determine its type.
315
 *
316
 * @param mixed  $data       The data to check
317
 * @param bool   $meta       Whether to include meta data such as length/size
318
// * @param string $returnType What type of value to return (array or string)
0 ignored issues
show
Bug introduced by
There is no parameter named $returnType. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
319
 *
320
 * @return string
321
 */
322
function typeof($data, $meta = true/*, $returnType = 'string'*/)
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
323
{
324 3
    $type = gettype($data);
325 3
    if ($meta) {
326
        switch($type) {
327 3
            case 'object':
328 1
                $class = get_class($data);
329 1
                return "{$type} <{$class}>";
330 3
            case 'resource':
331 1
                $restype = get_resource_type($data);
332 1
                return "{$type} <{$restype}>";
333
        }
334 3
    } else {
335
        switch($type) {
336 1
            case 'object':
337 1
                return get_class($data);
338 1
            case 'resource':
339 1
                return get_resource_type($data);
340
        }
341
    }
342 3
    return $type;
343
}
344
345
// BEGIN debug/testing functions
346
347
/**
348
 * Dump and die.
349
 *
350
 * @param mixed $input Data to dump
351
 * @param bool  $exit  Should we exit after dump?
352
 * @param bool  $label Should we print a label?
353
 * @codeCoverageIgnore
354
 */
355
function dd($input, $exit = true, $label = null)
356
{
357
    if (is_null($label)) {
358
        $trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 1);
359
        $label = 'File: ';
360
        $label .= pathinfo($trace[0]['file'], PATHINFO_FILENAME);
361
        $label .= ':' . $trace[0]['line'];
362
        echo $label . "\n";
363
    } else {
364
        echo $label . "\n" . implode(
365
                array_map(
366
                    function () {
367
                        return '-';
368
                    },
369
                    str_split($label)
370
                )
371
            ) . "\n";
372
    }
373
    var_dump($input);
374
    echo "\n";
375
    if ($exit) {
376
        exit;
377
    }
378
}
379
380
/**
381
 * Exactly the same as var_dump, except that it returns its output rather than dumping it.
382
 */
383
function sdump($var)
384
{
385 25
    ob_start();
386 25
    var_dump($var);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($var); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
387 25
    return ob_get_clean();
388
}
389
390
/**
391
 * Get object hash/checksum.
392
 * Using a var_dump of an object, this will return a hash that tells you if anything in your object has changed. Just
393
 * create a hash of an object, do some stuff with it, then create another hash of it and compare the two. If they are
394
 * different, teh object has changed in some way.
395
 *
396
 * @param object $obj The object to hash
397
 * @param string $alg The hash algorithm (supports md5 or sha1)
398
 *
399
 * @return string
400
 *
401
 * @throws InvalidArgumentException
402
 */
403
function object_hash($obj, $alg = 'md5')
404
{
405 25
    $algorithms = ['md5', 'sha1'];
406 25
    if (!in_array($alg, $algorithms)) {
407
        throw new InvalidArgumentException(sprintf(
408
            '"%s" is not a valid hash algorithm (%s).',
409
            $alg,
410
            implode(', ', $algorithms)
411
        ));
412
    }
413 25
    return call_user_func($alg, sdump($obj));
414
}
415