1 | <?php |
||||
2 | /** |
||||
3 | * This file is part of the O2System Framework package. |
||||
4 | * |
||||
5 | * For the full copyright and license information, please view the LICENSE |
||||
6 | * file that was distributed with this source code. |
||||
7 | * |
||||
8 | * @author Steeve Andrian Salim |
||||
9 | * @copyright Copyright (c) Steeve Andrian Salim |
||||
10 | */ |
||||
11 | |||||
12 | // ------------------------------------------------------------------------ |
||||
13 | |||||
14 | namespace O2System\Kernel\Cli; |
||||
15 | |||||
16 | // ------------------------------------------------------------------------ |
||||
17 | |||||
18 | use O2System\Gear\Trace; |
||||
19 | use O2System\Kernel\Cli\Writers\Format; |
||||
20 | use O2System\Kernel\Cli\Writers\Line; |
||||
21 | use O2System\Kernel\Cli\Writers\Table; |
||||
22 | use O2System\Spl\Exceptions\Abstracts\AbstractException; |
||||
23 | use O2System\Spl\Exceptions\ErrorException; |
||||
24 | use O2System\Spl\Traits\Collectors\FilePathCollectorTrait; |
||||
25 | |||||
26 | /** |
||||
27 | * Class Output |
||||
28 | * |
||||
29 | * @package O2System\Kernel\Http |
||||
30 | */ |
||||
31 | class Output |
||||
32 | { |
||||
33 | use FilePathCollectorTrait; |
||||
34 | |||||
35 | // ------------------------------------------------------------------------ |
||||
36 | |||||
37 | /** |
||||
38 | * Output::__construct |
||||
39 | */ |
||||
40 | public function __construct() |
||||
41 | { |
||||
42 | // Set Output Views Directory |
||||
43 | $this->setFileDirName('Views'); |
||||
44 | $this->addFilePath(PATH_KERNEL); |
||||
45 | |||||
46 | // Autoload exception and error language file |
||||
47 | language()->loadFile(['exception', 'error']); |
||||
48 | |||||
49 | // Register Kernel defined handler |
||||
50 | $this->register(); |
||||
51 | } |
||||
52 | |||||
53 | // ------------------------------------------------------------------------ |
||||
54 | |||||
55 | /** |
||||
56 | * Output::register |
||||
57 | * |
||||
58 | * Register Kernel defined error, exception and shutdown handler. |
||||
59 | * |
||||
60 | * @return void |
||||
61 | */ |
||||
62 | public function register() |
||||
63 | { |
||||
64 | set_error_handler([&$this, 'errorHandler']); |
||||
65 | set_exception_handler([&$this, 'exceptionHandler']); |
||||
66 | register_shutdown_function([&$this, 'shutdownHandler']); |
||||
67 | } |
||||
68 | |||||
69 | // ------------------------------------------------------------------------ |
||||
70 | |||||
71 | /** |
||||
72 | * Output::shutdownHandler |
||||
73 | * |
||||
74 | * Kernel defined shutdown handler function. |
||||
75 | * |
||||
76 | * @return void |
||||
77 | * @throws \O2System\Spl\Exceptions\ErrorException |
||||
78 | */ |
||||
79 | public function shutdownHandler() |
||||
80 | { |
||||
81 | $lastError = error_get_last(); |
||||
82 | |||||
83 | if (is_array($lastError)) { |
||||
0 ignored issues
–
show
introduced
by
![]() |
|||||
84 | $this->errorHandler( |
||||
85 | $lastError[ 'type' ], |
||||
86 | $lastError[ 'message' ], |
||||
87 | $lastError[ 'file' ], |
||||
88 | $lastError[ 'line' ] |
||||
89 | ); |
||||
90 | } |
||||
91 | |||||
92 | // Execute shutdown service |
||||
93 | if(services()->has('shutdown')) { |
||||
94 | shutdown()->execute(); |
||||
95 | } |
||||
96 | } |
||||
97 | |||||
98 | // ------------------------------------------------------------------------ |
||||
99 | |||||
100 | /** |
||||
101 | * Output::errorHandler |
||||
102 | * |
||||
103 | * Kernel defined error handler function. |
||||
104 | * |
||||
105 | * @param int $errno The first parameter, errno, contains the level of the error raised, as an integer. |
||||
106 | * @param string $errstr The second parameter, errstr, contains the error message, as a string. |
||||
107 | * @param string $errfile The third parameter is optional, errfile, which contains the filename that the error |
||||
108 | * was raised in, as a string. |
||||
109 | * @param string $errline The fourth parameter is optional, errline, which contains the line number the error |
||||
110 | * was raised at, as an integer. |
||||
111 | * @param array $errcontext The fifth parameter is optional, errcontext, which is an array that points to the |
||||
112 | * active symbol table at the point the error occurred. In other words, errcontext will |
||||
113 | * contain an array of every variable that existed in the scope the error was triggered |
||||
114 | * in. User error handler must not modify error context. |
||||
115 | * |
||||
116 | * @return bool If the function returns FALSE then the normal error handler continues. |
||||
117 | * @throws ErrorException |
||||
118 | */ |
||||
119 | public function errorHandler($errno, $errstr, $errfile, $errline, array $errcontext = []) |
||||
0 ignored issues
–
show
The parameter
$errcontext is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||
120 | { |
||||
121 | $isFatalError = (((E_ERROR | E_COMPILE_ERROR | E_CORE_ERROR | E_USER_ERROR) & $errno) === $errno); |
||||
122 | |||||
123 | // When the error is fatal the Kernel will throw it as an exception. |
||||
124 | if ($isFatalError) { |
||||
125 | throw new ErrorException($errstr, $errno, $errfile, $errline); |
||||
0 ignored issues
–
show
$errline of type string is incompatible with the type integer expected by parameter $line of O2System\Spl\Exceptions\...xception::__construct() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
126 | } |
||||
127 | |||||
128 | // Should we ignore the error? We'll get the current error_reporting |
||||
129 | // level and add its bits with the severity bits to find out. |
||||
130 | if (($errno & error_reporting()) !== $errno) { |
||||
131 | return false; |
||||
132 | } |
||||
133 | |||||
134 | $error = new ErrorException($errstr, $errno, $errfile, $errline); |
||||
135 | |||||
136 | // Logged the error |
||||
137 | if(services()->has('logger')) { |
||||
138 | logger()->error( |
||||
139 | implode( |
||||
140 | ' ', |
||||
141 | [ |
||||
142 | '[ ' . $error->getStringSeverity() . ' ] ', |
||||
143 | $error->getMessage(), |
||||
144 | $error->getFile() . ':' . $error->getLine(), |
||||
145 | ] |
||||
146 | ) |
||||
147 | ); |
||||
148 | } |
||||
149 | |||||
150 | $errdisplay = str_ireplace(['off', 'none', 'no', 'false', 'null'], 0, ini_get('display_errors')); |
||||
151 | |||||
152 | // Should we display the error? |
||||
153 | if ($errdisplay == 1) { |
||||
154 | output()->write( |
||||
0 ignored issues
–
show
The method
write() does not exist on O2System\Kernel\Http\Output .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||
155 | (new Format()) |
||||
156 | ->setContextualClass(Format::DANGER) |
||||
157 | ->setString(language()->getLine('E_CAUGHT_ERROR')) |
||||
158 | ->setNewLinesAfter(1) |
||||
159 | ); |
||||
160 | |||||
161 | output()->write( |
||||
162 | (new Format()) |
||||
163 | ->setContextualClass(Format::WARNING) |
||||
164 | ->setString('[ ' . language()->getLine($error->getStringSeverity()) . ' ] ' . $error->getMessage()) |
||||
165 | ->setNewLinesAfter(1) |
||||
166 | ); |
||||
167 | |||||
168 | output()->write( |
||||
169 | (new Format()) |
||||
170 | ->setContextualClass(Format::INFO) |
||||
171 | ->setString($error->getFile() . ':' . $error->getLine()) |
||||
172 | ->setNewLinesAfter(1) |
||||
173 | ); |
||||
174 | } |
||||
175 | } |
||||
176 | |||||
177 | // ------------------------------------------------------------------------ |
||||
178 | |||||
179 | /** |
||||
180 | * Output::write |
||||
181 | * |
||||
182 | * Write text to console. |
||||
183 | * |
||||
184 | * @param string $text |
||||
185 | * @param string $type |
||||
186 | */ |
||||
187 | public function write($text, $type = 'stdout') |
||||
188 | { |
||||
189 | if (in_array($type, ['stdout', 'stderr']) && ! empty($text)) { |
||||
190 | $f = fopen('php://' . $type, 'w'); |
||||
191 | |||||
192 | if (is_array($text)) { |
||||
0 ignored issues
–
show
|
|||||
193 | $text = json_encode($text); |
||||
194 | } |
||||
195 | |||||
196 | fwrite($f, $text); |
||||
0 ignored issues
–
show
It seems like
$f can also be of type false ; however, parameter $handle of fwrite() 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
![]() |
|||||
197 | fclose($f); |
||||
0 ignored issues
–
show
It seems like
$f can also be of type false ; however, parameter $handle of fclose() 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
![]() |
|||||
198 | } |
||||
199 | } |
||||
200 | |||||
201 | // ------------------------------------------------------------------------ |
||||
202 | |||||
203 | /** |
||||
204 | * Output::exceptionHandler |
||||
205 | * |
||||
206 | * Kernel defined exception handler function. |
||||
207 | * |
||||
208 | * @param \Exception|\Error|\O2System\Spl\Exceptions\Abstracts\AbstractException $exception Throwable exception. |
||||
209 | * |
||||
210 | * @return void |
||||
211 | */ |
||||
212 | public function exceptionHandler($exception) |
||||
213 | { |
||||
214 | // Standard PHP Libraries Error |
||||
215 | if ($exception instanceof \Error) { |
||||
216 | $error = new ErrorException( |
||||
217 | $exception->getMessage(), |
||||
218 | $exception->getCode(), |
||||
219 | $exception->getFile(), |
||||
220 | $exception->getLine() |
||||
221 | ); |
||||
222 | |||||
223 | output()->write( |
||||
224 | (new Format()) |
||||
225 | ->setContextualClass(Format::DANGER) |
||||
226 | ->setString(language()->getLine('E_CAUGHT_ERROR')) |
||||
227 | ->setNewLinesAfter(1) |
||||
228 | ); |
||||
229 | |||||
230 | output()->write( |
||||
231 | (new Format()) |
||||
232 | ->setContextualClass(Format::WARNING) |
||||
233 | ->setString('[ ' . language()->getLine($error->getStringSeverity()) . ' ] ' . $error->getMessage()) |
||||
234 | ->setNewLinesAfter(1) |
||||
235 | ); |
||||
236 | |||||
237 | output()->write( |
||||
238 | (new Format()) |
||||
239 | ->setContextualClass(Format::INFO) |
||||
240 | ->setString($error->getFile() . ':' . $error->getLine()) |
||||
241 | ->setNewLinesAfter(1) |
||||
242 | ); |
||||
243 | |||||
244 | exit(EXIT_ERROR); |
||||
0 ignored issues
–
show
|
|||||
245 | } elseif ($exception instanceof AbstractException) { |
||||
246 | |||||
247 | output()->write( |
||||
248 | (new Format()) |
||||
249 | ->setContextualClass(Format::DANGER) |
||||
250 | ->setString(language()->getLine($exception->getHeader())) |
||||
251 | ->setNewLinesAfter(1) |
||||
252 | ); |
||||
253 | |||||
254 | output()->write( |
||||
255 | (new Format()) |
||||
256 | ->setContextualClass(Format::DANGER) |
||||
257 | ->setString('[ ' . language()->getLine($exception->getCode()) . ' ] ' . language()->getLine($exception->getDescription())) |
||||
258 | ->setNewLinesAfter(1) |
||||
259 | ); |
||||
260 | |||||
261 | output()->write( |
||||
262 | (new Format()) |
||||
263 | ->setContextualClass(Format::DANGER) |
||||
264 | ->setString($exception->getMessage()) |
||||
265 | ->setNewLinesAfter(1) |
||||
266 | ); |
||||
267 | |||||
268 | output()->write( |
||||
269 | (new Format()) |
||||
270 | ->setString($debugTitle = language()->getLine('E_DEBUG_BACKTRACE') . ':') |
||||
271 | ->setContextualClass(Format::INFO) |
||||
272 | ->setNewLinesBefore(1) |
||||
273 | ->setNewLinesAfter(1) |
||||
274 | ); |
||||
275 | |||||
276 | output()->write((new Line(strlen($debugTitle) * 2)) |
||||
277 | ->setContextualClass(Line::INFO) |
||||
278 | ->setNewLinesAfter(2)); |
||||
279 | |||||
280 | $table = new Table(); |
||||
281 | $table->isShowBorder = false; |
||||
282 | |||||
283 | $i = 1; |
||||
284 | foreach ($exception->getChronology() as $chronology) { |
||||
285 | $table |
||||
286 | ->addRow() |
||||
287 | ->addColumn($i . '. ' . $chronology->call) |
||||
288 | ->addRow() |
||||
289 | ->addColumn($chronology->file . ':' . $chronology->line) |
||||
290 | ->addRow() |
||||
291 | ->addColumn(''); |
||||
292 | |||||
293 | $i++; |
||||
294 | } |
||||
295 | |||||
296 | output()->write( |
||||
297 | (new Format()) |
||||
298 | ->setContextualClass(Format::INFO) |
||||
299 | ->setString($table->render()) |
||||
300 | ->setNewLinesAfter(2) |
||||
301 | ); |
||||
302 | } // Standard PHP Libraries Exception |
||||
303 | elseif ($exception instanceof \Exception) { |
||||
0 ignored issues
–
show
|
|||||
304 | output()->write( |
||||
305 | (new Format()) |
||||
306 | ->setContextualClass(Format::DANGER) |
||||
307 | ->setString('[ ' . language()->getLine($exception->getCode()) . ' ] ' . language()->getLine($exception->getMessage())) |
||||
308 | ->setNewLinesAfter(1) |
||||
309 | ); |
||||
310 | |||||
311 | output()->write( |
||||
312 | (new Format()) |
||||
313 | ->setContextualClass(Format::DANGER) |
||||
314 | ->setString($exception->getMessage()) |
||||
315 | ->setNewLinesAfter(1) |
||||
316 | ); |
||||
317 | |||||
318 | output()->write( |
||||
319 | (new Format()) |
||||
320 | ->setString($debugTitle = language()->getLine('E_DEBUG_BACKTRACE') . ':') |
||||
321 | ->setContextualClass(Format::INFO) |
||||
322 | ->setNewLinesBefore(1) |
||||
323 | ->setNewLinesAfter(1) |
||||
324 | ); |
||||
325 | |||||
326 | output()->write((new Line(strlen($debugTitle) * 2)) |
||||
327 | ->setContextualClass(Line::INFO) |
||||
328 | ->setNewLinesAfter(2)); |
||||
329 | |||||
330 | $table = new Table(); |
||||
331 | $table->isShowBorder = false; |
||||
332 | |||||
333 | $trace = new Trace($exception->getTrace()); |
||||
334 | |||||
335 | $i = 1; |
||||
336 | foreach ($trace->getChronology() as $chronology) { |
||||
337 | $table |
||||
338 | ->addRow() |
||||
339 | ->addColumn($i . '. ' . $chronology->call) |
||||
340 | ->addRow() |
||||
341 | ->addColumn($chronology->file . ':' . $chronology->line) |
||||
342 | ->addRow() |
||||
343 | ->addColumn(''); |
||||
344 | |||||
345 | $i++; |
||||
346 | } |
||||
347 | |||||
348 | output()->write( |
||||
349 | (new Format()) |
||||
350 | ->setContextualClass(Format::INFO) |
||||
351 | ->setString($table->render()) |
||||
352 | ->setNewLinesAfter(2) |
||||
353 | ); |
||||
354 | } |
||||
355 | } |
||||
356 | |||||
357 | // ------------------------------------------------------------------------ |
||||
358 | |||||
359 | /** |
||||
360 | * Output::sendError |
||||
361 | * |
||||
362 | * @param int $code |
||||
363 | * @param null|array|string $vars |
||||
364 | */ |
||||
365 | public function sendError($code = 204, $vars = null) |
||||
366 | { |
||||
367 | static $errors = []; |
||||
368 | |||||
369 | if (empty($errors)) { |
||||
370 | $errors = require(str_replace('Cli', 'Config', __DIR__) . DIRECTORY_SEPARATOR . 'Errors.php'); |
||||
371 | } |
||||
372 | |||||
373 | if (isset($errors[ $code ])) { |
||||
374 | $languageKey = $errors[ $code ]; |
||||
375 | } |
||||
376 | |||||
377 | $languageKey = strtoupper($code . '_' . $languageKey); |
||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||
378 | |||||
379 | $error = [ |
||||
380 | 'code' => $code, |
||||
381 | 'title' => language()->getLine($languageKey . '_TITLE'), |
||||
382 | 'message' => language()->getLine($languageKey . '_MESSAGE'), |
||||
383 | ]; |
||||
384 | |||||
385 | $this->statusCode = $code; |
||||
0 ignored issues
–
show
|
|||||
386 | $this->reasonPhrase = $error[ 'title' ]; |
||||
0 ignored issues
–
show
|
|||||
387 | |||||
388 | if (is_string($vars)) { |
||||
389 | $error[ 'message' ] = $vars; |
||||
390 | } elseif (is_array($vars)) { |
||||
391 | $error = array_merge($error, $vars); |
||||
392 | } |
||||
393 | |||||
394 | $this->write( |
||||
395 | (new Format()) |
||||
396 | ->setContextualClass(Format::DANGER) |
||||
397 | ->setString($error[ 'code' ] . ' - ' . $error[ 'title' ]) |
||||
398 | ->setNewLinesAfter(1) |
||||
399 | ); |
||||
400 | |||||
401 | $this->write( |
||||
402 | (new Format()) |
||||
403 | ->setString($error[ 'message' ]) |
||||
404 | ->setNewLinesAfter(1) |
||||
405 | ); |
||||
406 | |||||
407 | exit(EXIT_ERROR); |
||||
0 ignored issues
–
show
|
|||||
408 | } |
||||
409 | |||||
410 | // ------------------------------------------------------------------------ |
||||
411 | |||||
412 | /** |
||||
413 | * Output::verbose |
||||
414 | * |
||||
415 | * Write verbose text to console. |
||||
416 | * |
||||
417 | * @param string $text |
||||
418 | * @param string $type |
||||
419 | */ |
||||
420 | public function verbose($text, $type = 'stdout') |
||||
421 | { |
||||
422 | if (isset($_ENV[ 'VERBOSE' ]) and $_ENV[ 'VERBOSE' ] === true) { |
||||
423 | $this->write($text, $type); |
||||
424 | } |
||||
425 | } |
||||
426 | } |