*/ protected $dontFlash = [ 'current_password', 'password', 'password_confirmation', ]; /** * Register the exception handling callbacks for the application. */ public function register(): void { $this->reportable(function (Throwable $e) { // }); } public function render($request, Throwable $e) { if ($request->expectsJson()) { if ($e instanceof ValidationException) { return ApiError::response( 'validation_failed', 'Validation failed', 'The given data was invalid.', 422, ['errors' => $e->errors()], ); } $status = $this->isHttpException($e) ? $this->toHttpException($e)->getStatusCode() : 500; $code = $status >= 500 ? 'server_error' : 'request_failed'; return ApiError::response( $code, $status >= 500 ? 'Unexpected error' : 'Request could not be completed', $this->buildGenericMessage($status), $status, ); } if ($request->inertia()) { if ($e instanceof ValidationException) { return back()->withErrors($e->errors())->withInput($request->all()); } } if (! $request->expectsJson() && ! $request->inertia()) { if ($hintKey = $this->resolveServerErrorHint($e)) { $request->attributes->set('serverErrorHint', __($hintKey, [], app()->getLocale())); } } return parent::render($request, $e); } private function buildGenericMessage(int $status): string { if ($status >= 500) { return 'Something went wrong on our side. Please try again later.'; } return 'Your request could not be processed. Please verify the details and try again.'; } private function resolveServerErrorHint(Throwable $throwable): ?string { $status = $this->isHttpException($throwable) ? $this->toHttpException($throwable)->getStatusCode() : 500; if ($status < 500) { return null; } if ($this->exceptionChainContains($throwable, [ QueryException::class, DatabaseConnectionException::class, PDOException::class, 'Doctrine\\DBAL\\Exception', ])) { return 'marketing.server_error.hints.database'; } if ($this->exceptionChainContains($throwable, [ FilesystemException::class, 'League\\Flysystem\\UnableToWriteFile', 'League\\Flysystem\\UnableToCreateDirectory', 'League\\Flysystem\\UnableToCheckExistence', ])) { return 'marketing.server_error.hints.storage'; } if ($this->exceptionChainContains($throwable, [ InvalidQueueException::class, MaxAttemptsExceededException::class, 'RedisException', 'Predis\\Connection\\ConnectionException', ])) { return 'marketing.server_error.hints.queue'; } if ($this->exceptionChainContains($throwable, [ HttpClientConnectionException::class, 'GuzzleHttp\\Exception\\ConnectException', 'Psr\\Http\\Client\\NetworkExceptionInterface', 'Symfony\\Component\\HttpClient\\Exception\\TransportExceptionInterface', ])) { return 'marketing.server_error.hints.network'; } return 'marketing.server_error.hints.generic'; } private function exceptionChainContains(Throwable $throwable, array $classNames): bool { do { foreach ($classNames as $className) { if ($this->throwableIsInstanceOf($throwable, $className)) { return true; } } $throwable = $throwable->getPrevious(); } while ($throwable); return false; } private function throwableIsInstanceOf(Throwable $throwable, string $className): bool { if ($className === '') { return false; } if (! class_exists($className) && ! interface_exists($className)) { return false; } return $throwable instanceof $className; } }