Utility   A
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 218
Duplicated Lines 0 %

Test Coverage

Coverage 96.83%

Importance

Changes 13
Bugs 0 Features 0
Metric Value
wmc 39
eloc 100
c 13
b 0
f 0
dl 0
loc 218
ccs 61
cts 63
cp 0.9683
rs 9.28

5 Methods

Rating   Name   Duplication   Size   Complexity  
B execute() 0 23 10
B emit() 0 54 10
A time() 0 11 3
B backtrace() 0 17 10
B respond() 0 63 6
1
<?php
2
3
/**
4
 * @author Marwan Al-Soltany <[email protected]>
5
 * @copyright Marwan Al-Soltany 2020
6
 * For the full copyright and license information, please view
7
 * the LICENSE file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace MAKS\AmqpAgent\Helper;
13
14
use Exception;
15
use DateTime;
16
use DateTimeZone;
17
18
/**
19
 * A class containing miscellaneous helper functions.
20
 * @since 1.2.0
21
 */
22
final class Utility
23
{
24
    /**
25
     * Returns a DateTime object with the right time zone.
26
     * @param string $time A valid php date/time string.
27
     * @param string|null $timezone A valid php timezone string.
28
     * @return DateTime
29
     * @throws Exception
30
     */
31 4
    public static function time(string $time = 'now', ?string $timezone = null): DateTime
32
    {
33 4
        $timezone = $timezone
34 1
            ? $timezone
35 4
            : date_default_timezone_get();
36
37 4
        $timezoneObject = $timezone
38 4
            ? new DateTimeZone($timezone)
39 4
            : null;
40
41 4
        return new DateTime($time, $timezoneObject);
42
    }
43
44
    /**
45
     * Generates a user-level notice, warning, or an error with styling.
46
     * @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).
47
     * @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).
48
     * @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.
49
     * @return bool True if error type is accepted.
50
     */
51 4
    public static function emit($text = null, ?string $color = 'yellow', int $type = E_USER_NOTICE): bool
52
    {
53
        $colors = [
54 4
            'reset'   => 0,
55
            'black'   => 30,
56
            'red'     => 31,
57
            'green'   => 32,
58
            'yellow'  => 33,
59
            'blue'    => 34,
60
            'magenta' => 35,
61
            'cyan'    => 36,
62
            'white'   => 37,
63
            'default' => 39,
64
        ];
65
66
        $types = [
67 4
            E_USER_NOTICE     => E_USER_NOTICE,
68 4
            E_USER_WARNING    => E_USER_WARNING,
69 4
            E_USER_ERROR      => E_USER_ERROR,
70 4
            E_USER_DEPRECATED => E_USER_DEPRECATED,
71
        ];
72
73 4
        $cli = php_sapi_name() === 'cli' || php_sapi_name() === 'cli-server' || http_response_code() === false;
74
75 4
        $trim = ' \t\0\x0B';
76 4
        $backspace = chr(8);
77 4
        $wrapper = $cli ? "\033[%dm %s\033[0m" : "@COLOR[%d] %s";
78 4
        $color = $colors[$color] ?? 39;
79 4
        $type = $types[$type] ?? 1024;
80 4
        $message = '';
81
82 4
        if (is_array($text)) {
83 2
            foreach ($text as $segmentColor => $string) {
84 2
                $string = trim($string, $trim);
85 2
                if (is_string($segmentColor)) {
86 2
                    $segmentColor = $colors[$segmentColor] ?? $color;
87 2
                    $message .= !strlen($message)
88 2
                        ? sprintf($wrapper, $segmentColor, $backspace . $string)
89 2
                        : sprintf($wrapper, $segmentColor, $string);
90 2
                    continue;
91
                }
92 2
                $message = $message . $string;
93
            }
94 2
        } elseif (is_string($text)) {
95 1
            $string = $backspace . trim($text, $trim);
96 1
            $message = sprintf($wrapper, $color, $string);
97
        } else {
98 1
            $string = $backspace . 'From ' . __METHOD__ . ': No message was specified!';
99 1
            $message = sprintf($wrapper, $color, $string);
100
        }
101
102 4
        $message = $cli ? $message : preg_replace('/@COLOR\[\d+\]/', '', $message);
103
104 4
        return trigger_error($message, $type);
105
    }
106
107
    /**
108
     * Returns the passed key(s) from the backtrace. Note that the backtrace is reversed (last is first).
109
     * @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].
110
     * @param int $offset [optional] The offset of the backtrace (last executed is index at 0).
111
     * @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.
112
     */
113 11
    public static function backtrace($pluck, int $offset = 0)
114
    {
115 11
        $backtrace = array_reverse(debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT));
116 11
        $plucked = null;
117
118 11
        if (count($backtrace) < $offset + 1) {
119 1
            return null;
120 10
        } elseif (is_string($pluck)) {
121 1
            $plucked = isset($backtrace[$offset][$pluck]) ? $backtrace[$offset][$pluck] : null;
122 9
        } elseif (is_array($pluck)) {
0 ignored issues
show
introduced by
The condition is_array($pluck) is always true.
Loading history...
123 9
            $plucked = [];
124 9
            foreach ($pluck as $key) {
125 9
                !isset($backtrace[$offset][$key]) ?: $plucked[$key] = $backtrace[$offset][$key];
126
            }
127
        }
128
129 10
        return is_string($plucked) || is_array($plucked) && count($plucked, COUNT_RECURSIVE) ? $plucked : null;
130
    }
131
132
    /**
133
     * Executes a CLI command in the specified path synchronously or asynchronous (cross platform).
134
     * @since 2.0.0
135
     * @param string $command The command to execute.
136
     * @param string|null $path [optional] The path where the command should be executed.
137
     * @param bool $asynchronous [optional] Whether the command should be a background process (asynchronous) or not (synchronous).
138
     * @return string|null The command result (as a string if possible) if synchronous otherwise null.
139
     * @throws Exception
140
     */
141 6
    public static function execute(string $command, string $path = null, bool $asynchronous = false): ?string
142
    {
143 6
        if (!strlen($command)) {
144 1
            throw new Exception('No valid command is specified!');
145
        }
146
147 5
        $isWindows = PHP_OS == 'WINNT' || substr(php_uname(), 0, 7) == 'Windows';
148 5
        $apWrapper = $isWindows ? 'start /B %s > NUL' : '/usr/bin/nohup %s >/dev/null 2>&1 &';
149
150 5
        if ($path && strlen($path) && getcwd() !== $path) {
151 5
            chdir(realpath($path));
152
        }
153
154 5
        if ($asynchronous) {
155 5
            $command = sprintf($apWrapper, $command);
156
        }
157
158 5
        if ($isWindows && $asynchronous) {
159
            pclose(popen($command, 'r'));
160
            return null;
161
        }
162
163 5
        return shell_exec($command);
164
    }
165
166
    /**
167
     * Returns an HTTP Response to the browser and lets blocking code that comes after this function to continue executing in the background.
168
     * This function is useful for example in Controllers that do some long-running tasks, and you just want to inform the client that the job has been started.
169
     * Please note that this function is tested MANUALLY ONLY, it is provided as is, there is no guarantee that it will work as expected nor on all platforms.
170
     * @param string $body The response body.
171
     * @param int $status [optional] The response status code.
172
     * @param array $headers [optional] An associative array of additional response headers `['headerName' => 'headerValue']`.
173
     * @return void
174
     * @since 2.2.0
175
     * @codeCoverageIgnore
176
     */
177
    public static function respond(string $body, int $status = 200, array $headers = []): void
178
    {
179
        // client disconnection should not abort script execution
180
        ignore_user_abort(true);
181
        // script execution should not bound by a timeout
182
        set_time_limit(0);
183
184
        // writing to the session must be closed to prevents subsequent requests from hanging
185
        if (session_id()) {
186
            session_write_close();
187
        }
188
189
        // clean the output buffer and turn off output buffering
190
        ob_end_clean();
191
        // turn on output buffering and buffer all upcoming output
192
        ob_start();
193
194
        // echo out response body (message)
195
        echo($body);
196
197
        // only keep the last buffer if nested and get the length of the output buffer
198
        while (ob_get_level() > 1) {
199
            ob_end_flush();
200
        }
201
        $length = ob_get_level() ? ob_get_length() : 0;
202
203
        // reserved headers that must not be overwritten
204
        $reservedHeaders = [
205
            'Connection' => 'close',
206
            'Content-Encoding' => 'none',
207
            'Content-Length' => $length
208
        ];
209
210
        // user headers after filtering out the reserved headers
211
        $filteredHeaders = array_filter($headers, function ($key) use ($reservedHeaders) {
212
            $immutable = array_map('strtolower', array_keys($reservedHeaders));
213
            $mutable   = strtolower($key);
214
            return !in_array($mutable, $immutable);
215
        }, ARRAY_FILTER_USE_KEY);
216
217
        // final headers for the response
218
        $finalHeaders = array_merge($reservedHeaders, $filteredHeaders);
219
220
        // send headers to tell the browser to close the connection
221
        foreach ($finalHeaders as $headerName => $headerValue) {
222
            header(sprintf('%s: %s', $headerName, $headerValue));
223
        }
224
225
        // set the HTTP response code
226
        http_response_code($status);
227
228
        // flush the output buffer and turn off output buffering
229
        ob_end_flush();
230
231
        // flush all output buffer layers
232
        if (ob_get_level()) {
233
            ob_flush();
234
        }
235
236
        // flush system output buffer
237
        flush();
238
239
        echo('You should not be seeing this!');
240
    }
241
}
242