why?
validate request and raise exception on undefined-parameters
Hi folks,
i am trying to solve a problem but i am missing an idea how to do it elegantly. I would like to trigger an exception / 404 http error if a parameter was passed that was not defined in the validator. i don't want to duplicate the code for every controller, so i moved the code to a parent controller, but i have no access to the specific request validator there, so i have to call the parent controller in each method in the specific controller. is there anything more elegant, perhaps a filter or middleware solution? i already tried to check in middleware but i can't access to specific request with validator, or i dont found how.
here is my TestController:
<?php
namespace App\Http\Controllers\API;
use App\Http\Requests\TestGetRequest;
use App\Http\Controllers\Controller;
use App\Exceptions\API\ParameterNotFoundException;
use App\Models\API\Test;
class TestController extends Controller {
/**
*
* Display a listing of the resource.
*
* @param TestGetRequest $request
* @return \Illuminate\Http\Response
*/
public function index(TestGetRequest $request) {
// this block is ugly because we need them in every method of every controller
try {
$this->prepareInput($request);
} catch (ParameterNotFoundException $ex) {
return $this->notFoundResponse($ex->getMessage());
} catch (\Exception $ex) {
return $this->notValidParameterResponse('UNDEFINED: ' . $ex->getMessage());
}
$queryBuilder = Test::clone();
// check filter "active"
if (true === isset($this->params['is_active'])) {
$active = (bool) $this->params['is_active'];
$queryBuilder->where('is_active', $active);
}
// check filter "name"
if (true === isset($this->params['name'])) {
$name = $this->params['name'];
$queryBuilder->where('name', 'LIKE', '%' . $name . '%');
}
$output = new TestResourceCollection($queryBuilder->paginate());
return $this->successResponse($output);
}
and my parent Controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Exceptions\API\ParameterNotFoundException;
use App\Models\API\Test;
class Controller extends BaseController {
use AuthorizesRequests,
DispatchesJobs,
ValidatesRequests;
protected $page = 1;
protected $params = [];
/**
*
* @param FormRequest $request
* @return bool
* @throws ParameterNotFoundException
*/
protected function prepareInput($request): bool {
$all = $request->all();
$this->params = $request->validated();
$diff = array_diff($all, $this->params);
if (count($diff) > 0) {
throw new ParameterNotFoundException('Parameter: \'' . key($diff) . '\' not found');
}
// set pagination parameter default for all controllers
if (isset($this->params['page'])) {
$this->page = (int) $this->params['page'];
}
return true;
}
/**
*
* @param array $data
* @return JsonResponse
*/
protected function successResponse($data, $header = []) { //: JsonResponse
return self::createResponse(200, $data, $header);
}
/**
*
* @param array $data
* @return JsonResponse
*/
protected function notFoundResponse($data): JsonResponse {
return self::errorResponse(404, $data);
}
/**
*
* @param array $data
* @return JsonResponse
*/
protected function notValidParameterResponse($data): JsonResponse {
return self::errorResponse(422, $data);
}
/**
*
* @param array $data
* @return JsonResponse
*/
protected function errorResponse($code, $data): JsonResponse {
return self::createResponse($code, ['error' => $data]);
}
/**
*
* @param int $code
* @param array $data
* @return JsonResponse
*/
protected static function createResponse($code, $input, $headers = []) {//: JsonResponse
$output = null;
if ($input instanceof JsonResource) {
$output = $input;
} else {
$output = response()->json($input, $code)->withHeaders($headers);
}
return $output;
}
is there a way to not use this method ($this->prepareInput($request);) in every controller method?
i am happy for any idea or suggestions for improvement!
thanks!
thanks for the tip, i have solved my problem! for this I have created a parent Request-class with my required validation check of undefined parameters in ->withValidator(). and in ->failedValidation(Validator $validator) i throw an exception when the validation fails. now i have a central place for validation of undefined parameters, and i dont need to re-check it in every method of new controller. just get request-parameters from specific request-class (extened from my parent class) if my parent class don't raise exception.
here is my solution:
<?php
namespace App\Http\Requests;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Foundation\Http\FormRequest;
abstract class GetRequest extends FormRequest {
/**
* Configure the validator instance.
*
* @param \Illuminate\Validation\Validator $validator
* @return void
*/
public function withValidator($validator) {
$validator->after(function ($validator) {
$diff_keys = array_diff(array_keys($this->input()), array_keys($this->rules()));
if (count($diff_keys) > 0) {
$values = array_map(function ($value) {
return "Parameter: '$value' is not defined!";
}, $diff_keys);
$messages = array_combine($diff_keys, [$values]);
$validator->errors()->merge($messages);
}
});
}
/**
* Handle a failed validation attempt.
*
* source: https://stackoverflow.com/questions/46350307/disable-request-validation-redirect-in-laravel-5-4
*
* @param \Illuminate\Contracts\Validation\Validator $validator
* @return void
*
* @throws \Illuminate\Validation\ValidationException
*/
protected function failedValidation(Validator $validator) {
throw new HttpResponseException(response()->json([
'error' => $validator->errors()->first()], 422));
}
}
Please or to participate in this conversation.