GDebug::handleException()   C
last analyzed

Complexity

Conditions 11
Paths 224

Size

Total Lines 61
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 30
c 1
b 0
f 0
nc 224
nop 1
dl 0
loc 61
rs 6.1833

How to fix   Long Method    Complexity   

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
 * Lenevor Framework
5
 *
6
 * LICENSE
7
 *
8
 * This source file is subject to the new BSD license that is bundled
9
 * with this package in the file license.md.
10
 * It is also available through the world-wide-web at this URL:
11
 * https://lenevor.com/license
12
 * If you did not receive a copy of the license and are unable to
13
 * obtain it through the world-wide-web, please send an email
14
 * to [email protected] so we can send you a copy immediately.
15
 *
16
 * @package     Lenevor
17
 * @subpackage  Base
18
 * @link        https://lenevor.com
19
 * @copyright   Copyright (c) 2019 - 2021 Alexander Campo <[email protected]>
20
 * @license     https://opensource.org/licenses/BSD-3-Clause New BSD license or see https://lenevor.com/license or see /license.md
21
 */
22
23
namespace Syscodes\Debug;
24
25
use Throwable;
26
use ErrorException;
27
use Syscodes\Debug\Benchmark;
28
use Syscodes\Debug\Util\Misc;
29
use InvalidArgumentException;
30
use Syscodes\Debug\Util\System;
31
use Syscodes\Debug\Handlers\MainHandler;
32
use Syscodes\Debug\Util\TemplateHandler;
33
use Syscodes\Debug\FrameHandler\Supervisor;
34
use Syscodes\Debug\Handlers\CallbackHandler;
35
use Syscodes\Contracts\Debug\Handler as DebugContract;
36
37
/**
38
 * Allows automatically load everything related to exception handlers.
39
 * 
40
 * @author Alexander Campo <[email protected]>
41
 */
42
class GDebug implements DebugContract
43
{
44
	/**
45
	 * Allow Handlers to force the script to quit.
46
	 * 
47
	 * @var bool $allowQuit
48
	 */
49
	protected $allowQuit = true;
50
	
51
	/**
52
	 * Benchmark instance.
53
	 * 
54
	 * @var string $benchmark
55
	 */
56
	protected $benchmark;
57
58
	/**
59
	 * The handler stack.
60
	 * 
61
	 * @var array $handlerStack
62
	 */
63
	protected $handlerStack = [];
64
65
	/**
66
	 * The send Http code by default: 500 Internal Server Error.
67
	 * 
68
	 * @var bool $sendHttpCode
69
	 */
70
	protected $sendHttpCode = 500;
71
72
	/**
73
	 * The send output.
74
	 * 
75
	 * @var bool $sendOutput
76
	 */
77
	protected $sendOutput = true;
78
79
	/**
80
	 * The functions of system what control errors and exceptions.
81
	 * 
82
	 * @var string $system
83
	 */
84
	protected $system;
85
86
	/**
87
	 * In certain scenarios, like in shutdown handler, we can not throw exceptions.
88
	 * 
89
	 * @var bool $throwExceptions
90
	 */
91
	protected $throwExceptions = true;
92
93
	/**
94
	 * Constructor. The Debug class instance.
95
	 * 
96
	 * @param  \Syscodes\Debug\Util\System|null  $system
97
	 * 
98
	 * @return void
99
	 */
100
	public function __construct(System $system = null)
101
	{
102
		$this->system    = $system ?: new System;
0 ignored issues
show
Documentation Bug introduced by
It seems like $system ?: new Syscodes\Debug\Util\System() of type Syscodes\Debug\Util\System is incompatible with the declared type string of property $system.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
103
		$this->benchmark = new Benchmark;
0 ignored issues
show
Documentation Bug introduced by
It seems like new Syscodes\Debug\Benchmark() of type Syscodes\Debug\Benchmark is incompatible with the declared type string of property $benchmark.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
104
	}
105
106
	/**
107
	 * Catches any uncaught errors and exceptions, including most Fatal errors. Will log the 
108
	 * error, display it if display_errors is on, and fire an event that allows custom actions 
109
	 * to be taken at this point.
110
	 *
111
	 * @param  \Throwable  $exception
112
	 *
113
	 * @return string
114
	 */
115
	public function handleException(Throwable $exception)
116
	{	
117
		// The start benchmark
118
		$this->benchmark->start('total_execution', LENEVOR_START);
0 ignored issues
show
Bug introduced by
The constant Syscodes\Debug\LENEVOR_START was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
119
120
		$supervisor = $this->getSupervisor($exception);
121
122
		// Start buffer
123
		$this->system->startOutputBuferring();
124
125
		$handlerResponse    = null;
126
		$handlerContentType = null;
127
		
128
		try {
129
			foreach ($this->handlerStack as $handler) {			
130
				$handler->setDebug($this);
131
				$handler->setException($exception);
132
				$handler->setSupervisor($supervisor);
133
				
134
				$handlerResponse = $handler->handle();
135
	
136
				// Collect the content type for possible sending in the headers
137
				$handlerContentType = method_exists($handler, 'contentType') ? $handler->contentType() : null;
138
	
139
				if (in_array($handlerResponse, [MainHandler::LAST_HANDLER, MainHandler::QUIT])) {
140
					break;
141
				}
142
			}
143
	
144
			$Quit = $handlerResponse == MainHandler::QUIT && $this->allowQuit();
145
		}
146
		finally {
147
			// Returns the contents of the output buffer
148
			$output = $this->system->CleanOutputBuffer();	
149
		}
150
151
		// Returns the contents of the output buffer for loading time of page
152
		$totalTime = $this->benchmark->getElapsedTime('total_execution');
153
		$output    = str_replace('{elapsed_time}', $totalTime, $output);
154
155
		if ($this->writeToOutput()) {
156
			if ($Quit) {
157
				while ($this->system->getOutputBufferLevel() > 0) {
158
					// Cleanes the output buffer
159
					$this->system->endOutputBuffering();
160
				}
161
162
				if (Misc::sendHeaders() && $handlerContentType)	{
163
					header("Content-Type: {$handlerContentType}");
164
				}
165
			}
166
167
			$this->writeToOutputBuffer($output);
168
		}
169
170
		if ($Quit) {
171
			$this->system->flushOutputBuffer();
172
			$this->system->stopException(1);
173
		}
174
175
		return $output;
176
	}
177
178
	/**
179
	 * Allow Handlers to force the script to quit.
180
	 * 
181
	 * @param  bool|int|null  $exit
182
	 * 
183
	 * @return bool
184
	 */
185
	public function allowQuit($exit = null)
186
	{
187
		if (func_num_args() == 0) {
188
			return $this->allowQuit;
189
		}
190
191
		return $this->allowQuit = (bool) $exit;
192
	}
193
194
	/**
195
	 * Lenevor Exception push output directly to the client it the data  
196
	 * if they are true, but if it is false, the output will be returned 
197
	 * by exception.
198
	 * 
199
	 * @param  bool|int|null  $send
200
	 *
201
	 * @return bool
202
	 */
203
	public function writeToOutput($send = null)
204
	{
205
		if (func_num_args() == 0) {
206
			return $this->sendOutput;
207
		}
208
		
209
		return $this->sendOutput = (bool) $send;
210
	}
211
	
212
	/**
213
	 * Generate output to the browser.
214
	 * 
215
	 * @param  string  $output
216
	 * 
217
	 * @return $this
218
	 */
219
	protected function writeToOutputBuffer($output)
220
	{
221
		if ($this->sendHttpCode() && Misc::sendHeaders()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->sendHttpCode() of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
222
			$this->system->setHttpResponseCode($this->sendHttpCode());
223
		}
224
		
225
		echo $output;
226
		
227
		return $this;
228
	}
229
230
	/**
231
	 * Error handler
232
	 *
233
	 * This will catch the php native error and treat it as a exception which will 
234
	 * provide a full back trace on all errors.
235
	 *
236
	 * @param  int  $level
237
	 * @param  string  $message
238
	 * @param  string|null  $file
239
	 * @param  int|null  $line
240
	 *
241
	 * @throws \ErrorException
242
	 */
243
	public function handleError(int $level, string $message, string $file = null, int $line = null)
244
	{
245
		if ($level & $this->system->getErrorReportingLevel()) {
246
			$exception = new ErrorException($message, $level, $level, $file, $line);
247
248
			if ($this->throwExceptions) {
249
				throw $exception;
250
			} else {
251
				$this->handleException($exception);
252
			}
253
254
			return true;
255
		}
256
257
		return false;
258
	}
259
260
	/**
261
	 * Pushes a handler to the end of the stack.
262
	 * 
263
	 * @param  \Callable|\Syscodes\Contracts\Debug\Handler  $handler
0 ignored issues
show
Bug introduced by
The type Callable was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
264
	 * 
265
	 * @return \Syscodes\Contracts\Debug\Handler
266
	 */
267
	public function pushHandler($handler)
268
	{
269
		return $this->prependHandler($handler);
270
	}
271
272
	/**
273
	 * Appends a handler to the end of the stack.
274
	 * 
275
	 * @param  \Callable|\Syscodes\Contracts\Debug\Handler  $handler
276
	 * 
277
	 * @return $this
278
	 */
279
	public function appendHandler($handler)
280
	{
281
		array_unshift($this->handlerStack, $this->resolveHandler($handler));
282
283
		return $this;
284
	}
285
286
	/**
287
	 * Prepends a handler to the start of the stack.
288
	 * 
289
	 * @param  \Callable|\Syscodes\Contracts\Debug\Handler  $handler
290
	 * 
291
	 * @return $this
292
	 */
293
	public function prependHandler($handler)
294
	{
295
		array_unshift($this->handlerStack, $this->resolveHandler($handler));
296
297
		return $this;
298
	}
299
300
	/**
301
	 * Create a CallbackHandler from callable and throw if handler is invalid.
302
	 * 
303
	 * @param  \Callable|\Syscodes\Contracts\Debug\Handler  $handler
304
	 * 
305
	 * @return \Syscodes\Contracts\Debug\Handler
306
	 * 
307
	 * @throws \InvalidArgumentException If argument is not callable or instance of \Syscodes\Contracts\Debug\Handler
308
	 */
309
	protected function resolveHandler($handler)
310
	{
311
		if (is_callable($handler)) {
312
			$handler = new CallbackHandler($handler);
313
		}
314
315
		if ( ! $handler instanceof MainHandler) {
316
			throw new InvalidArgumentException(
317
				"Argument to " . __METHOD__ . " must be a callable, or instance of ".
318
				"Syscodes\\Contracts\\Debug\\Handler"
319
			);
320
		}
321
322
		return $handler;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $handler returns the type Syscodes\Debug\Handlers\CallbackHandler which is incompatible with the documented return type Syscodes\Contracts\Debug\Handler.
Loading history...
323
	}
324
325
	/**
326
	 * Returns an array with all handlers, in the order they were added to the stack.
327
	 * 
328
	 * @return array
329
	 */
330
	public function getHandlers()
331
	{
332
		return $this->handlerStack;
333
	}
334
335
	/**
336
	 * Clears all handlers in the handlerStack, including the default PleasingPage handler.
337
	 * 
338
	 * @return $this
339
	 */
340
	public function clearHandlers()
341
	{
342
		$this->handlerStack = [];
343
344
		return $this;
345
	}
346
347
	/**
348
	 * Removes the last handler in the stack and returns it.
349
	 * 
350
	 * @return array|null
351
	 */
352
	public function popHandler()
353
	{
354
		return array_pop($this->handlerStack);
355
	}
356
357
	/**
358
	 * Gets supervisor already specified.
359
	 * 
360
	 * @param  \Throwable  $exception
361
	 * 
362
	 * @return \Syscodes\Debug\Engine\Supervisor
0 ignored issues
show
Bug introduced by
The type Syscodes\Debug\Engine\Supervisor was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
363
	 */
364
	protected function getSupervisor(Throwable $exception)
365
	{
366
		return new Supervisor($exception);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new Syscodes\Debu...\Supervisor($exception) returns the type Syscodes\Debug\FrameHandler\Supervisor which is incompatible with the documented return type Syscodes\Debug\Engine\Supervisor.
Loading history...
367
	}
368
369
	/**
370
	 * Unregisters all handlers registered by this Debug instance.
371
	 * 
372
	 * @return void
373
	 */
374
	public function off()
375
	{
376
		$this->system->restoreExceptionHandler();
377
		$this->system->restoreErrorHandler();
378
	}
379
	
380
	/**
381
	 * Registers this instance as an error handler.
382
	 * 
383
	 * @return void
384
	 */
385
	public function on() 
386
	{
387
		// Set the exception handler
388
		$this->system->setExceptionHandler([$this, self::EXCEPTION_HANDLER]);
389
		// Set the error handler
390
		$this->system->setErrorHandler([$this, self::ERROR_HANDLER]);
391
		// Set the handler for shutdown to catch Parse errors
392
		$this->system->registerShutdownFunction([$this, self::SHUTDOWN_HANDLER]);
393
	}
394
395
	/**
396
	 * Lenevor Exception will by default send HTTP code 500, but you may wish
397
	 * to use 502, 503, or another 5xx family code.
398
	 * 
399
	 * @param  bool|int  $code
400
	 * 
401
	 * @return int|false
402
	 * 
403
	 * @throws \InvalidArgumentException
404
	 */
405
	public function sendHttpCode($code = null)
406
	{
407
		if (func_num_args() == 0) {
408
			return $this->sendHttpCode;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->sendHttpCode returns the type boolean which is incompatible with the documented return type false|integer.
Loading history...
409
		}
410
		
411
		if ( ! $code) {
412
			return $this->sendHttpCode = false;
413
		}
414
		
415
		if ($code === true) {
416
			$code = 500;
417
		}
418
		
419
		if ($code < 400 || 600 <= $code) {
420
			throw new InvalidArgumentException("Invalid status code {$code}, must be 4xx or 5xx");
421
		}
422
		
423
		return $this->sendHttpCode = $code;
0 ignored issues
show
Documentation Bug introduced by
The property $sendHttpCode was declared of type boolean, but $code is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
424
	}
425
426
	/**
427
	 * This will catch errors that are generated at the shutdown level of execution.
428
	 *
429
	 * @return void
430
	 *
431
	 * @throws \ErrorException
432
	 */
433
	public function handleShutdown()
434
	{
435
		$this->throwExceptions = false;
436
437
		$error = $this->system->getLastError();
438
439
		// If we've got an error that hasn't been displayed, then convert
440
		// it to an Exception and use the Exception handler to display it
441
		// to the user
442
		if ($error && Misc::isFatalError($error['type'])) {
443
			$this->errorHandler($error['type'], $error['message'], $error['file'], $error['line']);
0 ignored issues
show
Bug introduced by
The method errorHandler() does not exist on Syscodes\Debug\GDebug. ( Ignorable by Annotation )

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

443
			$this->/** @scrutinizer ignore-call */ 
444
          errorHandler($error['type'], $error['message'], $error['file'], $error['line']);

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.

Loading history...
444
		}
445
	}
446
}