Passed
Push — dev ( add87b...32bbde )
by Marwan
06:54
created

Utility::respond()   B

Complexity

Conditions 7
Paths 64

Size

Total Lines 67
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 30
c 1
b 0
f 0
dl 0
loc 67
ccs 0
cts 0
cp 0
rs 8.5066
cc 7
nc 64
nop 3
crap 56

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 1
                $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
        // makes the rest of the script not bound by a timeout
182
        set_time_limit(0);
183
184
        // closes writing to the session, this prevents subsequent requests from hanging
185
        if (session_id()) {
186
            session_write_close();
187
        }
188
189
        // clean (erase) 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
        // build up response headers
204
        $reservedHeaders = [
205
            'Connection' => 'close',
206
            'Content-Encoding' => 'none',
207
            'Content-Length' => $length
208
        ];
209
210
        $immutable = array_map('strtolower', array_keys($reservedHeaders));
211
        $filteredHeaders = array_filter($headers, function ($key) use ($immutable) {
212
            return !in_array(strtolower($key), $immutable);
213
        }, ARRAY_FILTER_USE_KEY);
214
215
        $finalHeaders = array_merge($reservedHeaders, $filteredHeaders);
216
217
        // send headers to tell the browser to close the connection
218
        foreach ($finalHeaders as $headerName => $headerValue) {
219
            $currentHeader = sprintf("%s: %s\r\n", $headerName, $headerValue);
220
            header($currentHeader);
221
        }
222
223
        // set the HTTP response code
224
        http_response_code($status);
225
226
227
        // flush (send) the output buffer and turn off output buffering
228
        ob_end_flush();
229
230
        // flush (send) the output buffer (to be sure)
231
        if (ob_get_level()) {
232
            @ob_flush();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ob_flush(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

232
            /** @scrutinizer ignore-unhandled */ @ob_flush();

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
233
        }
234
235
        // flush system output buffer
236
        flush();
237
238
        // to be sure one last time
239
        if (ob_get_contents()) {
240
            ob_end_clean();
241
        }
242
243
        echo('You should not be seeing this, if you see this, this means that something has gone wrong!');
244
    }
245
}
246