Passed
Push — 0.7.0 ( 2cebcc...974078 )
by Alexander
03:14 queued 10s
created

Handler::renderable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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

245
    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...
246
    {
247
        if ( ! $this->isHttpException($e) && config('app.debug')) {
248
            return $this->toSyscodesResponse($this->convertExceptionToResponse($e), $e);
249
        }
250
251
        // When the debug is not active, the HTTP 500 code view is throw
252
        if ( ! $this->isHttpException($e)) {
253
            $e = new HttpException(500, $e->getMessage());
254
        }
255
256
        return $this->toSyscodesResponse($this->renderHttpException($e), $e);
257
    }
258
259
    /**
260
     * Render the given HttpException.
261
     * 
262
     * @param  \Syscodes\Core\Http\Exceptions\HttpException  $e
263
     * 
264
     * @return \Syscodes\Http\Response
265
     */
266
    protected function renderHttpException(HttpException $e)
267
    {
268
        $this->registerViewErrorPaths();
269
270
        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

270
        if (view()->/** @scrutinizer ignore-call */ viewExists($view = $this->getHttpExceptionView($e))) {
Loading history...
271
            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

271
            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...
272
                $view, 
273
                ['exception' => $e],
274
                $e->getStatusCode(),
275
                $e->getHeaders()
276
            );
277
        }
278
279
        return $this->convertExceptionToResponse($e);
280
    }
281
282
    /**
283
     * Register the error view paths.
284
     * 
285
     * @return void
286
     */
287
    protected function registerViewErrorPaths()
288
    {
289
        (new RegisterErrorViewPaths)();
290
    }
291
292
    /**
293
     * Get the view used to render HTTP exceptions.
294
     * 
295
     * @param  \Syscodes\Core\Http\Exceptions\HttpException  $e
296
     * 
297
     * @return string
298
     */
299
    protected function getHttpExceptionView(HttpException $e)
300
    {
301
        return "errors::{$e->getStatusCode()}";
302
    }
303
304
    /**
305
     * Create a response for the given exception.
306
     * 
307
     * @param  \Exception  $e
308
     * 
309
     * @return \Syscodes\Http\Response
310
     */
311
    protected function convertExceptionToResponse(Throwable $e)
312
    {
313
        return Response::render(
314
            $this->renderExceptionContent($e),
315
            $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

315
            $this->isHttpException($e) ? $e->/** @scrutinizer ignore-call */ getStatusCode() : 500,
Loading history...
316
            $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

316
            $this->isHttpException($e) ? $e->/** @scrutinizer ignore-call */ getHeaders() : []
Loading history...
317
        );
318
    }
319
320
    /**
321
     * Gets the response content for the given exception.
322
     * 
323
     * @param  \Throwable  $e
324
     * 
325
     * @return string
326
     */
327
    protected function renderExceptionContent(Throwable $e)
328
    {
329
        try {
330
            return config('app.debug') && class_exists(GDebug::class)
331
                        ? $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...
332
                        : $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

332
                        : $this->renderExceptionWithFlatDesignDebug($e, /** @scrutinizer ignore-type */ config('app.debug'));
Loading history...
333
        } catch (Exception $e) {
334
            $this->renderExceptionWithFlatDesignDebug($e, config('app.debug'));
335
        }
336
    }
337
338
    /**
339
     * Render an exception to a string using "GDebug".
340
     * 
341
     * @param  \Throwable  $e
342
     * 
343
     * @return void
344
     * 
345
     * @uses   \Syscodes\Debug\GDebug
346
     */
347
    protected function renderExceptionWithGDebug(Throwable $e)
348
    {
349
        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

349
        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...
350
            
351
            $debug->pushHandler($this->DebugHandler());
352
353
            $debug->writeToOutput(false);
354
355
            $debug->allowQuit(false);
356
357
        })->handleException($e);
358
    }
359
360
    /**
361
     * Get the Debug handler for the application.
362
     *
363
     * @return \Syscodes\Debug\Handlers\MainHandler
364
     */
365
    protected function DebugHandler()
366
    {
367
        return (new DebugHandler)->initDebug();
368
    }
369
370
    /**
371
     * Render an exception to a string using Flat Design Debug.
372
     * 
373
     * @param  \Throwable  $e
374
     * @param  bool  $debug
375
     * 
376
     * @return string
377
     */
378
    protected function renderExceptionWithFlatDesignDebug(Throwable $e, $debug)
379
    {
380
        return (new ExceptionHandler($debug))->getHtmlResponse(
381
            FlattenException::make($e)
0 ignored issues
show
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...
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

381
            /** @scrutinizer ignore-type */ FlattenException::make($e)
Loading history...
382
        );
383
    }
384
385
    /**
386
     * Map the given exception into an Syscodes response.
387
     * 
388
     * @param  \Syscodes\Http\Response  $response
389
     * @param  \Exception  $e 
390
     * 
391
     * @return \Syscodes\Http\Response
392
     */
393
    protected function toSyscodesResponse($response, Throwable $e)
394
    {
395
        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...
396
            $response = new RedirectResponse(
397
                $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

397
                $response->/** @scrutinizer ignore-call */ 
398
                           getTargetUrl(), $response->status(), $response->headers->all()
Loading history...
398
            );
399
        } else {
400
            $response = new Response(
401
                $response->content(), $response->status(), $response->headers->all()
402
            );
403
        }
404
405
        return $response->withException($e);
406
    }
407
408
    /**
409
     * Render an exception to the console.
410
     * 
411
     * @param  \Throwable  $e
412
     * 
413
     * @return void
414
     */
415
    public function renderForConsole(Throwable $e)
416
    {
417
        $message = sprintf(
418
            Cli::color("%s: ", 'light_red').
419
            Cli::color("%s in file %s on line %d", 'light_cyan')."\n\n%s\n",
420
            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

420
            /** @scrutinizer ignore-type */ getClass($e, true),
Loading history...
421
            $e->getMessage(),
422
            $e->getFile(),
423
            $e->getLine(),
424
            $e->getTraceAsString()
425
        );
426
427
        echo $message;
428
    }
429
430
    /**
431
     * Determine if the given exception is an HTTP exception.
432
     * 
433
     * @param  \Throwable  $e
434
     * 
435
     * @return bool
436
     */
437
    protected function isHttpException(Throwable $e)
438
    {
439
        return $e instanceof HttpException;
440
    }
441
}