Passed
Push — dev ( f5cf87...4396b8 )
by Marwan
14:15
created

Utility::getArrayValueByKey()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 7

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 11
c 2
b 0
f 0
dl 0
loc 23
rs 8.8333
ccs 11
cts 11
cp 1
cc 7
nc 6
nop 3
crap 7
1
<?php
2
/**
3
 * @author Marwan Al-Soltany <[email protected]>
4
 * @copyright Marwan Al-Soltany 2020
5
 * For the full copyright and license information, please view
6
 * the LICENSE file that was distributed with this source code.
7
 */
8
9
namespace MAKS\AmqpAgent\Helper;
10
11
use stdClass;
12
use Exception;
13
use ReflectionObject;
14
use DateTime;
15
use DateTimeZone;
16
17
/**
18
 * A class containing miscellaneous helper functions.
19
 * @since 1.2.0
20
 */
21
final class Utility
22
{
23
    /**
24
     * Returns a DateTime object with the right time zone.
25
     * @param string $time A valid php date/time string.
26
     * @param string $timezone A valid php timezone string.
27
     * @return DateTime
28 4
     */
29
    public static function time(string $time = 'now', ?string $timezone = null): DateTime
30 4
    {
31 1
        $timezone = $timezone
32 4
            ? $timezone
33
            : date_default_timezone_get();
34 4
35 4
        $timezoneObject = $timezone
36 4
            ? new DateTimeZone($timezone)
37
            : null;
38 4
39
        return new DateTime($time, $timezoneObject);
40
    }
41
42
    /**
43
     * Generates a user-level notice, warning, or an error with styling.
44
     * @param array|string|null $text [optional] The text wished to be styled (when passing an array, if array key is a valid color it will style this array element value with its key).
45
     * @param string $color [optional] Case sensitive ANSI color name in this list [black, red, green, yellow, magenta, cyan, white, default] (when passing array, this parameter will be the fallback).
46
     * @param int $type [optional] Error type (E_USER family). 1024 E_USER_NOTICE, 512 E_USER_WARNING, 256 E_USER_ERROR, 16384 E_USER_DEPRECATED.
47
     * @return bool True if error type is accepted.
48 4
     */
49
    public static function emit($text = null, ?string $color = 'yellow', int $type = E_USER_NOTICE): bool
50
    {
51 4
        $colors = [
52
            'reset'   => 0,
53
            'black'   => 30,
54
            'red'     => 31,
55
            'green'   => 32,
56
            'yellow'  => 33,
57
            'blue'    => 34,
58
            'magenta' => 35,
59
            'cyan'    => 36,
60
            'white'   => 37,
61
            'default' => 39,
62
        ];
63
64 4
        $types = [
65 4
            E_USER_NOTICE     => E_USER_NOTICE,
66 4
            E_USER_WARNING    => E_USER_WARNING,
67 4
            E_USER_ERROR      => E_USER_ERROR,
68
            E_USER_DEPRECATED => E_USER_DEPRECATED,
69
        ];
70 4
71
        $cli = php_sapi_name() === 'cli' || php_sapi_name() === 'cli-server' || http_response_code() === false;
72 4
73 4
        $trim = ' \t\0\x0B';
74 4
        $backspace = chr(8);
75 4
        $wrapper = $cli ? "\033[%dm %s\033[0m" : "@COLOR[%d] %s";
76 4
        $color = $colors[$color] ?? 39;
77 4
        $type = $types[$type] ?? 1024;
78
        $message = '';
79 4
80 2
        if (is_array($text)) {
81 2
            foreach ($text as $segmentColor => $string) {
82 2
                $string = trim($string, $trim);
83 2
                if (is_string($segmentColor)) {
84 2
                    $segmentColor = $colors[$segmentColor] ?? $color;
85 2
                    $message .= !strlen($message)
86 2
                        ? sprintf($wrapper, $segmentColor, $backspace . $string)
87 2
                        : sprintf($wrapper, $segmentColor, $string);
88
                    continue;
89 1
                }
90
                $message = $message . $string;
91 2
            }
92 1
        } elseif (is_string($text)) {
93 1
            $string = $backspace . trim($text, $trim);
94
            $message = sprintf($wrapper, $color, $string);
95 1
        } else {
96 1
            $string = $backspace . 'From ' . __METHOD__ . ': No message was specified!';
97
            $message = sprintf($wrapper, $color, $string);
98
        }
99 4
100
        $message = $cli ? $message : preg_replace('/@COLOR\[\d+\]/', '', $message);
101 4
102
        return trigger_error($message, $type);
103
    }
104
105
    /**
106
     * Returns the passed key(s) from the backtrace. Note that the backtrace is reversed (last is first).
107
     * @param string|array $pluck The key to to get as a string or an array of strings (keys) from this list [file, line, function, class, type, args].
108
     * @param int $offset [optional] The offset of the backtrace (last executed is index at 0).
109
     * @return string|int|array|null A string or int if a string is passed, an array if an array is passed and null if no match was found.
110 8
     */
111
    public static function backtrace($pluck, int $offset = 0)
112 8
    {
113 8
        $backtrace = array_reverse(debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT));
114
        $plucked = null;
115 8
116 1
        if (count($backtrace) < $offset + 1) {
117 7
            return null;
118 1
        } elseif (is_string($pluck)) {
119 6
            $plucked = isset($backtrace[$offset][$pluck]) ? $backtrace[$offset][$pluck] : null;
120 6
        } elseif (is_array($pluck)) {
0 ignored issues
show
introduced by
The condition is_array($pluck) is always true.
Loading history...
121 6
            $plucked = [];
122 6
            foreach ($pluck as $key) {
123
                !isset($backtrace[$offset][$key]) ?: $plucked[$key] = $backtrace[$offset][$key];
124
            }
125
        }
126 7
127
        return is_string($plucked) || is_array($plucked) && count($plucked, COUNT_RECURSIVE) ? $plucked : null;
128
    }
129
130
    /**
131
     * Returns a string representation of an array by imploding it recursively with common formatting of data-types.
132
     * @since 1.2.1
133
     * @param array $pieces The array to implode.
134
     * @return string
135 4
     */
136
    public static function collapse(array $pieces): string
137 4
    {
138
        $flat = [];
139 4
140 2
        foreach ($pieces as $piece) {
141 1
            if (is_array($piece)) {
142 2
                $flat[] = self::collapse($piece);
143 1
            } elseif (is_object($piece)) {
144 2
                $flat[] = get_class($piece) ?? 'object';
145 2
            } elseif (is_string($piece)) {
146 1
                $flat[] = "'{$piece}'";
147 1
            } elseif (is_bool($piece)) {
148 1
                $flat[] = $piece ? 'true' : 'false';
149 1
            } elseif (is_null($piece)) {
150
                $flat[] = 'null';
151 1
            } else {
152
                $flat[] = $piece;
153
            }
154
        }
155 4
156
        return '[' . implode(', ', $flat). ']';
157
    }
158
159
    /**
160
     * Converts (casts) an object to an associative array.
161
     * @since 1.2.2
162
     * @param object $object The object to convert.
163
     * @param bool $useJson [optional] Wether to use json_decode/json_encode to cast the object, default is via reflection.
164
     * @return array The result array.
165 2
     */
166
    public static function objectToArray($object, bool $useJson = false): array
167 2
    {
168 1
        if ($useJson) {
169
            return json_decode(json_encode($object), true);
170
        }
171 1
172
        $array = [];
173 1
174 1
        $reflectionClass = new ReflectionObject($object);
175 1
        foreach ($reflectionClass->getProperties() as $property) {
176 1
            $property->setAccessible(true);
177 1
            $array[$property->getName()] = $property->getValue($object);
178
            $property->setAccessible(false);
179
        }
180 1
181
        return $array;
182
    }
183
184
    /**
185
     * Converts (casts) an array to an object (stdClass).
186
     * @since 1.2.2
187
     * @param object $object The array to convert.
188
     * @param bool $useJson [optional] Wether to use json_decode/json_encode to cast the array, default is via iteration.
189
     * @return stdClass The result object.
190 2
     */
191
    public static function arrayToObject(array $array, bool $useJson = false): stdClass
192 2
    {
193 1
        if ($useJson) {
194
            return json_decode(json_encode($array));
195
        }
196 1
197
        $stdClass = new stdClass();
198 1
199 1
        foreach ($array as $key => $value) {
200 1
            $stdClass->{$key} = is_array($value)
201 1
                ? self::arrayToObject($value, $useJson)
202
                : $value;
203
        }
204 1
205
        return $stdClass;
206
    }
207
208
    /**
209
     * Gets a value from an array via dot-notation representation.
210
     * @since 1.2.2
211
     * @param array $array The array to get the value from.
212
     * @param string $key The dotted key representation.
213
     * @param mixed $default [optional] The default fallback value.
214
     * @return mixed The requested value if found otherwise the default parameter.
215 4
     */
216
    public static function getArrayValueByKey(array &$array, string $key, $default = null)
217 4
    {
218 1
        if (!strlen($key) || !count($array)) {
219
            return $default;
220
        }
221 4
222
        $data = &$array;
223 4
224 4
        if (strpos($key, '.') !== false) {
225
            $parts = explode('.', $key);
226 4
227 4
            foreach ($parts as $part) {
228 2
                if (!array_key_exists($part, $data)) {
229
                    return $default;
230
                }
231 4
232
                $data = &$data[$part];
233
            }
234 4
235
            return $data;
236
        }
237 4
238
        return array_key_exists($key, $data) ? $data[$key] : $default;
239
    }
240
241
    /**
242
     * Sets a value of an array via dot-notation representation.
243
     * @since 1.2.2
244
     * @param array $array The array to set the value in.
245
     * @param string $key The string key representation.
246
     * @param mixed $value The value to set.
247
     * @return bool True on success.
248 2
     */
249
    public static function setArrayValueByKey(array &$array, string $key, $value): bool
250 2
    {
251 1
        if (!strlen($key)) {
252
            return false;
253
        }
254 2
255 2
        $parts = explode('.', $key);
256
        $lastPart = array_pop($parts);
257 2
258
        $data = &$array;
259 2
260 2
        if (!empty($parts)) {
261 2
            foreach ($parts as $part) {
262 2
                if (!isset($data[$part])) {
263
                    $data[$part] = [];
264
                }
265 2
266
                $data = &$data[$part];
267
            }
268
        }
269 2
270
        $data[$lastPart] = $value;
271 2
272
        return true;
273
    }
274
275
    /**
276
     * Generates an md5 hash from microtime and uniqid.
277
     * @param string $entropy [optional] Additional entropy.
278
     * @since 2.0.0
279
     * @return string
280
     */
281
    public static function generateHash(string $entropy = 'maks-amqp-agent-id'): string
282
    {
283
        $prefix = sprintf('-%s-[%d]-', $entropy, rand());
284
        $symbol = microtime(true) . uniqid($prefix, true);
285
286
        return md5($symbol);
287
    }
288
289
    /**
290
     * Generates a crypto safe unique token. Note that this function is pretty expensive.
291
     * @since 2.0.0
292
     * @param int $length The length of the token. If the token is hashed this will not be the length of the returned string.
293
     * @param string $charset [optional] A string of characters to generate the token from. Defaults to alphanumeric.
294
     * @param string $hashing [optional] A name of hashing algorithm to hash the generated token with. Defaults to no hashing.
295
     * @return string
296
     */
297
    public static function generateToken(int $length = 32, ?string $charset = null, ?string $hashing = null): string
298
    {
299
        $token = '';
300
        $charset = $charset ?? (
301
            implode(range('A', 'Z')) .
302
            implode(range('a', 'z')) .
303
            implode(range(0, 9))
304
        );
305
        $max = strlen($charset);
306
307
        for ($i = 0; $i < $length; $i++) {
308
            $token .= $charset[
309
                self::generateCryptoSecureRandom(0, $max - 1)
310
            ];
311
        }
312
313
        return $hashing ? hash($hashing, $token) : $token;
314
    }
315
316
    /**
317
     * Generates a crypto secure random number.
318
     * @since 2.0.0
319
     * @param int $min
320
     * @param int $max
321
     * @return int
322
     */
323
    protected static function generateCryptoSecureRandom(int $min, int $max): int
324
    {
325
        $range = $max - $min;
326
        if ($range < 1) {
327
            return $min;
328
        }
329
330
        $log = ceil(log($range, 2));
331
        $bytes = (int)(($log / 8) + 1); // length in bytes
332
        $bits = (int)($log + 1); // length in bits
333
        $filter = (int)((1 << $bits) - 1); // set all lower bits to 1
334
335
        do {
336
            $random = PHP_VERSION >= 7
337
                ? random_bytes($bytes)
338
                : openssl_random_pseudo_bytes($bytes);
339
            $random = hexdec(bin2hex($random));
340
            $random = $random & $filter; // discard irrelevant bits
341
        } while ($random > $range);
342
343
        return $min + $random;
344
    }
345
346
    /**
347
     * Executes a CLI command in the specified path synchronously or asynchronous (cross platform).
348
     * @since 2.0.0
349
     * @param string $command The command to execute.
350
     * @param string $path [optional] The path where the command should be executed.
351
     * @param bool $asynchronous [optional] Wether the command should be a background process (asynchronous) or not (synchronous).
352
     * @return string|null The command result (as a string if possible) if synchronous otherwise null.
353
     */
354
    public static function execute(string $command, string $path = null, bool $asynchronous = false): ?string
355
    {
356
        if (!strlen($command)) {
357
            throw new Exception('No valid command is specified!');
358
        }
359
360
        $isWindows = PHP_OS == 'WINNT' || substr(php_uname(), 0, 7) == 'Windows';
361
        $apWrapper = $isWindows ? 'start /B %s > NUL' : '/usr/bin/nohup %s >/dev/null 2>&1 &';
362
363
        if (strlen($path) && getcwd() !== $path) {
364
            chdir(realpath($path));
365
        }
366
367
        if ($asynchronous) {
368
            $command = sprintf($apWrapper, $command);
369
        }
370
371
        if ($isWindows && $asynchronous) {
372
            pclose(popen($command, 'r'));
0 ignored issues
show
Bug introduced by
It seems like popen($command, 'r') can also be of type false; however, parameter $handle of pclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

372
            pclose(/** @scrutinizer ignore-type */ popen($command, 'r'));
Loading history...
373
            return null;
374
        }
375
376
        return shell_exec($command);
377
    }
378
}
379