Laravel cast Request to different Request - validation

I have a three different forms that submit to the same controller in Laravel. Each form has its own validation rules stored in a Request. Here's an example of my code:
public function store($id, $type, Request $request)
{
switch ($type) {
case 'daily':
$this->monthly($id, $type, $request);
break;
case 'monthly':
$this->monthly($id, $type, $request);
break;
case 'yearly':
$this->yearly($id, $type, $request);
}
return redirect(route('x.show', $id));
}
private function monthly($id, $type, MonthlyFormRequest $request)
{
//store form
}
However, this doesn't work and throwns an instance error since Request isn't the same type as MonthlyFormRequest in the monthly method. Is there a way to cast the Request to MonthlyFormRequest or is there some other way to do it? I prefer to avoid declaring validation rules in the controller itself. What would be the best way to get a uniform Request type request in the store method and then use MonthlyFormRequest?

You could pass the type trough a request parameter and move the switch case to your request and preform the check there as such:
In your request:
public function rules()
{
switch($this->type){
case 'dailty':
return [
'field': 'required'
];
break;
case 'monthly':
return [
'field': 'required'
];
break;
case 'yearly':
return [
'field': 'required'
];
break;
}
}
In your Controller:
public function store($id, YourCustomRequest $request)
{
return redirect(route('x.show', $id));
}

Related

Laravel how to pass request from controller to resource collection?

I will pass a parameter in the request. The query won't change. How can I pass the request to SurveyResouce
public function getAllSurveys(\Illuminate\Http\Request $request) {
$surveys = DB::table('surveys')
->select('id', 'survey_name')
->get();
return response()->json([
'error' => false,
'data' => SurveyResource::collection($surveys)
],
}
I want get the request parameters in the resource
public function toArray($request) {
$controller = new SurveyController(new SurveyRepository());
return [
'question' => $request->has('ques') ? $request->input('ques'):'',
];
}
You can try by directly using request() helper function. Like request('parameter');

Put other function on FormRequest in Laravel

I'm building a Laravel 6 application, and I am concerned about "best practices." I have one controller named CustomerController. In my controller, I want to update the Customer model, so I will have a function like the following.
public function update(UpdateCustomer $request, Customer $customer){
//
}
UpdateCustomer is my form request and where I will do the validation. In my update() method, I have classic validation.
public function rules()
{
$validationArray = [];
$validationArray['customer.name'] = 'string|required';
$validationArray['customer.vat'] = 'string|required';
$validationArray['customer.email'] = 'email|required';
return $validationArray;
}
Now I have to do some particular validation other the classic.
Let's assume that I have more data in my model, and I don't want these values to be changed.
For example, I have the following: address, cap, locality. I have a second method on the UpdateCustomer request that I can validate.
public function validateForDataCantChange()
{
$data = $this->input("customer");
$customer = $this->route("customerID");
$validator = Validator::make([], []); // Empty data and rules fields
$arrayDataThatCantChange = [
'address' => $data['address'] ?? NULL,
'cap' => $data['cap'] ?? NULL,
'locality' => $data['locality'] ?? NULL
];
foreach ($arrayDataThatCantChange as $key => $v) {
if ($customer->{$key} !== $v) {
$validator->errors()->add($key, __("messages.the field :field can't be changed", ['field' => $key]));
}
}
if ($validator->errors()->any()) {
throw new ValidationException($validator);
}
}
And then in my controller, I've added the following.
public function update(UpdateCustomer $request, Customer $customer){
$request->validateForDataCantChange();
}
Is this a bad practice? Should I create a new FormRequest? How, in this case (two form requests), can I use two different requests for a single controller?
For the little effort required, I'd personally create a new form request.
If you wish to use the same form request you can do the following:
public function rules()
{
$rules = [
'title' => 'required:unique:posts'
];
// when editing i.e. /posts/2/edit
if ($id = $this->segment(2)) {
$rules['title'] .= ",$id";
}
return $rules;
}
However, I always use a separate class for each action.

How to produce API error responses in Laravel 5.4?

Whenever I make a call to /api/v1/posts/1, the call is forwarded to the show method
public function show(Post $post) {
return $post;
}
in PostController.php resourceful controller. If the post does exist, the server returns a JSON response. However, if the post does not exist, the server returns plain HTML, despite the request clearly expecting JSON in return. Here's a demonstration with Postman.
The problem is that an API is supposed to return application/json, not text/html. So, here are my questions:
1. Does Laravel have built-in support for automatically returning JSON if exceptions occur when we use implicit route model binding (like in show method above, when we have 404)?
2. If it does, how do I enable it? (by default, I get plain HTML, not JSON)
If it doesn't what's the alternative to replicating the following across every single API controller
public function show($id) {
$post = Post::find($id); // findOrFail() won't return JSON, only plain HTML
if (!$post)
return response()->json([ ... ], 404);
return $post;
}
3. Is there a generic approach to use in app\Exceptions\Handler?
4. What does a standard error/exception response contain? I googled this but found many custom variations.
5. And why isn't JSON response still built into implicit route model binding? Why not simplify devs life and handle this lower-level fuss automatically?
EDIT
I am left with a conundrum after the folks at Laravel IRC advised me to leave the error responses alone, arguing that standard HTTP exceptions are rendered as HTML by default, and the system that consumes the API should handle 404s without looking at the body. I hope more people will join the discussion, and I wonder how you guys will respond.
I use this code in app/Exceptions/Handler.php, probably you will need making some changes
public function render($request, Exception $exception)
{
$exception = $this->prepareException($exception);
if ($exception instanceof \Illuminate\Http\Exception\HttpResponseException) {
return $exception->getResponse();
}
if ($exception instanceof \Illuminate\Auth\AuthenticationException) {
return $this->unauthenticated($request, $exception);
}
if ($exception instanceof \Illuminate\Validation\ValidationException) {
return $this->convertValidationExceptionToResponse($exception, $request);
}
$response = [];
$statusCode = 500;
if (method_exists($exception, 'getStatusCode')) {
$statusCode = $exception->getStatusCode();
}
switch ($statusCode) {
case 404:
$response['error'] = 'Not Found';
break;
case 403:
$response['error'] = 'Forbidden';
break;
default:
$response['error'] = $exception->getMessage();
break;
}
if (config('app.debug')) {
$response['trace'] = $exception->getTrace();
$response['code'] = $exception->getCode();
}
return response()->json($response, $statusCode);
}
Additionally, if you will use formRequest validations, you need override the method response, or you will be redirected and it may cause some errors.
use Illuminate\Http\JsonResponse;
...
public function response(array $errors)
{
// This will always return JSON object error messages
return new JsonResponse($errors, 422);
}
Is there a generic approach to use in app\Exceptions\Handler?
You can check if json is expected in the generic exception handler.
// app/Exceptions/Handler.php
public function render($request, Exception $exception) {
if ($request->expectsJson()) {
return response()->json(["message" => $exception->getMessage()]);
}
return parent::render($request, $exception);
}
The way we have handled it by creating a base controller which takes care of the returning response part. Looks something like this,
class BaseApiController extends Controller
{
private $responseStatus = [
'status' => [
'isSuccess' => true,
'statusCode' => 200,
'message' => '',
]
];
// Setter method for the response status
public function setResponseStatus(bool $isSuccess = true, int $statusCode = 200, string $message = '')
{
$this->responseStatus['status']['isSuccess'] = $isSuccess;
$this->responseStatus['status']['statusCode'] = $statusCode;
$this->responseStatus['status']['message'] = $message;
}
// Returns the response with only status key
public function sendResponseStatus($isSuccess = true, $statusCode = 200, $message = '')
{
$this->responseStatus['status']['isSuccess'] = $isSuccess;
$this->responseStatus['status']['statusCode'] = $statusCode;
$this->responseStatus['status']['message'] = $message;
$json = $this->responseStatus;
return response()->json($json, $this->responseStatus['status']['statusCode']);
}
// If you have additional data to send in the response
public function sendResponseData($data)
{
$tdata = $this->dataTransformer($data);
if(!empty($this->meta)) $tdata['meta'] = $this->meta;
$json = [
'status' => $this->responseStatus['status'],
'data' => $tdata,
];
return response()->json($json, $this->responseStatus['status']['statusCode']);
}
}
Now you need to extend this in your controller
class PostController extends BaseApiController {
public function show($id) {
$post = \App\Post::find($id);
if(!$post) {
return $this->sendResponseStatus(false, 404, 'Post not found');
}
$this->setResponseStatus(true, 200, 'Your post');
return $this->sendResponseData(['post' => $post]);
}
}
You would get response like this
{
"status": {
"isSuccess": false,
"statusCode": 404,
"message": "Post not found"
}
}
{
"status": {
"isSuccess": true,
"statusCode": 200,
"message": "Your post"
},
"data": {
"post": {
//Your post data
}
}
}
You just use use Illuminate\Support\Facades\Response;.
then, make the return as am:
public function index(){
$analysis = Analysis::all();
if(empty($analysis)) return Response::json(['error'=>'Empty data'], 200);
return Response::json($analysis, 200, [], JSON_NUMERIC_CHECK);
}
And now you will have a JSON return....

How can I use Laravel Validation in the API, it's returning my view now?

I have this route in route/api.php:
Route::post('/register', 'LoginController#register');
My LoginController
class LoginController extends Controller {
public function register(Request $request) {
$this->validate($request, // <-- using this will return the view
// from **web.php** instead of the expected json response.
[
'email' => 'required|email',
'firstName' => 'required|alpha_dash',
'lastName' => 'required',
'password' => 'required|confirmed',
]);
$input = $request->all();
//$plain_password = $input['password'];
$input['uuid'] = uuid();
$input['password'] = Hash::make($input['password']);
$user = User::create($input);
dd($errors);
$response['succes'] = true;
$response['user'] = $user;
return response($response);
}
}
Why does adding a validation call change the behaviour to returning my view / the wrong route. I want the api to validate my request too, not just my "frontend".
When you use Laravel's validate method from controller it automatically handles/takes the step if the validation fails. So, depending on the required content type/request type, it determines whether to redirect back or to a given url or sending a json response. Ultimately, something like thos following happens when your validation fails:
protected function buildFailedValidationResponse(Request $request, array $errors)
{
if ($request->expectsJson()) {
return new JsonResponse($errors, 422);
}
return redirect()->to($this->getRedirectUrl())
->withInput($request->input())
->withErrors($errors, $this->errorBag());
}
So, if the first if statement is true then you'll get a json response and it'll be true if you either send an ajax request or if you attach a accept header with your request to accept json response (When requesting from a remote server). So, make sure your request fulfills the requirements.
Alternatively, you can manually validate the request using the Validator component and return a json response explicitly if it fails.

Lumen can't set custom validation rules

I try this code to set custom validation messages, but with no effect -
class TestController extends Controller
{
public function submit(Request $request)
{
$this->validate($request,
[
'items' => 'required'
],
[
'items.required' => 'test test'
]
);
}
}
But on error I got this response -
{
"error": "The given data failed to pass validation."
}
What wrong with this code?
UPD:
Earlier I edit App\Exceptions\Handler to put errors(in API response) in specific format -
{
"error": "123"
}
This code is reason that validation errors not shown -
public function render($request, Exception $e)
{
return response([
'error' => $e->getMessage()
], 500);
}
I update Handler::render method regarding to this purpose
public function render($request, Exception $e) {
$response = parent::render($request, $e);
if (isset($response->exception) and !empty($response->exception)) {
return response(['error' => $response->exception->getMessage()], 500);
} else {
return parent::render($request, $e);
}
}
But I think I need to improve this code.
It seems you mixed validation rules and messages.
The validate method takes 3 parameter: request, rules, messages.
Please try this:
public function submit(Request $request)
{
$rules = [
'items' => 'required',
'otheritems' => 'required',
];
$messages = [
'items.required' => 'Error: Please enter something.',
'otheritems.required' => 'Otheritems are also required',
];
$this->validate($request, $rules, $messages);
}
The latest Lumen version always gives back JSON, see documentation:
The $this->validate helper will always return JSON responses with the relevant error messages when validation fails. If you are not building a stateless API that solely sends JSON responses, you should use the full Laravel framework.
Update regarding error:
The given result by Lumen looks like that.
{"items":["Items are required"],"otheritems":["Otheritems are also required"]}
Each item that failed the validation gets an entry in your response. So your error bag, need to be a JSON array.
Custom exception render method:
public function render($request, Exception $e)
{
$response = parent::render($request, $e);
if ($response->getStatusCode() == 422) {
$renderResult = parent::render($request, $e);
$returnResult['error'] = json_decode($renderResult->content(), true);
$returnResult = json_encode($returnResult);
return new Response($returnResult, $response->getStatusCode());
} else {
return parent::render($request, $e);
}
}

Resources