Completed
Pull Request — master (#49)
by Luke
10:01 queued 06:50
created

functions.php ➔ sdump()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 1
dl 0
loc 6
ccs 3
cts 3
cp 1
crap 1
rs 9.4285
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 Serializable;
21
use Traversable;
22
23
use Symfony\Component\Serializer\Serializer;
24
use Symfony\Component\Serializer\Encoder\XmlEncoder;
25
use Symfony\Component\Serializer\Encoder\JsonEncoder;
26
use Symfony\Component\Serializer\Encoder\YamlEncoder;
27
use Symfony\Component\Serializer\Encoder\CsvEncoder;
28
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
29
30
/**
31
 * Collection factory.
32
 *
33
 * Simply an alias to (new Collection($in)). Allows for a little more concise and
34
 * simpler instantiation of a collection. Also I plan to eventually support
35
 * additional input types that will make this function more flexible and forgiving
36
 * than simply instantiating a Collection object, but for now the two are identical.
37
 *
38
 * @param array|Iterator $data Either an array or an iterator of data
39
 *
40
 * @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...
41
 */
42
function collect($data = null)
43
{
44 44
    return Collection::factory($data);
45
}
46
47
/**
48
 * Invoke a callable and return result.
49
 *
50
 * Pass in a callable followed by whatever arguments you want passed to
51
 * it and this function will invoke it with your arguments and return
52
 * the result.
53
 *
54
 * @param callable $callback The callback function to invoke
55
 * @param array ...$args     The args to pass to your callable
56
 *
57
 * @return mixed The result of your invoked callable
58
 */
59
function invoke(callable $callback, ...$args)
60
{
61 7
    return call_user_func($callback, ...$args);
62
}
63
64
/**
65
 * Underscore function.
66
67
 * This function is meant to work sort of like jQuery's "$()". It is a contextual catch-all type function. It works
68
 * as a short-hand alias for invoke, collect, and with.
69
70
 * @param callable|mixed    $in
71
 * @param mixed ...         $_
72
 *
73
 * @return mixed|CollectionInterface
74
 */
75
function _($in, ...$args)
76
{
77 5
    if (is_callable($in)) {
78 3
        return invoke($in, ...$args);
79
    }
80 2
    if (is_traversable($in)) {
81 1
        return collect($in);
82
    }
83 1
    return $in;
84
}
85
86
/**
87
 * Determine if data is traversable.
88
 *
89
 * Pass in any variable and this function will tell you whether or not it
90
 * is traversable. Basically this just means that it is either an array or an iterator.
91
 * This function was written simply because I was tired of if statements that checked
92
 * whether a variable was an array or a descendant of \Iterator. So I wrote this guy.
93
 *
94
 * @param mixed $data The variable to determine traversability
95
 *
96
 * @return bool True if $input is an array or an Iterator
97
 */
98
function is_traversable($data)
99
{
100 98
    return is_array($data) || $data instanceof Traversable;
101
}
102
103
/**
104
 * Can data be converted to an array?
105
 *
106
 * @param mixed $data The data to check
107
 *
108
 * @return bool
109
 */
110
function is_arrayable($data)
111
{
112 71
    if (!is_array($data)) {
113 48
        if (is_object($data)) {
114
            return (
115 5
                method_exists($data, 'toArray') ||
116
                $data instanceof Traversable
117 5
            );
118
        }
119 46
        return false;
120
    }
121 40
    return true;
122
}
123
124
/**
125
 * Convert any traversable to an array.
126
 *
127
 * @todo I'm not sure if this function is necessary or not. Does iterator_to_array do everything this can do?
128
 *
129
 * @param Traversable $data      Traversable data
130
 * @param bool        $recursive Apply recursively?
131
 *
132
 * @return array
133
 */
134
function traversable_to_array(Traversable $data, $recursive = true)
135
{
136 3
    $arr = [];
137 3
    foreach ($data as $key => $val) {
138 3
        if ($recursive) {
139 3
            if (is_traversable($val)) {
140
                $val = traversable_to_array($val);
141
            }
142 3
        }
143 3
        $arr[$key] = $val;
144 3
    }
145 3
    return $arr;
146
}
147
148
/**
149
 * Convert data to an array.
150
 *
151
 * Accepts any kind of data and converts it to an array. If strict mode is on, only data that returns true from
152
 * is_arrayable() will be converted to an array. Anything else will cause an InvalidArgumentException to be thrown.
153
154
 * @param mixed $data   Data to convert to array
155
 * @param bool  $strict Whether to use strict mode
156
157
 * @return array
158
 *
159
 * @throws InvalidArgumentException
160
 */
161
function to_array($data, $strict = true)
162
{
163 41
    if (is_arrayable($data)) {
164 40
        if (!is_array($data)) {
165
            // this is what makes toArray() work recursively
166
            // it must stay right where it is do not move it
167 3
            if (method_exists($data, 'toArray')) {
168 3
                $data = $data->toArray();
169 3
            }
170 3
            if ($data instanceof Traversable) {
171 2
                $data = traversable_to_array($data);
172 2
            }
173 3
        }
174 40
        return $data;
175
    }
176 2
    if ($strict) {
177 1
        throw new InvalidArgumentException(sprintf(
178 1
            'Invalid argument for "%s". Cannot convert "%s" to an array.',
179 1
            __FUNCTION__,
180 1
            typeof($data)
181 1
        ));
182
    }
183 1
    if (is_object($data)) {
184 1
        $values = [];
185 1
        foreach ($data as $key => $val) {
186 1
            $values[$key] = $val;
187 1
        }
188 1
        return $values;
189
    }
190 1
    if (is_null($data)) {
191 1
        return [];
192
    }
193 1
    return [$data];
194
}
195
196
/**
197
 * Get object count.
198
 *
199
 * This function will accept any data and attempt to return its count.
200
 *
201
 * @param mixed $data The data to count
202
 *
203
 * @return int
204
 */
205
function get_count($data)
206
{
207 13
    if (is_null($data)) {
208 5
        return $data;
209
    }
210
211 10
    if (is_array($data)) {
212 1
        return count($data);
213
    }
214
215 10
    if (is_numeric($data)) {
216 8
        return (int) $data;
217
    }
218
219 5
    if ($data === '') {
220 1
        return 0;
221
    }
222
223 5
    if (is_object($data)) {
224 4
        if (method_exists($data, 'count')) {
225 2
            $count = $data->count();
226 4
        } elseif (method_exists($data, '__toString')) {
227 3
            $count = (int) $data->__toString();
228 3
        } elseif (is_traversable($data)) {
229 1
            $count = 0;
230 1
            foreach ($data as $item) {
231 1
                $count++;
232 1
            }
233 1
        }
234 4
        if (isset($count)) {
235 4
            return (int) $count;
236
        }
237
    }
238
239 1
    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 11
    if (is_object($offset) && method_exists($offset, '__toString')) {
260 1
        $offset = (string) $offset;
261 1
    }
262
263 11
    if (!is_numeric($offset)) {
264 3
        throw new RuntimeException('Invalid offset.');
265
    }
266
267 8
    $count = get_count($count);
268
269 8
    if ($offset < 0) {
270 6
        if (is_null($count)) {
271 1
            throw new InvalidArgumentException('Cannot normalize negative offset without a total count.');
272
        }
273 5
        $offset += $count;
274 5
    }
275 7
    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 6
    $count = get_count($count);
292 6
    if (Str::contains($range, Sequence::SLICE_DELIM)) {
293
        // return slice as a new sequence
294 5
        list($start, $end) = explode(Sequence::SLICE_DELIM, $range, 2);
295 5
        if ($start == '') {
296 4
            $start = 0;
297 4
        }
298 5
        if ($end == '') {
299 4
            $end = $count - 1;
300 4
        }
301 5
        $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 1
    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 4
    $type = gettype($data);
325 4
    if ($meta) {
326
        switch($type) {
327 4
            case 'object':
328 2
                $class = get_class($data);
329 2
                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 27
    ob_start();
386 27
    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 27
    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 27
    $algorithms = ['md5', 'sha1'];
406 27
    if (!in_array($alg, $algorithms)) {
407 1
        throw new InvalidArgumentException(sprintf(
408 1
            '"%s" is not a valid hash algorithm (%s).',
409 1
            $alg,
410 1
            implode(', ', $algorithms)
411 1
        ));
412
    }
413 26
    return call_user_func($alg, sdump($obj));
414
}
415