Вход Регистрация
Файл: vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php
Строк: 1214
<?php

namespace IlluminateFoundationExceptions;

use 
Closure;
use 
Exception;
use 
IlluminateAuthAccessAuthorizationException;
use 
IlluminateAuthAuthenticationException;
use 
IlluminateCacheRateLimiter;
use 
IlluminateCacheRateLimitingLimit;
use 
IlluminateCacheRateLimitingUnlimited;
use 
IlluminateConsoleViewComponentsBulletList;
use 
IlluminateConsoleViewComponentsError;
use 
IlluminateContractsContainerContainer;
use 
IlluminateContractsDebugExceptionHandler as ExceptionHandlerContract;
use 
IlluminateContractsFoundationExceptionRenderer;
use 
IlluminateContractsSupportResponsable;
use 
IlluminateDatabaseEloquentModelNotFoundException;
use 
IlluminateDatabaseMultipleRecordsFoundException;
use 
IlluminateDatabaseRecordsNotFoundException;
use 
IlluminateHttpExceptionsHttpResponseException;
use 
IlluminateHttpJsonResponse;
use 
IlluminateHttpRedirectResponse;
use 
IlluminateHttpResponse;
use 
IlluminateRoutingExceptionsBackedEnumCaseNotFoundException;
use 
IlluminateRoutingRouter;
use 
IlluminateSessionTokenMismatchException;
use 
IlluminateSupportArr;
use 
IlluminateSupportFacadesAuth;
use 
IlluminateSupportLottery;
use 
IlluminateSupportReflector;
use 
IlluminateSupportTraitsReflectsClosures;
use 
IlluminateSupportViewErrorBag;
use 
IlluminateValidationValidationException;
use 
InvalidArgumentException;
use 
PsrLogLoggerInterface;
use 
PsrLogLogLevel;
use 
SymfonyComponentConsoleApplication as ConsoleApplication;
use 
SymfonyComponentConsoleExceptionCommandNotFoundException;
use 
SymfonyComponentErrorHandlerErrorRendererHtmlErrorRenderer;
use 
SymfonyComponentHttpFoundationExceptionSuspiciousOperationException;
use 
SymfonyComponentHttpFoundationRedirectResponse as SymfonyRedirectResponse;
use 
SymfonyComponentHttpFoundationResponse as SymfonyResponse;
use 
SymfonyComponentHttpKernelExceptionAccessDeniedHttpException;
use 
SymfonyComponentHttpKernelExceptionHttpException;
use 
SymfonyComponentHttpKernelExceptionHttpExceptionInterface;
use 
SymfonyComponentHttpKernelExceptionNotFoundHttpException;
use 
Throwable;
use 
WeakMap;

class 
Handler implements ExceptionHandlerContract
{
    use 
ReflectsClosures;

    
/**
     * The container implementation.
     *
     * @var IlluminateContractsContainerContainer
     */
    
protected $container;

    
/**
     * A list of the exception types that are not reported.
     *
     * @var array<int, class-string<Throwable>>
     */
    
protected $dontReport = [];

    
/**
     * The callbacks that should be used during reporting.
     *
     * @var IlluminateFoundationExceptionsReportableHandler[]
     */
    
protected $reportCallbacks = [];

    
/**
     * A map of exceptions with their corresponding custom log levels.
     *
     * @var array<class-string<Throwable>, PsrLogLogLevel::*>
     */
    
protected $levels = [];

    
/**
     * The callbacks that should be used during rendering.
     *
     * @var Closure[]
     */
    
protected $renderCallbacks = [];

    
/**
     * The registered exception mappings.
     *
     * @var array<string, Closure>
     */
    
protected $exceptionMap = [];

    
/**
     * Indicates that throttled keys should be hashed.
     *
     * @var bool
     */
    
protected $hashThrottleKeys true;

    
/**
     * A list of the internal exception types that should not be reported.
     *
     * @var array<int, class-string<Throwable>>
     */
    
protected $internalDontReport = [
        
AuthenticationException::class,
        
AuthorizationException::class,
        
BackedEnumCaseNotFoundException::class,
        
HttpException::class,
        
HttpResponseException::class,
        
ModelNotFoundException::class,
        
MultipleRecordsFoundException::class,
        
RecordsNotFoundException::class,
        
SuspiciousOperationException::class,
        
TokenMismatchException::class,
        
ValidationException::class,
    ];

    
/**
     * A list of the inputs that are never flashed for validation exceptions.
     *
     * @var array<int, string>
     */
    
protected $dontFlash = [
        
'current_password',
        
'password',
        
'password_confirmation',
    ];

    
/**
     * Indicates that an exception instance should only be reported once.
     *
     * @var bool
     */
    
protected $withoutDuplicates false;

    
/**
     * The already reported exception map.
     *
     * @var WeakMap
     */
    
protected $reportedExceptionMap;

    
/**
     * Create a new exception handler instance.
     *
     * @param  IlluminateContractsContainerContainer  $container
     * @return void
     */
    
public function __construct(Container $container)
    {
        
$this->container $container;

        
$this->reportedExceptionMap = new WeakMap;

        
$this->register();
    }

    
/**
     * Register the exception handling callbacks for the application.
     *
     * @return void
     */
    
public function register()
    {
        
//
    
}

    
/**
     * Register a reportable callback.
     *
     * @param  callable  $reportUsing
     * @return IlluminateFoundationExceptionsReportableHandler
     */
    
public function reportable(callable $reportUsing)
    {
        if (! 
$reportUsing instanceof Closure) {
            
$reportUsing Closure::fromCallable($reportUsing);
        }

        return 
tap(new ReportableHandler($reportUsing), function ($callback) {
            
$this->reportCallbacks[] = $callback;
        });
    }

    
/**
     * Register a renderable callback.
     *
     * @param  callable  $renderUsing
     * @return $this
     */
    
public function renderable(callable $renderUsing)
    {
        if (! 
$renderUsing instanceof Closure) {
            
$renderUsing Closure::fromCallable($renderUsing);
        }

        
$this->renderCallbacks[] = $renderUsing;

        return 
$this;
    }

    
/**
     * Register a new exception mapping.
     *
     * @param  Closure|string  $from
     * @param  Closure|string|null  $to
     * @return $this
     *
     * @throws InvalidArgumentException
     */
    
public function map($from$to null)
    {
        if (
is_string($to)) {
            
$to fn ($exception) => new $to(''0$exception);
        }

        if (
is_callable($from) && is_null($to)) {
            
$from $this->firstClosureParameterType($to $from);
        }

        if (! 
is_string($from) || ! $to instanceof Closure) {
            throw new 
InvalidArgumentException('Invalid exception mapping.');
        }

        
$this->exceptionMap[$from] = $to;

        return 
$this;
    }

    
/**
     * Indicate that the given exception type should not be reported.
     *
     * @param  string  $class
     * @return $this
     */
    
public function ignore(string $class)
    {
        
$this->dontReport[] = $class;

        return 
$this;
    }

    
/**
     * Set the log level for the given exception type.
     *
     * @param  class-string<Throwable>  $type
     * @param  PsrLogLogLevel::*  $level
     * @return $this
     */
    
public function level($type$level)
    {
        
$this->levels[$type] = $level;

        return 
$this;
    }

    
/**
     * Report or log an exception.
     *
     * @param  Throwable  $e
     * @return void
     *
     * @throws Throwable
     */
    
public function report(Throwable $e)
    {
        
$e $this->mapException($e);

        if (
$this->shouldntReport($e)) {
            return;
        }

        
$this->reportThrowable($e);
    }

    
/**
     * Reports error based on report method on exception or to logger.
     *
     * @param  Throwable  $e
     * @return void
     *
     * @throws Throwable
     */
    
protected function reportThrowable(Throwable $e): void
    
{
        
$this->reportedExceptionMap[$e] = true;

        if (
Reflector::isCallable($reportCallable = [$e'report']) &&
            
$this->container->call($reportCallable) !== false) {
            return;
        }

        foreach (
$this->reportCallbacks as $reportCallback) {
            if (
$reportCallback->handles($e) && $reportCallback($e) === false) {
                return;
            }
        }

        try {
            
$logger $this->newLogger();
        } catch (
Exception) {
            throw 
$e;
        }

        
$level Arr::first(
            
$this->levelsfn ($level$type) => $e instanceof $typeLogLevel::ERROR
        
);

        
$context $this->buildExceptionContext($e);

        
method_exists($logger$level)
            ? 
$logger->{$level}($e->getMessage(), $context)
            : 
$logger->log($level$e->getMessage(), $context);
    }

    
/**
     * Determine if the exception should be reported.
     *
     * @param  Throwable  $e
     * @return bool
     */
    
public function shouldReport(Throwable $e)
    {
        return ! 
$this->shouldntReport($e);
    }

    
/**
     * Determine if the exception is in the "do not report" list.
     *
     * @param  Throwable  $e
     * @return bool
     */
    
protected function shouldntReport(Throwable $e)
    {
        if (
$this->withoutDuplicates && ($this->reportedExceptionMap[$e] ?? false)) {
            return 
true;
        }

        
$dontReport array_merge($this->dontReport$this->internalDontReport);

        if (! 
is_null(Arr::first($dontReportfn ($type) => $e instanceof $type))) {
            return 
true;
        }

        return 
rescue(fn () => with($this->throttle($e), function ($throttle) use ($e) {
            if (
$throttle instanceof Unlimited || $throttle === null) {
                return 
false;
            }

            if (
$throttle instanceof Lottery) {
                return ! 
$throttle($e);
            }

            return ! 
$this->container->make(RateLimiter::class)->attempt(
                
with($throttle->key ?: 'illuminate:foundation:exceptions:'.$e::class, fn ($key) => $this->hashThrottleKeys md5($key) : $key),
                
$throttle->maxAttempts,
                
fn () => true,
                
60 $throttle->decayMinutes
            
);
        }), 
rescuefalsereportfalse);
    }

    
/**
     * Throttle the given exception.
     *
     * @param  Throwable  $e
     * @return IlluminateSupportLottery|IlluminateCacheRateLimitingLimit|null
     */
    
protected function throttle(Throwable $e)
    {
        return 
Limit::none();
    }

    
/**
     * Remove the given exception class from the list of exceptions that should be ignored.
     *
     * @param  string  $exception
     * @return $this
     */
    
public function stopIgnoring(string $exception)
    {
        
$this->dontReport collect($this->dontReport)
                ->
reject(fn ($ignored) => $ignored === $exception)->values()->all();

        
$this->internalDontReport collect($this->internalDontReport)
                ->
reject(fn ($ignored) => $ignored === $exception)->values()->all();

        return 
$this;
    }

    
/**
     * Create the context array for logging the given exception.
     *
     * @param  Throwable  $e
     * @return array
     */
    
protected function buildExceptionContext(Throwable $e)
    {
        return 
array_merge(
            
$this->exceptionContext($e),
            
$this->context(),
            [
'exception' => $e]
        );
    }

    
/**
     * Get the default exception context variables for logging.
     *
     * @param  Throwable  $e
     * @return array
     */
    
protected function exceptionContext(Throwable $e)
    {
        if (
method_exists($e'context')) {
            return 
$e->context();
        }

        return [];
    }

    
/**
     * Get the default context variables for logging.
     *
     * @return array
     */
    
protected function context()
    {
        try {
            return 
array_filter([
                
'userId' => Auth::id(),
            ]);
        } catch (
Throwable) {
            return [];
        }
    }

    
/**
     * Render an exception into an HTTP response.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  Throwable  $e
     * @return SymfonyComponentHttpFoundationResponse
     *
     * @throws Throwable
     */
    
public function render($requestThrowable $e)
    {
        
$e $this->mapException($e);

        if (
method_exists($e'render') && $response $e->render($request)) {
            return 
Router::toResponse($request$response);
        }

        if (
$e instanceof Responsable) {
            return 
$e->toResponse($request);
        }

        
$e $this->prepareException($e);

        if (
$response $this->renderViaCallbacks($request$e)) {
            return 
$response;
        }

        return 
match (true) {
            
$e instanceof HttpResponseException => $e->getResponse(),
            
$e instanceof AuthenticationException => $this->unauthenticated($request$e),
            
$e instanceof ValidationException => $this->convertValidationExceptionToResponse($e$request),
            default => 
$this->renderExceptionResponse($request$e),
        };
    }

    
/**
     * Prepare exception for rendering.
     *
     * @param  Throwable  $e
     * @return Throwable
     */
    
protected function prepareException(Throwable $e)
    {
        return 
match (true) {
            
$e instanceof BackedEnumCaseNotFoundException => new NotFoundHttpException($e->getMessage(), $e),
            
$e instanceof ModelNotFoundException => new NotFoundHttpException($e->getMessage(), $e),
            
$e instanceof AuthorizationException && $e->hasStatus() => new HttpException(
                
$e->status(), $e->response()?->message() ?: (Response::$statusTexts[$e->status()] ?? 'Whoops, looks like something went wrong.'), $e
            
),
            
$e instanceof AuthorizationException && ! $e->hasStatus() => new AccessDeniedHttpException($e->getMessage(), $e),
            
$e instanceof TokenMismatchException => new HttpException(419$e->getMessage(), $e),
            
$e instanceof SuspiciousOperationException => new NotFoundHttpException('Bad hostname provided.'$e),
            
$e instanceof RecordsNotFoundException => new NotFoundHttpException('Not found.'$e),
            default => 
$e,
        };
    }

    
/**
     * Map the exception using a registered mapper if possible.
     *
     * @param  Throwable  $e
     * @return Throwable
     */
    
protected function mapException(Throwable $e)
    {
        if (
method_exists($e'getInnerException') &&
            (
$inner $e->getInnerException()) instanceof Throwable) {
            return 
$inner;
        }

        foreach (
$this->exceptionMap as $class => $mapper) {
            if (
is_a($e$class)) {
                return 
$mapper($e);
            }
        }

        return 
$e;
    }

    
/**
     * Try to render a response from request and exception via render callbacks.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  Throwable  $e
     * @return mixed
     *
     * @throws ReflectionException
     */
    
protected function renderViaCallbacks($requestThrowable $e)
    {
        foreach (
$this->renderCallbacks as $renderCallback) {
            foreach (
$this->firstClosureParameterTypes($renderCallback) as $type) {
                if (
is_a($e$type)) {
                    
$response $renderCallback($e$request);

                    if (! 
is_null($response)) {
                        return 
$response;
                    }
                }
            }
        }
    }

    
/**
     * Render a default exception response if any.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  Throwable  $e
     * @return IlluminateHttpResponse|IlluminateHttpJsonResponse|IlluminateHttpRedirectResponse
     */
    
protected function renderExceptionResponse($requestThrowable $e)
    {
        return 
$this->shouldReturnJson($request$e)
                    ? 
$this->prepareJsonResponse($request$e)
                    : 
$this->prepareResponse($request$e);
    }

    
/**
     * Convert an authentication exception into a response.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  IlluminateAuthAuthenticationException  $exception
     * @return IlluminateHttpResponse|IlluminateHttpJsonResponse|IlluminateHttpRedirectResponse
     */
    
protected function unauthenticated($requestAuthenticationException $exception)
    {
        return 
$this->shouldReturnJson($request$exception)
                    ? 
response()->json(['message' => $exception->getMessage()], 401)
                    : 
redirect()->guest($exception->redirectTo() ?? route('login'));
    }

    
/**
     * Create a response object from the given validation exception.
     *
     * @param  IlluminateValidationValidationException  $e
     * @param  IlluminateHttpRequest  $request
     * @return SymfonyComponentHttpFoundationResponse
     */
    
protected function convertValidationExceptionToResponse(ValidationException $e$request)
    {
        if (
$e->response) {
            return 
$e->response;
        }

        return 
$this->shouldReturnJson($request$e)
                    ? 
$this->invalidJson($request$e)
                    : 
$this->invalid($request$e);
    }

    
/**
     * Convert a validation exception into a response.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  IlluminateValidationValidationException  $exception
     * @return IlluminateHttpResponse|IlluminateHttpJsonResponse|IlluminateHttpRedirectResponse
     */
    
protected function invalid($requestValidationException $exception)
    {
        return 
redirect($exception->redirectTo ?? url()->previous())
                    ->
withInput(Arr::except($request->input(), $this->dontFlash))
                    ->
withErrors($exception->errors(), $request->input('_error_bag'$exception->errorBag));
    }

    
/**
     * Convert a validation exception into a JSON response.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  IlluminateValidationValidationException  $exception
     * @return IlluminateHttpJsonResponse
     */
    
protected function invalidJson($requestValidationException $exception)
    {
        return 
response()->json([
            
'message' => $exception->getMessage(),
            
'errors' => $exception->errors(),
        ], 
$exception->status);
    }

    
/**
     * Determine if the exception handler response should be JSON.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  Throwable  $e
     * @return bool
     */
    
protected function shouldReturnJson($requestThrowable $e)
    {
        return 
$request->expectsJson();
    }

    
/**
     * Prepare a response for the given exception.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  Throwable  $e
     * @return IlluminateHttpResponse|IlluminateHttpJsonResponse|IlluminateHttpRedirectResponse
     */
    
protected function prepareResponse($requestThrowable $e)
    {
        if (! 
$this->isHttpException($e) && config('app.debug')) {
            return 
$this->toIlluminateResponse($this->convertExceptionToResponse($e), $e)->prepare($request);
        }

        if (! 
$this->isHttpException($e)) {
            
$e = new HttpException(500$e->getMessage(), $e);
        }

        return 
$this->toIlluminateResponse(
            
$this->renderHttpException($e), $e
        
)->prepare($request);
    }

    
/**
     * Create a Symfony response for the given exception.
     *
     * @param  Throwable  $e
     * @return SymfonyComponentHttpFoundationResponse
     */
    
protected function convertExceptionToResponse(Throwable $e)
    {
        return new 
SymfonyResponse(
            
$this->renderExceptionContent($e),
            
$this->isHttpException($e) ? $e->getStatusCode() : 500,
            
$this->isHttpException($e) ? $e->getHeaders() : []
        );
    }

    
/**
     * Get the response content for the given exception.
     *
     * @param  Throwable  $e
     * @return string
     */
    
protected function renderExceptionContent(Throwable $e)
    {
        try {
            return 
config('app.debug') && app()->has(ExceptionRenderer::class)
                        ? 
$this->renderExceptionWithCustomRenderer($e)
                        : 
$this->renderExceptionWithSymfony($econfig('app.debug'));
        } catch (
Throwable $e) {
            return 
$this->renderExceptionWithSymfony($econfig('app.debug'));
        }
    }

    
/**
     * Render an exception to a string using the registered `ExceptionRenderer`.
     *
     * @param  Throwable  $e
     * @return string
     */
    
protected function renderExceptionWithCustomRenderer(Throwable $e)
    {
        return 
app(ExceptionRenderer::class)->render($e);
    }

    
/**
     * Render an exception to a string using Symfony.
     *
     * @param  Throwable  $e
     * @param  bool  $debug
     * @return string
     */
    
protected function renderExceptionWithSymfony(Throwable $e$debug)
    {
        
$renderer = new HtmlErrorRenderer($debug);

        return 
$renderer->render($e)->getAsString();
    }

    
/**
     * Render the given HttpException.
     *
     * @param  SymfonyComponentHttpKernelExceptionHttpExceptionInterface  $e
     * @return SymfonyComponentHttpFoundationResponse
     */
    
protected function renderHttpException(HttpExceptionInterface $e)
    {
        
$this->registerErrorViewPaths();

        if (
$view $this->getHttpExceptionView($e)) {
            try {
                return 
response()->view($view, [
                    
'errors' => new ViewErrorBag,
                    
'exception' => $e,
                ], 
$e->getStatusCode(), $e->getHeaders());
            } catch (
Throwable $t) {
                
config('app.debug') && throw $t;

                
$this->report($t);
            }
        }

        return 
$this->convertExceptionToResponse($e);
    }

    
/**
     * Register the error template hint paths.
     *
     * @return void
     */
    
protected function registerErrorViewPaths()
    {
        (new 
RegisterErrorViewPaths)();
    }

    
/**
     * Get the view used to render HTTP exceptions.
     *
     * @param  SymfonyComponentHttpKernelExceptionHttpExceptionInterface  $e
     * @return string|null
     */
    
protected function getHttpExceptionView(HttpExceptionInterface $e)
    {
        
$view 'errors::'.$e->getStatusCode();

        if (
view()->exists($view)) {
            return 
$view;
        }

        
$view substr($view0, -2).'xx';

        if (
view()->exists($view)) {
            return 
$view;
        }

        return 
null;
    }

    
/**
     * Map the given exception into an Illuminate response.
     *
     * @param  SymfonyComponentHttpFoundationResponse  $response
     * @param  Throwable  $e
     * @return IlluminateHttpResponse|IlluminateHttpRedirectResponse
     */
    
protected function toIlluminateResponse($responseThrowable $e)
    {
        if (
$response instanceof SymfonyRedirectResponse) {
            
$response = new RedirectResponse(
                
$response->getTargetUrl(), $response->getStatusCode(), $response->headers->all()
            );
        } else {
            
$response = new Response(
                
$response->getContent(), $response->getStatusCode(), $response->headers->all()
            );
        }

        return 
$response->withException($e);
    }

    
/**
     * Prepare a JSON response for the given exception.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  Throwable  $e
     * @return IlluminateHttpJsonResponse
     */
    
protected function prepareJsonResponse($requestThrowable $e)
    {
        return new 
JsonResponse(
            
$this->convertExceptionToArray($e),
            
$this->isHttpException($e) ? $e->getStatusCode() : 500,
            
$this->isHttpException($e) ? $e->getHeaders() : [],
            
JSON_PRETTY_PRINT JSON_UNESCAPED_SLASHES
        
);
    }

    
/**
     * Convert the given exception to an array.
     *
     * @param  Throwable  $e
     * @return array
     */
    
protected function convertExceptionToArray(Throwable $e)
    {
        return 
config('app.debug') ? [
            
'message' => $e->getMessage(),
            
'exception' => get_class($e),
            
'file' => $e->getFile(),
            
'line' => $e->getLine(),
            
'trace' => collect($e->getTrace())->map(fn ($trace) => Arr::except($trace, ['args']))->all(),
        ] : [
            
'message' => $this->isHttpException($e) ? $e->getMessage() : 'Server Error',
        ];
    }

    
/**
     * Render an exception to the console.
     *
     * @param  SymfonyComponentConsoleOutputOutputInterface  $output
     * @param  Throwable  $e
     * @return void
     *
     * @internal This method is not meant to be used or overwritten outside the framework.
     */
    
public function renderForConsole($outputThrowable $e)
    {
        if (
$e instanceof CommandNotFoundException) {
            
$message str($e->getMessage())->explode('.')->first();

            if (! empty(
$alternatives $e->getAlternatives())) {
                
$message .= '. Did you mean one of these?';

                
with(new Error($output))->render($message);
                
with(new BulletList($output))->render($alternatives);

                
$output->writeln('');
            } else {
                
with(new Error($output))->render($message);
            }

            return;
        }

        (new 
ConsoleApplication)->renderThrowable($e$output);
    }

    
/**
     * Do not report duplicate exceptions.
     *
     * @return $this
     */
    
public function dontReportDuplicates()
    {
        
$this->withoutDuplicates true;

        return 
$this;
    }

    
/**
     * Determine if the given exception is an HTTP exception.
     *
     * @param  Throwable  $e
     * @return bool
     */
    
protected function isHttpException(Throwable $e)
    {
        return 
$e instanceof HttpExceptionInterface;
    }

    
/**
     * Create a new logger instance.
     *
     * @return PsrLogLoggerInterface
     */
    
protected function newLogger()
    {
        return 
$this->container->make(LoggerInterface::class);
    }
}
Онлайн: 0
Реклама