Passed
Push — 0.7.0 ( 3fbacd...941781 )
by Alexander
03:27 queued 11s
created

Handler::renderHttpException()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 8
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 14
rs 10
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\Core\Exceptions;
24
25
use Exception;
26
use Throwable;
27
use Syscodes\Console\Cli;
28
use Syscodes\Debug\GDebug;
29
use Syscodes\Http\Response;
30
use Psr\Log\LoggerInterface;
31
use Syscodes\Routing\Router;
32
use Syscodes\Collections\Arr;
33
use Syscodes\Debug\ExceptionHandler;
34
use Syscodes\Contracts\Container\Container;
35
use Syscodes\Core\Http\Exceptions\HttpException;
36
use Syscodes\Http\Exceptions\HttpResponseException;
37
use Syscodes\Debug\FatalExceptions\FlattenException;
38
use Syscodes\Core\Http\Exceptions\NotFoundHttpException;
39
use Syscodes\Database\Exceptions\ModelNotFoundException;
40
use Syscodes\Contracts\Debug\ExceptionHandler as ExceptionHandlerContract;
41
42
/**
43
 * The system's main exception class is loaded for activate the render method of debugging.
44
 * 
45
 * @author Alexander Campo <[email protected]>
46
 */
47
class Handler implements ExceptionHandlerContract
48
{
49
    /**
50
     * The container implementation.
51
     * 
52
     * @var \Syscodes\Contracts\Container\Container $container
53
     */
54
    protected $container;
55
56
    /**
57
     * A list of the exception types that should not be reported.
58
     * 
59
     * @var array $dontReport
60
     */
61
    protected $dontReport = [];
62
63
    /**
64
     * A list of the Core exception types that should not be reported.
65
     * 
66
     * @var array $coreDontReport
67
     */
68
    protected $coreDontReport = [
69
        HttpException::class,
70
        HttpResponseException::class,
71
        ModelNotFoundException::class,
72
    ];
73
74
    /**
75
     * The callbacks that should be used during reporting.
76
     * 
77
     * @var array $reportCallbacks
78
     */
79
    protected $reportCallbacks = [];
80
81
    /**
82
     * The callbacks that should be used during rendering.
83
     * 
84
     * @var array $renderCallbacks
85
     */
86
    protected $renderCallbacks = [];
87
88
    /**
89
     * Constructor. Create a new exception handler instance.
90
     * 
91
     * @param  \Syscodes\Contracts\Container\Container  $container
92
     * 
93
     * @return void
94
     */
95
    public function __construct(Container $container)
96
    {
97
        $this->container = $container;
98
99
        $this->register();
100
    }
101
102
    /**
103
     * Register the exception handling with callbacks for the application.
104
     * 
105
     * @return void
106
     */
107
    public function register() {}
108
109
    /**
110
     * Register a reportable callback.
111
     * 
112
     * @param  \callable  $callback
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...
113
     * 
114
     * @return $this
115
     */
116
    public function reportable(callable $callback)
117
    {
118
        $this->reportCallbacks[] = $callback;
119
120
        return $this;
121
    }
122
123
    /**
124
     * Register a renderable callback.
125
     * 
126
     * @param  \callable  $callback
127
     * 
128
     * @return $this
129
     */
130
    public function renderable(callable $callback)
131
    {
132
        $this->renderCallbacks[] = $callback;
133
134
        return $this;
135
    }
136
    
137
    /**
138
     * Report or log an exception.
139
     * 
140
     * @param  \Exception  $e
141
     * 
142
     * @return mixed
143
     * 
144
     * @throws \Exception
145
     */
146
    public function report(Throwable $e)
147
    {
148
        if ($this->shouldntReport($e)) {
149
            return;
150
        }
151
152
        if (method_exists($e, 'report')) {
153
            return $e->report($e);
154
        }
155
        
156
        foreach ($this->reportCallbacks as $reportCallback) {
157
            if ($reportCallback($e) === false) {
158
                return;
159
            }
160
        }
161
162
        try {
163
            $logger = $this->container->make(LoggerInterface::class);
164
        } catch (Exception $e) {
165
            throw $e;
166
        }
167
        
168
        $logger->error($e->getMessage());
169
    }
170
171
    /**
172
     * Determine if the exception should be reported.
173
     * 
174
     * @param  \Throwable  $e
175
     * 
176
     * @return bool
177
     */
178
    public function shouldReport(Throwable $e)
179
    {
180
        return ! $this->shouldntReport($e);
181
    }
182
183
    /**
184
     * Determine if the exception is in the "do not report" list.
185
     * 
186
     * @param  \Throwable  $e
187
     * 
188
     * @return bool
189
     */
190
    public function shouldntReport(Throwable $e)
191
    {
192
        $dontReport = array_merge($this->dontReport, $this->coreDontReport);
193
194
        foreach ($dontReport as $type) {
195
            if ($e instanceof $type) {
196
                return true;
197
            }
198
        }
199
200
        return false;
201
    }
202
203
    /**
204
     * Render an exception into a response.
205
     *
206
     * @param  \Syscodes\Http\Request  $request
207
     * @param  \Throwable  $e
208
     * 
209
     * @return \Syscodes\Http\Response
210
     */
211
    public function render($request, Throwable $e)
212
    {
213
        if (method_exists($e, 'render') && $response = $e->render($request)) {
214
            return Router::toResponse($request, $response);
215
        }
216
        
217
        $e = $this->prepareException($e);
218
219
        foreach ($this->renderCallbacks as $renderCallback) {
220
            $response = $renderCallback($e, $request);
221
222
            if ( ! is_null($response)) {
223
                return $response;
224
            }
225
        }
226
227
        if ($e instanceof HttpResponseException) {
228
            $e->getResponse();
229
        }
230
231
        return $this->prepareResponse($request, $e);
232
    }
233
234
    /**
235
     * Prepare exception for rendering.
236
     * 
237
     * @param  \Throwable  $e
238
     * 
239
     * @return \Throwable
240
     */
241
    protected function prepareException(Throwable $e)
242
    {
243
        if ($e instanceof ModelNotFoundException) {
244
            $e = new NotFoundHttpException($e->getMessage(), $e);
245
        }
246
247
        return $e;
248
    }
249
     
250
    /**
251
     * Prepare a response for the given exception.
252
     * 
253
     * @param  \Syscodes\Http\Request  $request
254
     * @param  \Throwable  $e
255
     * 
256
     * @return \Syscodes\Http\Response
257
     * 
258
     * @uses   \Syscodes\Core\Http\Exceptions\HttpException
259
     */
260
    protected function prepareResponse($request, Throwable $e)
0 ignored issues
show
Unused Code introduced by
The parameter $request 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 ignore-unused  annotation

260
    protected function prepareResponse(/** @scrutinizer ignore-unused */ $request, Throwable $e)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
261
    {
262
        if ( ! $this->isHttpException($e) && config('app.debug')) {
263
            return $this->toSyscodesResponse($this->convertExceptionToResponse($e), $e);
264
        }
265
266
        // When the debug is not active, the HTTP 500 code view is throw
267
        if ( ! $this->isHttpException($e)) {
268
            $e = new HttpException(500, $e->getMessage());
269
        }
270
271
        return $this->toSyscodesResponse($this->renderHttpException($e), $e);
272
    }
273
274
    /**
275
     * Render the given HttpException.
276
     * 
277
     * @param  \Syscodes\Core\Http\Exceptions\HttpException  $e
278
     * 
279
     * @return \Syscodes\Http\Response
280
     */
281
    protected function renderHttpException(HttpException $e)
282
    {
283
        $this->registerViewErrorPaths();
284
285
        if (view()->viewExists($view = $this->getHttpExceptionView($e))) {
0 ignored issues
show
Bug introduced by
The method viewExists() does not exist on Syscodes\View\View. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

285
        if (view()->/** @scrutinizer ignore-call */ viewExists($view = $this->getHttpExceptionView($e))) {
Loading history...
286
            return response()->view(
0 ignored issues
show
Bug introduced by
The method view() does not exist on Syscodes\Http\Response. ( Ignorable by Annotation )

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

286
            return response()->/** @scrutinizer ignore-call */ view(

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...
287
                $view, 
288
                ['exception' => $e],
289
                $e->getStatusCode(),
290
                $e->getHeaders()
291
            );
292
        }
293
294
        return $this->convertExceptionToResponse($e);
295
    }
296
297
    /**
298
     * Register the error view paths.
299
     * 
300
     * @return void
301
     */
302
    protected function registerViewErrorPaths()
303
    {
304
        (new RegisterErrorViewPaths)();
305
    }
306
307
    /**
308
     * Get the view used to render HTTP exceptions.
309
     * 
310
     * @param  \Syscodes\Core\Http\Exceptions\HttpException  $e
311
     * 
312
     * @return string
313
     */
314
    protected function getHttpExceptionView(HttpException $e)
315
    {
316
        return "errors::{$e->getStatusCode()}";
317
    }
318
319
    /**
320
     * Create a response for the given exception.
321
     * 
322
     * @param  \Exception  $e
323
     * 
324
     * @return \Syscodes\Http\Response
325
     */
326
    protected function convertExceptionToResponse(Throwable $e)
327
    {
328
        return Response::render(
329
            $this->renderExceptionContent($e),
330
            $this->isHttpException($e) ? $e->getStatusCode() : 500,
0 ignored issues
show
Bug introduced by
The method getStatusCode() does not exist on Throwable. It seems like you code against a sub-type of Throwable such as Syscodes\Core\Http\Exceptions\HttpException. ( Ignorable by Annotation )

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

330
            $this->isHttpException($e) ? $e->/** @scrutinizer ignore-call */ getStatusCode() : 500,
Loading history...
331
            $this->isHttpException($e) ? $e->getHeaders() : []
0 ignored issues
show
Bug introduced by
The method getHeaders() does not exist on Throwable. It seems like you code against a sub-type of Throwable such as Syscodes\Core\Http\Exceptions\HttpException. ( Ignorable by Annotation )

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

331
            $this->isHttpException($e) ? $e->/** @scrutinizer ignore-call */ getHeaders() : []
Loading history...
332
        );
333
    }
334
335
    /**
336
     * Gets the response content for the given exception.
337
     * 
338
     * @param  \Throwable  $e
339
     * 
340
     * @return string
341
     */
342
    protected function renderExceptionContent(Throwable $e)
343
    {
344
        try {
345
            return config('app.debug') && class_exists(GDebug::class)
346
                        ? $this->renderExceptionWithGDebug($e) 
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->renderExceptionWithGDebug($e) targeting Syscodes\Core\Exceptions...erExceptionWithGDebug() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
347
                        : $this->renderExceptionWithFlatDesignDebug($e, config('app.debug'));
0 ignored issues
show
Bug introduced by
It seems like config('app.debug') can also be of type Syscodes\Config\Configure; however, parameter $debug of Syscodes\Core\Exceptions...onWithFlatDesignDebug() does only seem to accept boolean, 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 ignore-type  annotation

347
                        : $this->renderExceptionWithFlatDesignDebug($e, /** @scrutinizer ignore-type */ config('app.debug'));
Loading history...
348
        } catch (Exception $e) {
349
            $this->renderExceptionWithFlatDesignDebug($e, config('app.debug'));
350
        }
351
    }
352
353
    /**
354
     * Render an exception to a string using "GDebug".
355
     * 
356
     * @param  \Throwable  $e
357
     * 
358
     * @return void
359
     * 
360
     * @uses   \Syscodes\Debug\GDebug
361
     */
362
    protected function renderExceptionWithGDebug(Throwable $e)
363
    {
364
        return take(new GDebug, function ($debug) {
0 ignored issues
show
Bug introduced by
new Syscodes\Debug\GDebug() of type Syscodes\Debug\GDebug is incompatible with the type string expected by parameter $value of take(). ( Ignorable by Annotation )

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

364
        return take(/** @scrutinizer ignore-type */ new GDebug, function ($debug) {
Loading history...
Bug Best Practice introduced by
The expression return take(new Syscodes...})->handleException($e) returns the type string which is incompatible with the documented return type void.
Loading history...
365
            
366
            $debug->pushHandler($this->DebugHandler());
367
368
            $debug->writeToOutput(false);
369
370
            $debug->allowQuit(false);
371
372
        })->handleException($e);
373
    }
374
375
    /**
376
     * Get the Debug handler for the application.
377
     *
378
     * @return \Syscodes\Debug\Handlers\MainHandler
379
     */
380
    protected function DebugHandler()
381
    {
382
        return (new DebugHandler)->initDebug();
383
    }
384
385
    /**
386
     * Render an exception to a string using Flat Design Debug.
387
     * 
388
     * @param  \Throwable  $e
389
     * @param  bool  $debug
390
     * 
391
     * @return string
392
     */
393
    protected function renderExceptionWithFlatDesignDebug(Throwable $e, $debug)
394
    {
395
        return (new ExceptionHandler($debug))->getHtmlResponse(
396
            FlattenException::make($e)
0 ignored issues
show
Bug introduced by
Syscodes\Debug\FatalExce...ttenException::make($e) of type void is incompatible with the type Exception|Syscodes\Debug...ptions\FlattenException expected by parameter $exception of Syscodes\Debug\ExceptionHandler::getHtmlResponse(). ( Ignorable by Annotation )

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

396
            /** @scrutinizer ignore-type */ FlattenException::make($e)
Loading history...
Bug introduced by
Are you sure the usage of Syscodes\Debug\FatalExce...ttenException::make($e) targeting Syscodes\Debug\FatalExce...lattenException::make() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
397
        );
398
    }
399
400
    /**
401
     * Map the given exception into an Syscodes response.
402
     * 
403
     * @param  \Syscodes\Http\Response  $response
404
     * @param  \Exception  $e 
405
     * 
406
     * @return \Syscodes\Http\Response
407
     */
408
    protected function toSyscodesResponse($response, Throwable $e)
409
    {
410
        if ($response instanceof RedirectResponse) {
0 ignored issues
show
Bug introduced by
The type Syscodes\Core\Exceptions\RedirectResponse 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...
411
            $response = new RedirectResponse(
412
                $response->getTargetUrl(), $response->status(), $response->headers->all()
0 ignored issues
show
Bug introduced by
The method getTargetUrl() does not exist on Syscodes\Http\Response. It seems like you code against a sub-type of Syscodes\Http\Response such as Syscodes\Http\RedirectResponse. ( Ignorable by Annotation )

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

412
                $response->/** @scrutinizer ignore-call */ 
413
                           getTargetUrl(), $response->status(), $response->headers->all()
Loading history...
413
            );
414
        } else {
415
            $response = new Response(
416
                $response->content(), $response->status(), $response->headers->all()
417
            );
418
        }
419
420
        return $response->withException($e);
421
    }
422
423
    /**
424
     * Render an exception to the console.
425
     * 
426
     * @param  \Throwable  $e
427
     * 
428
     * @return void
429
     */
430
    public function renderForConsole(Throwable $e)
431
    {
432
        $message = sprintf(
433
            Cli::color("%s: ", 'light_red').
434
            Cli::color("%s in file %s on line %d", 'light_cyan')."\n\n%s\n",
435
            getClass($e, true),
0 ignored issues
show
Bug introduced by
getClass($e, true) of type array is incompatible with the type double|integer|string expected by parameter $values of sprintf(). ( Ignorable by Annotation )

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

435
            /** @scrutinizer ignore-type */ getClass($e, true),
Loading history...
436
            $e->getMessage(),
437
            $e->getFile(),
438
            $e->getLine(),
439
            $e->getTraceAsString()
440
        );
441
442
        echo $message;
443
    }
444
445
    /**
446
     * Determine if the given exception is an HTTP exception.
447
     * 
448
     * @param  \Throwable  $e
449
     * 
450
     * @return bool
451
     */
452
    protected function isHttpException(Throwable $e)
453
    {
454
        return $e instanceof HttpException;
455
    }
456
}