web-dev-qa-db-fra.com

Laravel 5: Gérer les exceptions lorsque la demande souhaite JSON

Je fais des envois de fichiers via AJAX le Laravel 5.). J'ai pratiquement tout ce qui fonctionne sauf une chose.

Lorsque j'essaie de télécharger un fichier trop volumineux (plus gros que upload_max_filesize et post_max_size Je reçois une exception TokenMismatchException.

Ceci est toutefois à prévoir, car je sais que mon entrée sera vide si ces limites sont dépassées. Entrée vide, signifie non _token est reçu, ce qui explique pourquoi le middleware responsable de la vérification des jetons CSRF fait des bêtises.

Mon problème, toutefois, n’est pas que cette exception soit levée, mais bien comment elle est rendue. Lorsque cette exception est interceptée par Laravel, il crache le code HTML de la page générique Whoops (avec une charge de traçage de pile depuis que je suis en mode débogage).

Quelle est la meilleure façon de gérer cette exception afin que JSON soit renvoyé sur AJAX (ou lorsque JSON est demandé) tout en conservant le comportement par défaut?


Edit: Cela semble se produire quelle que soit l'exception levée. Je viens d'essayer de faire une demande via AJAX (type de données: JSON)) vers une "page" qui n'existe pas pour tenter d'obtenir un 404 et que la même chose se produise - le code HTML est renvoyé , rien de JSON amical.

35
Jonathon

Je vais essayer moi-même cette question en tenant compte de la réponse donnée par @Wader et des commentaires de @Tyler Crompton:

app/Exceptions/Handler.php

/**
 * Render an exception into an HTTP response.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Exception $e
 * @return \Illuminate\Http\Response
 */
public function render($request, Exception $e)
{
    // If the request wants JSON (AJAX doesn't always want JSON)
    if ($request->wantsJson()) {
        // Define the response
        $response = [
            'errors' => 'Sorry, something went wrong.'
        ];

        // If the app is in debug mode
        if (config('app.debug')) {
            // Add the exception class name, message and stack trace to response
            $response['exception'] = get_class($e); // Reflection might be better here
            $response['message'] = $e->getMessage();
            $response['trace'] = $e->getTrace();
        }

        // Default response of 400
        $status = 400;

        // If this exception is an instance of HttpException
        if ($this->isHttpException($e)) {
            // Grab the HTTP status code from the Exception
            $status = $e->getStatusCode();
        }

        // Return a JSON response with the response array and status code
        return response()->json($response, $status);
    }

    // Default to the parent class' implementation of handler
    return parent::render($request, $e);
}
84
Jonathon

Dans votre application, vous devriez avoir app/Http/Middleware/VerifyCsrfToken.php. Dans ce fichier, vous pouvez gérer le fonctionnement du middleware. Ainsi, vous pouvez vérifier si la demande est ajax et gérer cela comme vous le souhaitez.

Alternativement, et probablement une meilleure solution, éditer le gestionnaire d'exceptions pour renvoyer json. Voir app/exceptions/Handler.php, quelque chose comme ci-dessous serait un point de départ

public function render($request, Exception $e)
{
    if ($request->ajax() || $request->wantsJson())
    {
        $json = [
            'success' => false,
            'error' => [
                'code' => $e->getCode(),
                'message' => $e->getMessage(),
            ],
        ];

        return response()->json($json, 400);
    }

    return parent::render($request, $e);
}
11
Wader

En me basant sur la fonction de rendu de @ Jonathon, je modifierais simplement les conditions pour exclure les instances ValidationException.

// If the request wants JSON + exception is not ValidationException
if ($request->wantsJson() && ( ! $exception instanceof ValidationException))

Laravel 5 renvoie déjà des erreurs de validation dans JSON, le cas échéant.

La méthode complète dans App/Exceptions/Handler.php:

/**
 * Render an exception into an HTTP response.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Exception  $exception
 * @return \Illuminate\Http\Response
 */
public function render($request, Exception $exception)
{
    // If the request wants JSON + exception is not ValidationException
    if ($request->wantsJson() && ( ! $exception instanceof ValidationException))
    {
        // Define the response
        $response = [
            'errors' => 'Sorry, something went wrong.'
        ];

        // If the app is in debug mode
        if (config('app.debug'))
        {
            // Add the exception class name, message and stack trace to response
            $response['exception'] = get_class($exception); // Reflection might be better here
            $response['message'] = $exception->getMessage();
            $response['trace'] = $exception->getTrace();
        }

        // Default response of 400
        $status = 400;

        // If this exception is an instance of HttpException
        if ($this->isHttpException($exception))
        {
            // Grab the HTTP status code from the Exception
            $status = $exception->getCode();
        }

        // Return a JSON response with the response array and status code
        return response()->json($response, $status);
    }
    return parent::render($request, $exception);
}
8
Liore Shai

J'ai modifié plusieurs implémentations trouvées ici pour travailler sur Laravel 5.3. La principale différence est que la mienne renverra les textes d'état HTTP corrects

Dans votre fonction render () de app\Exceptions\Handler.php, ajoutez ce fragment de code en haut:

    if ($request->wantsJson()) {
        return $this->renderExceptionAsJson($request, $exception);
    }

Contenu de renderExceptionAsJson:

/**
 * Render an exception into a JSON response
 *
 * @param $request
 * @param Exception $exception
 * @return SymfonyResponse
 */
protected function renderExceptionAsJson($request, Exception $exception)
{
    // Currently converts AuthorizationException to 403 HttpException
    // and ModelNotFoundException to 404 NotFoundHttpException
    $exception = $this->prepareException($exception);
    // Default response
    $response = [
        'error' => 'Sorry, something went wrong.'
    ];

    // Add debug info if app is in debug mode
    if (config('app.debug')) {
        // Add the exception class name, message and stack trace to response
        $response['exception'] = get_class($exception); // Reflection might be better here
        $response['message'] = $exception->getMessage();
        $response['trace'] = $exception->getTrace();
    }

    $status = 400;
    // Build correct status codes and status texts
    switch ($exception) {
        case $exception instanceof ValidationException:
            return $this->convertValidationExceptionToResponse($exception, $request);
        case $exception instanceof AuthenticationException:
            $status = 401;
            $response['error'] = Response::$statusTexts[$status];
            break;
        case $this->isHttpException($exception):
            $status = $exception->getStatusCode();
            $response['error'] = Response::$statusTexts[$status];
            break;
        default:
            break;
    }

    return response()->json($response, $status);
}
6
Joe Alamo

En utilisant le code de @ Jonathon, voici une solution rapide pour Laravel/Lumen 5.3 :)

/**
 * Render an exception into an HTTP response.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Exception $e
 * @return \Illuminate\Http\Response
 */
public function render($request, Exception $e)
{
    // If the request wants JSON (AJAX doesn't always want JSON)
    if ($request->wantsJson())
    {
        // Define the response
        $response = [
            'errors' => 'Sorry, something went wrong.'
        ];

        // If the app is in debug mode
        if (config('app.debug'))
        {
            // Add the exception class name, message and stack trace to response
            $response['exception'] = get_class($e); // Reflection might be better here
            $response['message'] = $e->getMessage();
            $response['trace'] = $e->getTrace();
        }

        // Default response of 400
        $status = 400;

        // If this exception is an instance of HttpException
        if ($e instanceof HttpException)
        {
            // Grab the HTTP status code from the Exception
            $status = $e->getStatusCode();
        }

        // Return a JSON response with the response array and status code
        return response()->json($response, $status);
    }

    // Default to the parent class' implementation of handler
    return parent::render($request, $e);
}
0
juliochina