Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
88.24% covered (warning)
88.24%
60 / 68
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Handler
88.24% covered (warning)
88.24%
60 / 68
50.00% covered (danger)
50.00%
2 / 4
18.53
0.00% covered (danger)
0.00%
0 / 1
 register
50.00% covered (danger)
50.00%
2 / 4
0.00% covered (danger)
0.00%
0 / 1
2.50
 render
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
4
 handleApiException
88.68% covered (warning)
88.68%
47 / 53
0.00% covered (danger)
0.00%
0 / 1
10.15
 getReponse
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace App\Exceptions;
4
5use App\Enums\ApiError;
6use App\Enums\ApiStatus;
7use App\Exceptions\Api\V2\ApiErrorException as V2ApiErrorException;
8use App\Exceptions\Api\V4\ApiErrorException as V4ApiErrorException;
9use App\Http\Resources\Api\V2\ApiResponse as V2ApiResponse;
10use App\Http\Resources\Api\V4\ApiResponse as V4ApiResponse;
11use App\Lib\Helper;
12use Illuminate\Auth\Access\AuthorizationException;
13use Illuminate\Auth\AuthenticationException;
14use Illuminate\Database\Eloquent\ModelNotFoundException;
15use Illuminate\Http\Exceptions\ThrottleRequestsException;
16use Illuminate\Http\JsonResponse;
17use Illuminate\Http\Request;
18use Illuminate\Validation\ValidationException;
19use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
20use Symfony\Component\HttpFoundation\Response;
21use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
22
23class Handler extends ExceptionHandler
24{
25    /**
26     * A list of the exception types that are not reported.
27     *
28     * @var array
29     */
30    protected $dontReport = [
31        ApiErrorException::class,
32        RedirectTo::class
33    ];
34
35    /**
36     * A list of the inputs that are never flashed for validation exceptions.
37     *
38     * @var array
39     */
40    protected $dontFlash = [
41        'password',
42        'password_confirmation',
43    ];
44
45    /**
46     * Register the exception handling callbacks for the application.
47     *
48     * @return void
49     */
50    public function register(): void
51    {
52        $this->reportable(function (\Throwable $e) {
53            if (app()->bound('sentry')) {
54                app('sentry')->captureException($e);
55            }
56        });
57    }
58
59    /**
60     * Render an exception into an HTTP response.
61     *
62     * @param  mixed      $request
63     * @param  \Throwable $e
64     * @return \Symfony\Component\HttpFoundation\Response
65     */
66    public function render(mixed $request, \Throwable $e): Response
67    {
68        if ((Helper::isRequestForApiV2($request) || Helper::isRequestForApiV4($request)) and $request->isJson()) {
69            return $this->handleApiException($request, $e);
70        } //end if
71        // @codeCoverageIgnoreStart
72        return parent::render($request, $e);
73        // @codeCoverageIgnoreEnd
74    }
75
76    /**
77     * Generate a json response for any apiV4 error case.
78     *
79     * @param \Illuminate\Http\Request $request
80     * @param \Throwable               $e
81     * @return \Illuminate\Http\JsonResponse
82     */
83    private function handleApiException(Request $request, \Throwable $e): JsonResponse
84    {
85        switch (get_class($e)) {
86            case AuthenticationException::class:
87                /** @var AuthenticationException $e */
88                return $this->getReponse(
89                    $request,
90                    401,
91                    ApiError::apiAuthFailed->value(),
92                    sprintf('%s, %s', ApiError::apiAuthFailed->label(), $e->getMessage())
93                );
94            case MethodNotAllowedHttpException::class:
95                /** @var MethodNotAllowedHttpException $e */
96                return $this->getReponse(
97                    $request,
98                    $e->getStatusCode(),
99                    ApiError::methodNotAllowed->value(),
100                    sprintf('%s, %s', ApiError::methodNotAllowed->label(), $e->getMessage())
101                );
102            case ValidationException::class:
103                /** @var ValidationException $e */
104                return $this->getReponse(
105                    $request,
106                    $e->status,
107                    ApiError::validationFailed->value(),
108                    sprintf(ApiError::validationFailed->label(), $e->getMessage()),
109                    ['errors' => $e->errors()]
110                );
111            case ModelNotFoundException::class:
112                /** @var ModelNotFoundException $e */
113                return $this->getReponse(
114                    $request,
115                    404,
116                    ApiError::modelNotFound->value(),
117                    sprintf(
118                        ApiError::modelNotFound->label(),
119                        \array_slice(explode('\\', $e->getModel()), -1)[0]
120                    )
121                );
122            case V4ApiErrorException::class:
123                /** @var \App\Exceptions\Api\V4\ApiErrorException $e */
124                return $this->getReponse(
125                    $request,
126                    $e->getStatusCode(),
127                    $e->getCode(),
128                    $e->getMessage()
129                );
130            case V2ApiErrorException::class:
131                /** @var \App\Exceptions\Api\V2\ApiErrorException $e */
132                return $this->getReponse(
133                    $request,
134                    $e->getStatusCode(),
135                    $e->getCode(),
136                    $e->getMessage()
137                );
138            case ThrottleRequestsException::class:
139                /** @var ThrottleRequestsException $e */
140                return $this->getReponse(
141                    $request,
142                    429,
143                    ApiError::throttleRequestsException->value(),
144                    ApiError::throttleRequestsException->label()
145                );
146            case AuthorizationException::class:
147                /** @var AuthorizationException $e */
148                return $this->getReponse(
149                    $request,
150                    403,
151                    ApiError::authorizationException->value(),
152                    ApiError::authorizationException->label()
153                );
154        } //end switch
155        // @codeCoverageIgnoreStart
156        if (config('app.env') === 'testing') {
157            // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Found
158            print_r((string)$e);
159        }
160        return $this->getReponse($request, 500, \intval($e->getCode()), $e->getMessage());
161        // @codeCoverageIgnoreEnd
162    }
163
164    /**
165     * Generate an ApiV4 json response.
166     *
167     * @param \Illuminate\Http\Request $request
168     * @param integer                  $httpCode
169     * @param integer                  $apiCode
170     * @param string                   $message
171     * @param array                    $data
172     * @return \Illuminate\Http\JsonResponse
173     */
174    private static function getReponse(
175        Request $request,
176        int $httpCode,
177        int $apiCode,
178        string $message,
179        array $data = []
180    ): JsonResponse {
181        if (Helper::isRequestForApiV2($request)) {
182            return (new V2ApiResponse(
183                (object)array_merge($data, ['message' => $message, 'code' => $apiCode]),
184                ApiStatus::KO
185            ))->toResponse($request)->setStatusCode($httpCode);
186        }
187        return (new V4ApiResponse(
188            (object)array_merge($data, ['message' => $message, 'code' => $apiCode]),
189            ApiStatus::KO
190        ))->toResponse($request)->setStatusCode($httpCode);
191    }
192}