MarwanAlsoltany /
amqp-agent
| 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
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 |