Handler::prepareException()   A
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
nc 5
nop 1
dl 0
loc 14
ccs 0
cts 10
cp 0
crap 30
rs 9.4888
c 0
b 0
f 0
1
<?php
2
3
namespace CodexShaper\WP\Exceptions;
4
5
use Exception;
6
use Illuminate\Auth\Access\AuthorizationException;
7
use Illuminate\Auth\AuthenticationException;
8
use Illuminate\Contracts\Container\BindingResolutionException;
9
use Illuminate\Contracts\Container\Container;
10
use Illuminate\Contracts\Debug\ExceptionHandler as ExceptionHandlerContract;
11
use Illuminate\Contracts\Support\Responsable;
12
use Illuminate\Database\Eloquent\ModelNotFoundException;
13
use Illuminate\Http\Exceptions\HttpResponseException;
14
use Illuminate\Http\JsonResponse;
15
use Illuminate\Http\RedirectResponse;
16
use Illuminate\Http\Response;
17
use Illuminate\Routing\Router;
18
use Illuminate\Session\TokenMismatchException;
19
use Illuminate\Support\Arr;
20
use Illuminate\Support\Facades\Auth;
21
use Illuminate\Support\Facades\View;
22
use Illuminate\Support\ViewErrorBag;
23
use Illuminate\Validation\ValidationException;
24
use Psr\Log\LoggerInterface;
25
use Symfony\Component\Console\Application as ConsoleApplication;
26
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
27
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
28
use Symfony\Component\HttpFoundation\RedirectResponse as SymfonyRedirectResponse;
29
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
30
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
31
use Symfony\Component\HttpKernel\Exception\HttpException;
32
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
33
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
34
use Throwable;
35
use Whoops\Handler\HandlerInterface;
36
use Whoops\Run as Whoops;
37
38
class Handler implements ExceptionHandlerContract
39
{
40
    /**
41
     * The container implementation.
42
     *
43
     * @var \Illuminate\Contracts\Container\Container
44
     */
45
    protected $container;
46
47
    /**
48
     * A list of the exception types that are not reported.
49
     *
50
     * @var array
51
     */
52
    protected $dontReport = [];
53
54
    /**
55
     * A list of the internal exception types that should not be reported.
56
     *
57
     * @var array
58
     */
59
    protected $internalDontReport = [
60
        AuthenticationException::class,
61
        AuthorizationException::class,
62
        HttpException::class,
63
        HttpResponseException::class,
64
        ModelNotFoundException::class,
65
        SuspiciousOperationException::class,
66
        TokenMismatchException::class,
67
        ValidationException::class,
68
    ];
69
70
    /**
71
     * A list of the inputs that are never flashed for validation exceptions.
72
     *
73
     * @var array
74
     */
75
    protected $dontFlash = [
76
        'password',
77
        'password_confirmation',
78
    ];
79
80
    /**
81
     * Create a new exception handler instance.
82
     *
83
     * @param \Illuminate\Contracts\Container\Container $container
84
     *
85
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
86
     */
87
    public function __construct(Container $container)
88
    {
89
        $this->container = $container;
90
    }
91
92
    /**
93
     * Report or log an exception.
94
     *
95
     * @param \Throwable $e
96
     *
97
     * @throws \Exception
98
     *
99
     * @return void
100
     */
101
    public function report(Throwable $e)
102
    {
103
        if ($this->shouldntReport($e)) {
104
            return;
105
        }
106
107
        if (is_callable($reportCallable = [$e, 'report'])) {
108
            $this->container->call($reportCallable);
109
110
            return;
111
        }
112
113
        try {
114
            $logger = $this->container->make(LoggerInterface::class);
115
        } catch (Exception $ex) {
116
            throw $e;
117
        }
118
119
        $logger->error(
120
            $e->getMessage(),
121
            array_merge(
122
                $this->exceptionContext($e),
123
                $this->context(),
124
                ['exception' => $e]
125
            )
126
        );
127
    }
128
129
    /**
130
     * Determine if the exception should be reported.
131
     *
132
     * @param \Throwable $e
133
     *
134
     * @return bool
135
     */
136
    public function shouldReport(Throwable $e)
137
    {
138
        return !$this->shouldntReport($e);
139
    }
140
141
    /**
142
     * Determine if the exception is in the "do not report" list.
143
     *
144
     * @param \Throwable $e
145
     *
146
     * @return bool
147
     */
148
    protected function shouldntReport(Throwable $e)
149
    {
150
        $dontReport = array_merge($this->dontReport, $this->internalDontReport);
151
152
        return !is_null(Arr::first($dontReport, function ($type) use ($e) {
0 ignored issues
show
Documentation introduced by
$dontReport is of type array, but the function expects a object<Illuminate\Support\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
153
            return $e instanceof $type;
154
        }));
155
    }
156
157
    /**
158
     * Get the default exception context variables for logging.
159
     *
160
     * @param \Throwable $e
161
     *
162
     * @return array
163
     */
164
    protected function exceptionContext(Throwable $e)
0 ignored issues
show
Unused Code introduced by
The parameter $e is not used and could be removed.

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

Loading history...
165
    {
166
        return [];
167
    }
168
169
    /**
170
     * Get the default context variables for logging.
171
     *
172
     * @return array
173
     */
174
    protected function context()
175
    {
176
        try {
177
            return array_filter([
178
                'userId' => Auth::id(),
179
                // 'email' => optional(Auth::user())->email,
180
            ]);
181
        } catch (Throwable $e) {
182
            return [];
183
        }
184
    }
185
186
    /**
187
     * Render an exception into an HTTP response.
188
     *
189
     * @param \Illuminate\Http\Request $request
190
     * @param \Throwable               $e
191
     *
192
     * @throws \Throwable
193
     *
194
     * @return \Symfony\Component\HttpFoundation\Response
195
     */
196
    public function render($request, Throwable $e)
197
    {
198
        if (method_exists($e, 'render') && $response = $e->render($request)) {
199
            return Router::toResponse($request, $response);
200
        } elseif ($e instanceof Responsable) {
201
            return $e->toResponse($request);
202
        }
203
204
        $e = $this->prepareException($e);
205
206
        if ($e instanceof HttpResponseException) {
207
            return $e->getResponse();
208
        } elseif ($e instanceof AuthenticationException) {
0 ignored issues
show
Bug introduced by
The class Illuminate\Auth\AuthenticationException does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
209
            return $this->unauthenticated($request, $e);
210
        } elseif ($e instanceof ValidationException) {
0 ignored issues
show
Bug introduced by
The class Illuminate\Validation\ValidationException does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
211
            return $this->convertValidationExceptionToResponse($e, $request);
212
        }
213
214
        return $request->expectsJson()
215
                    ? $this->prepareJsonResponse($request, $e)
216
                    : $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
        } elseif ($e instanceof AuthorizationException) {
0 ignored issues
show
Bug introduced by
The class Illuminate\Auth\Access\AuthorizationException does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
231
            $e = new AccessDeniedHttpException($e->getMessage(), $e);
232
        } elseif ($e instanceof TokenMismatchException) {
233
            $e = new HttpException(419, $e->getMessage(), $e);
234
        } elseif ($e instanceof SuspiciousOperationException) {
235
            $e = new NotFoundHttpException('Bad hostname provided.', $e);
236
        }
237
238
        return $e;
239
    }
240
241
    /**
242
     * Convert an authentication exception into a response.
243
     *
244
     * @param \Illuminate\Http\Request                 $request
245
     * @param \Illuminate\Auth\AuthenticationException $exception
246
     *
247
     * @return \Symfony\Component\HttpFoundation\Response
248
     */
249
    protected function unauthenticated($request, AuthenticationException $exception)
250
    {
251
        return $request->expectsJson()
252
                    ? response()->json(['message' => $exception->getMessage()], 401)
253
                    : redirect()->guest($exception->redirectTo() ?? route('login'));
254
    }
255
256
    /**
257
     * Create a response object from the given validation exception.
258
     *
259
     * @param \Illuminate\Validation\ValidationException $e
260
     * @param \Illuminate\Http\Request                   $request
261
     *
262
     * @return \Symfony\Component\HttpFoundation\Response
263
     */
264
    protected function convertValidationExceptionToResponse(ValidationException $e, $request)
265
    {
266
        if ($e->response) {
267
            return $e->response;
268
        }
269
270
        return $request->expectsJson()
271
                    ? $this->invalidJson($request, $e)
272
                    : $this->invalid($request, $e);
273
    }
274
275
    /**
276
     * Convert a validation exception into a response.
277
     *
278
     * @param \Illuminate\Http\Request                   $request
279
     * @param \Illuminate\Validation\ValidationException $exception
280
     *
281
     * @return \Illuminate\Http\Response
282
     */
283
    protected function invalid($request, ValidationException $exception)
284
    {
285
        return redirect($exception->redirectTo ?? url()->previous())
286
                    ->withInput(Arr::except($request->input(), $this->dontFlash))
287
                    ->withErrors($exception->errors(), $exception->errorBag);
288
    }
289
290
    /**
291
     * Convert a validation exception into a JSON response.
292
     *
293
     * @param \Illuminate\Http\Request                   $request
294
     * @param \Illuminate\Validation\ValidationException $exception
295
     *
296
     * @return \Illuminate\Http\JsonResponse
297
     */
298
    protected function invalidJson($request, ValidationException $exception)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

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

Loading history...
299
    {
300
        return response()->json([
301
            'message' => $exception->getMessage(),
302
            'errors'  => $exception->errors(),
303
        ], $exception->status);
304
    }
305
306
    /**
307
     * Prepare a response for the given exception.
308
     *
309
     * @param \Illuminate\Http\Request $request
310
     * @param \Throwable               $e
311
     *
312
     * @return \Symfony\Component\HttpFoundation\Response
313
     */
314
    protected function prepareResponse($request, Throwable $e)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

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

Loading history...
315
    {
316
        if (!$this->isHttpException($e) && $this->container['config']['app.debug']) {
317
            return $this->toIlluminateResponse($this->convertExceptionToResponse($e), $e);
318
        }
319
320
        if (!$this->isHttpException($e)) {
321
            $e = new HttpException(500, $e->getMessage());
322
        }
323
324
        return $this->toIlluminateResponse(
325
            $this->renderHttpException($e),
0 ignored issues
show
Compatibility introduced by
$e of type object<Throwable> is not a sub-type of object<Symfony\Component...HttpExceptionInterface>. It seems like you assume a child interface of the interface Throwable to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
326
            $e
327
        );
328
    }
329
330
    /**
331
     * Create a Symfony response for the given exception.
332
     *
333
     * @param \Throwable $e
334
     *
335
     * @return \Symfony\Component\HttpFoundation\Response
336
     */
337
    protected function convertExceptionToResponse(Throwable $e)
338
    {
339
        return SymfonyResponse::create(
0 ignored issues
show
Deprecated Code introduced by
The method Symfony\Component\HttpFo...tion\Response::create() has been deprecated with message: since Symfony 5.1, use __construct() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
340
            $this->renderExceptionContent($e),
341
            $this->isHttpException($e) ? $e->getStatusCode() : 500,
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Throwable as the method getStatusCode() does only exist in the following implementations of said interface: Illuminate\Http\Exceptions\PostTooLargeException, Illuminate\Http\Exceptio...rottleRequestsException, Illuminate\Routing\Excep...validSignatureException, Symfony\Component\HttpKe...cessDeniedHttpException, Symfony\Component\HttpKe...BadRequestHttpException, Symfony\Component\HttpKe...n\ConflictHttpException, Symfony\Component\HttpKe...ption\GoneHttpException, Symfony\Component\HttpKe...Exception\HttpException, Symfony\Component\HttpKe...thRequiredHttpException, Symfony\Component\HttpKe...NotAllowedHttpException, Symfony\Component\HttpKe...AcceptableHttpException, Symfony\Component\HttpKe...n\NotFoundHttpException, Symfony\Component\HttpKe...tionFailedHttpException, Symfony\Component\HttpKe...onRequiredHttpException, Symfony\Component\HttpKe...navailableHttpException, Symfony\Component\HttpKe...nyRequestsHttpException, Symfony\Component\HttpKe...authorizedHttpException, Symfony\Component\HttpKe...ableEntityHttpException, Symfony\Component\HttpKe...dMediaTypeHttpException.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
342
            $this->isHttpException($e) ? $e->getHeaders() : []
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Throwable as the method getHeaders() does only exist in the following implementations of said interface: Illuminate\Http\Exceptions\PostTooLargeException, Illuminate\Http\Exceptio...rottleRequestsException, Illuminate\Routing\Excep...validSignatureException, Symfony\Component\HttpKe...cessDeniedHttpException, Symfony\Component\HttpKe...BadRequestHttpException, Symfony\Component\HttpKe...n\ConflictHttpException, Symfony\Component\HttpKe...ption\GoneHttpException, Symfony\Component\HttpKe...Exception\HttpException, Symfony\Component\HttpKe...thRequiredHttpException, Symfony\Component\HttpKe...NotAllowedHttpException, Symfony\Component\HttpKe...AcceptableHttpException, Symfony\Component\HttpKe...n\NotFoundHttpException, Symfony\Component\HttpKe...tionFailedHttpException, Symfony\Component\HttpKe...onRequiredHttpException, Symfony\Component\HttpKe...navailableHttpException, Symfony\Component\HttpKe...nyRequestsHttpException, Symfony\Component\HttpKe...authorizedHttpException, Symfony\Component\HttpKe...ableEntityHttpException, Symfony\Component\HttpKe...dMediaTypeHttpException.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
343
        );
344
    }
345
346
    /**
347
     * Get the response content for the given exception.
348
     *
349
     * @param \Throwable $e
350
     *
351
     * @return string
352
     */
353
    protected function renderExceptionContent(Throwable $e)
0 ignored issues
show
Unused Code introduced by
The parameter $e is not used and could be removed.

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

Loading history...
354
    {
355
        try {
356
            return $this->container['config']['app.debug'] && class_exists(Whoops::class)
357
                        ? $this->renderExceptionWithWhoops($e)
358
                        : $this->renderExceptionWithSymfony($e, $this->container['config']['app.debug']);
359
        } catch (Exception $e) {
360
            return $this->renderExceptionWithSymfony($e, $this->container['config']['app.debug']);
361
        }
362
    }
363
364
    /**
365
     * Render an exception to a string using "Whoops".
366
     *
367
     * @param \Throwable $e
368
     *
369
     * @return string
370
     */
371
    protected function renderExceptionWithWhoops(Throwable $e)
372
    {
373
        return tap(new Whoops(), function ($whoops) {
374
            $whoops->appendHandler($this->whoopsHandler());
375
376
            $whoops->writeToOutput(false);
377
378
            $whoops->allowQuit(false);
379
        })->handleException($e);
380
    }
381
382
    /**
383
     * Get the Whoops handler for the application.
384
     *
385
     * @return \Whoops\Handler\Handler
386
     */
387
    protected function whoopsHandler()
388
    {
389
        try {
390
            return $this->container(HandlerInterface::class);
0 ignored issues
show
Bug introduced by
The method container() does not seem to exist on object<CodexShaper\WP\Exceptions\Handler>.

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...
391
        } catch (BindingResolutionException $e) {
392
            return (new WhoopsHandler())->forDebug();
393
        }
394
    }
395
396
    /**
397
     * Render an exception to a string using Symfony.
398
     *
399
     * @param \Throwable $e
400
     * @param bool       $debug
401
     *
402
     * @return string
403
     */
404
    protected function renderExceptionWithSymfony(Throwable $e, $debug)
405
    {
406
        $renderer = new HtmlErrorRenderer($debug);
407
408
        return $renderer->getBody($renderer->render($e));
409
    }
410
411
    /**
412
     * Render the given HttpException.
413
     *
414
     * @param \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $e
415
     *
416
     * @return \Symfony\Component\HttpFoundation\Response
417
     */
418
    protected function renderHttpException(HttpExceptionInterface $e)
419
    {
420
        $this->registerErrorViewPaths();
421
422
        if (view()->exists($view = $this->getHttpExceptionView($e))) {
0 ignored issues
show
Bug introduced by
The call to view() misses a required argument $view.

This check looks for function calls that miss required arguments.

Loading history...
423
            return response()->view($view, [
424
                'errors'    => new ViewErrorBag(),
425
                'exception' => $e,
426
            ], $e->getStatusCode(), $e->getHeaders());
427
        }
428
429
        return $this->convertExceptionToResponse($e);
430
    }
431
432
    /**
433
     * Register the error template hint paths.
434
     *
435
     * @return void
436
     */
437
    protected function registerErrorViewPaths()
438
    {
439
        $paths = collect($this->container['config']['view.paths']);
440
441
        View::replaceNamespace('errors', $paths->map(function ($path) {
442
            return "{$path}/errors";
443
        })->push(__DIR__.'/views')->all());
444
    }
445
446
    /**
447
     * Get the view used to render HTTP exceptions.
448
     *
449
     * @param \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $e
450
     *
451
     * @return string
452
     */
453
    protected function getHttpExceptionView(HttpExceptionInterface $e)
454
    {
455
        return "errors::{$e->getStatusCode()}";
456
    }
457
458
    /**
459
     * Map the given exception into an Illuminate response.
460
     *
461
     * @param \Symfony\Component\HttpFoundation\Response $response
462
     * @param \Throwable                                 $e
463
     *
464
     * @return \Illuminate\Http\Response
465
     */
466
    protected function toIlluminateResponse($response, Throwable $e)
467
    {
468
        if ($response instanceof SymfonyRedirectResponse) {
469
            $response = new RedirectResponse(
470
                $response->getTargetUrl(),
471
                $response->getStatusCode(),
472
                $response->headers->all()
473
            );
474
        } else {
475
            $response = new Response(
476
                $response->getContent(),
477
                $response->getStatusCode(),
478
                $response->headers->all()
479
            );
480
        }
481
482
        return $response->withException($e);
483
    }
484
485
    /**
486
     * Prepare a JSON response for the given exception.
487
     *
488
     * @param \Illuminate\Http\Request $request
489
     * @param \Throwable               $e
490
     *
491
     * @return \Illuminate\Http\JsonResponse
492
     */
493
    protected function prepareJsonResponse($request, Throwable $e)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

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

Loading history...
494
    {
495
        return new JsonResponse(
496
            $this->convertExceptionToArray($e),
497
            $this->isHttpException($e) ? $e->getStatusCode() : 500,
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Throwable as the method getStatusCode() does only exist in the following implementations of said interface: Illuminate\Http\Exceptions\PostTooLargeException, Illuminate\Http\Exceptio...rottleRequestsException, Illuminate\Routing\Excep...validSignatureException, Symfony\Component\HttpKe...cessDeniedHttpException, Symfony\Component\HttpKe...BadRequestHttpException, Symfony\Component\HttpKe...n\ConflictHttpException, Symfony\Component\HttpKe...ption\GoneHttpException, Symfony\Component\HttpKe...Exception\HttpException, Symfony\Component\HttpKe...thRequiredHttpException, Symfony\Component\HttpKe...NotAllowedHttpException, Symfony\Component\HttpKe...AcceptableHttpException, Symfony\Component\HttpKe...n\NotFoundHttpException, Symfony\Component\HttpKe...tionFailedHttpException, Symfony\Component\HttpKe...onRequiredHttpException, Symfony\Component\HttpKe...navailableHttpException, Symfony\Component\HttpKe...nyRequestsHttpException, Symfony\Component\HttpKe...authorizedHttpException, Symfony\Component\HttpKe...ableEntityHttpException, Symfony\Component\HttpKe...dMediaTypeHttpException.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
498
            $this->isHttpException($e) ? $e->getHeaders() : [],
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Throwable as the method getHeaders() does only exist in the following implementations of said interface: Illuminate\Http\Exceptions\PostTooLargeException, Illuminate\Http\Exceptio...rottleRequestsException, Illuminate\Routing\Excep...validSignatureException, Symfony\Component\HttpKe...cessDeniedHttpException, Symfony\Component\HttpKe...BadRequestHttpException, Symfony\Component\HttpKe...n\ConflictHttpException, Symfony\Component\HttpKe...ption\GoneHttpException, Symfony\Component\HttpKe...Exception\HttpException, Symfony\Component\HttpKe...thRequiredHttpException, Symfony\Component\HttpKe...NotAllowedHttpException, Symfony\Component\HttpKe...AcceptableHttpException, Symfony\Component\HttpKe...n\NotFoundHttpException, Symfony\Component\HttpKe...tionFailedHttpException, Symfony\Component\HttpKe...onRequiredHttpException, Symfony\Component\HttpKe...navailableHttpException, Symfony\Component\HttpKe...nyRequestsHttpException, Symfony\Component\HttpKe...authorizedHttpException, Symfony\Component\HttpKe...ableEntityHttpException, Symfony\Component\HttpKe...dMediaTypeHttpException.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
499
            JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
500
        );
501
    }
502
503
    /**
504
     * Convert the given exception to an array.
505
     *
506
     * @param \Throwable $e
507
     *
508
     * @return array
509
     */
510
    protected function convertExceptionToArray(Throwable $e)
511
    {
512
        return $this->container['config']['app.debug'] ? [
513
            'message'   => $e->getMessage(),
514
            'exception' => get_class($e),
515
            'file'      => $e->getFile(),
516
            'line'      => $e->getLine(),
517
            'trace'     => collect($e->getTrace())->map(function ($trace) {
518
                return Arr::except($trace, ['args']);
519
            })->all(),
520
        ] : [
521
            'message' => $this->isHttpException($e) ? $e->getMessage() : 'Server Error',
522
        ];
523
    }
524
525
    /**
526
     * Render an exception to the console.
527
     *
528
     * @param \Symfony\Component\Console\Output\OutputInterface $output
529
     * @param \Throwable                                        $e
530
     *
531
     * @return void
532
     */
533
    public function renderForConsole($output, Throwable $e)
534
    {
535
        (new ConsoleApplication())->renderThrowable($e, $output);
536
    }
537
538
    /**
539
     * Determine if the given exception is an HTTP exception.
540
     *
541
     * @param \Throwable $e
542
     *
543
     * @return bool
544
     */
545
    protected function isHttpException(Throwable $e)
546
    {
547
        return $e instanceof HttpExceptionInterface;
548
    }
549
}
550