web-dev-qa-db-fra.com

Comment forcer FormRequest return json in Laravel 5.1?

J'utilise FormRequest pour valider à partir duquel est envoyé un appel API depuis mon application smartphone. Donc, je veux que FormRequest retourne toujours json lorsque la validation échoue.

J'ai vu le code source suivant de Laravel framework, le comportement par défaut de FormRequest est return json si reqeust est Ajax ou wantJson.

//Illuminate\Foundation\Http\FormRequest class
/**
 * Get the proper failed validation response for the request.
 *
 * @param  array  $errors
 * @return \Symfony\Component\HttpFoundation\Response
 */
public function response(array $errors)
{
    if ($this->ajax() || $this->wantsJson()) {
        return new JsonResponse($errors, 422);
    }

    return $this->redirector->to($this->getRedirectUrl())
                                    ->withInput($this->except($this->dontFlash))
                                    ->withErrors($errors, $this->errorBag);
}

Je savais que je pouvais ajouter Accept= application/json dans l'en-tête de la demande. FormRequest renverra json. Mais je veux fournir un moyen plus simple de demander mon API en prenant en charge json par défaut sans définir d'en-tête. J'ai donc essayé de trouver des options pour forcer la réponse json de FormRequest dans Illuminate\Foundation\Http\FormRequest classe. Mais je n'ai trouvé aucune option prise en charge par défaut.

Solution 1: remplacer la classe abstraite de demande

J'ai essayé d'écraser ma classe abstraite de demande d'application comme suit:

<?php

namespace Laravel5Cg\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\JsonResponse;

abstract class Request extends FormRequest
{
    /**
     * Force response json type when validation fails
     * @var bool
     */
    protected $forceJsonResponse = false;

    /**
     * Get the proper failed validation response for the request.
     *
     * @param  array  $errors
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function response(array $errors)
    {
        if ($this->forceJsonResponse || $this->ajax() || $this->wantsJson()) {
            return new JsonResponse($errors, 422);
        }

        return $this->redirector->to($this->getRedirectUrl())
            ->withInput($this->except($this->dontFlash))
            ->withErrors($errors, $this->errorBag);
    }
}

J'ai ajouté protected $forceJsonResponse = false; pour définir si nous devons forcer la réponse json ou non. Et, dans chaque FormRequest qui s'étend de la classe abstraite Request. J'ai défini cette option.

Par exemple: j'ai créé un StoreBlogPostRequest et défini $forceJsoResponse=true pour cette FormRequest et en faire une réponse json.

<?php

namespace Laravel5Cg\Http\Requests;

use Laravel5Cg\Http\Requests\Request;

class StoreBlogPostRequest extends Request
{

    /**
     * Force response json type when validation fails
     * @var bool
     */

     protected $forceJsonResponse = true;
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title' => 'required|unique:posts|max:255',
            'body' => 'required',
        ];
    }
}

Solution 2: ajouter un en-tête de demande de changement de middleware et forcer

Je construis un middleware comme les suivants:

namespace Laravel5Cg\Http\Middleware;

use Closure;
use Symfony\Component\HttpFoundation\HeaderBag;

class AddJsonAcceptHeader
{
    /**
     * Add Json HTTP_ACCEPT header for an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $request->server->set('HTTP_ACCEPT', 'application/json');
        $request->headers = new HeaderBag($request->server->getHeaders());
        return $next($request);
    }
}

C'est du travail. Mais je me demande si ces solutions sont bonnes? Et y a-t-il un Laravel moyen de m'aider dans cette situation?

25
Chung

Cela m'embarrasse de savoir pourquoi c'est si difficile à faire à Laravel. En fin de compte, sur la base de votre idée de remplacer la classe Request, j'ai trouvé cela.

app/Http/Requests/ApiRequest.php

<?php

namespace App\Http\Requests;


class ApiRequest extends Request
{
    public function wantsJson()
    {
        return true;
    }
}

Ensuite, dans chaque contrôleur, passez simplement \App\Http\Requests\ApiRequest

public function index(ApiRequest $request)

32
Bouke Versteegh

Je sais que ce message est un peu ancien, mais je viens de créer un middleware qui remplace l'en-tête "Accept" de la demande par "application/json". Cela fait que la fonction wantsJson() retourne true lorsqu'elle est utilisée. (Cela a été testé en Laravel 5.2 mais je pense que cela fonctionne de la même façon en 5.1)

Voici comment vous implémentez cela:

  1. Créez le fichier app/Http/Middleware/Jsonify.php

    namespace App\Http\Middleware;
    
    use Closure;
    
    class Jsonify
    {
    
        /**
         * Change the Request headers to accept "application/json" first
         * in order to make the wantsJson() function return true
         *
         * @param  \Illuminate\Http\Request  $request
         * @param  \Closure  $next
         * 
         * @return mixed
         */
        public function handle($request, Closure $next)
        {
            $request->headers->set('Accept', 'application/json');
    
            return $next($request);
        }
    }
    
  2. Ajoutez le middleware à votre $routeMiddleware tableau de votre app/Http/Kernel.php fichier

    protected $routeMiddleware = [
        'auth'       => \App\Http\Middleware\Authenticate::class,
        'guest'      => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'jsonify'    => \App\Http\Middleware\Jsonify::class
    ];
    
  3. Enfin, utilisez-le dans votre routes.php comme pour tout middleware. Dans mon cas, cela ressemble à ceci:

    Route::group(['prefix' => 'api/v1', 'middleware' => ['jsonify']], function() {
        // Routes
    });
    
30
SimonDepelchin

si votre demande a soit X-Request-With: XMLHttpRequest en-tête ou accepter le type de contenu comme application/json FormRequest retournera automatiquement une réponse json contenant les erreurs avec un état de 422.

enter image description here

2
Afraz Ahmad

Sur la base de réponse de ZeroOne , si vous utilisez validation de demande de formulaire , vous pouvez remplacer la méthode failedValidation pour toujours renvoyer json en cas d'échec de la validation.

La bonne chose à propos de cette solution, c'est que vous ne remplacez pas toutes les réponses pour renvoyer json, mais seulement les échecs de validation. Donc, pour toutes les autres exceptions Php, vous verrez toujours la page d'erreur conviviale Laravel.

namespace App\Http\Requests;

use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
use Symfony\Component\HttpFoundation\Response;

class InventoryRequest extends FormRequest
{
    protected function failedValidation(Validator $validator)
    {
        throw new HttpResponseException(response($validator->errors(), Response::HTTP_UNPROCESSABLE_ENTITY));
    }
1
T30

je remplace simplement la fonction failedValidation

protected function failedValidation(Validator $validator)
{
        if ($this->wantsJson()) {
            // flatten all the message
            $collection  = collect($validator->errors())->flatten()->values()->all();
            throw new HttpResponseException(Response::error('Validation Error', $collection));
        }

        parent::failedValidation($validator);
}

Échantillon de sortie:

{
    "error": true,
    "message": "Validation Error",
    "reference": [
        "The device id field is required.",
        "The os version field is required.",
        "The apps version field is required."
    ],
}
0
ZeroOne