I've created a custom request in my project, but somehow it is not working. I'm facing two errors. I'm trying to show a message on view if validation fails through Ajax.
1) 422 Unprocessable Entity error
and
2) Undefined variable: teacherrequest
validation rules which i set in Request folder,
TeacherRequest.php:
public function rules()
{
return [
'Name' => 'required|regex:/^[\pL\s\-]+$/u',
'FName' => 'required|regex:/^[\pL\s\-]+$/u',
];
}
Controller:
public function update(TeacherRequest $request, $id)
{
if ($teacherrequest->fails()) {
return response()->json([
'msg' => 'Please Enter Correct Data',
]);
}
}
AJAX:
success: function (data) {
if(data.msg){
alert("please validate data");
}
}
Update:
if i remove if condition, i am getting 422 error, how to show that on view?
First, public function update(TeacherRequest $request) so in the function you need to use $request not $teacherrequest.
And second You need to have public function authorize() returning true.
You define TeacherRequest as $request TeacherRequest $request
but in next line use it as
if ($teacherrequest->fails()){ // this is wrong
correct one should be define like this
TeacherRequest $teacherrequest
or if you didn't change the dependency injection, just change the validator like this
if ($request->fails()){
summary: why error happened already specifically explained which is undefined teacherrequest variable, therefore 2 solutions above can solve it
If you type hint your request class as a parameter in your controller's action method (like you are doing in the example above) Laravel will automatically run your validation rules and return a 422 Unprocessable Entity if validation fails. You don't need to manually check if validation fails like you're doing above; in your controller's update method you can just implement the logic you want to run when validation passes.
Also on your front end you will need to use the error ajax callback to display the error message because a 422 status code is not considered successful.
See the documentation on creating form requests.
So, how are the validation rules evaluated? All you need to do is type-hint the request on your controller method. The incoming form request is validated before the controller method is called, meaning you do not need to clutter your controller with any validation logic.
Related
PUT|PATCH api/v1/tweets/{tweet}/comments/{comment} tweets.comments.update › Api\\V1\\TweetCommentController#update
i've this above route, and i'm trying to bind the relationship from controller
I'm passing formadata with key comment = This is a test comment
I tried this code below in TweetCommentController.
public function update(Tweet $tweet, TweetComment $comment, TweetCommentRequest $request)
but this is not working. it just redirect to the login page.
I also tried this below too
public function update(Tweet $tweet, TweetComment $comment)
This one seems working, atleast i'm able to log $tweet and $comment., but i cannot access the form data.
Please help.
My mistake,
I tried the api call from postman, and i used method PUT.
I changed the method to POST and add a new key _method = PUT in form data
so, my current (working) code looks like this
API call
POST {{base_url}}/tweets/1/comments/2
form-data :
comment = "edited test comment"
_method = PUT
Controller
public function update(TweetCommentRequest $request, Tweet $tweet, TweetComment $comment){
$comment->update($request->validated());
return $this->sendResponse([
'message' => __('Comment updated successfully.'),
'comment' => $comment
]);
}
I wonder if I should do form validation before retrieving input values or vice versa.
I usually do validation first as I see no benefit in trying to access input values that might not be valid. However, a coworker looked at my code recently and found it strange. Is there any correct order for these steps?
public function createGroups(Request $request)
{
$this->validate($request, [
'courses' => 'required_without:sections',
'sections' => 'required_without:courses',
'group_set_name' => 'required',
'group_number' => 'required|integer|min:1'
]);
$courses = $request->input('courses');
$sections = $request->input('sections');
$group_set_name = $request->input('group_set_name');
$group_number = $request->input('group_number');
Positioning the validation for your controller logic at the beginning of a method is probably the way to go here, as you have required parameters defined. If you receive data that does not fully satisfy the requirements, you produce a validation error back to the user. This follows the productive "Fail Fast" line of thinking: https://en.wikipedia.org/wiki/Fail-fast
It's also important that you're not using any data that hasn't passed your stringent requirements from validation. Data that fails validation should no longer be trusted. Unless there's some other reason you need to be, say, logging any incoming data from the frontend, the order here looks good to me.
I totally agree with #1000Nettles response, to elaborate a little bit more on his/her answer (who should be the accepted one): There isn't any need to continue with your business logic when the data doens't comply with your specifications. Let's say you expected a string of a N characters long, because you defined your database with that limitation (in order to optimize the db desing), will you try to persist it even when it'll throw an exception? Not really.
Besides, Laravel has a particular way to extract validation classes: Form Request. This are injected in controllers. When a call reach the controller it means that already passed the validation, if not, an 422error be returned.
Create a custom request and keep the mess out of your controller, it doesn't even hit your controller function if validation failed and can just grab the data in your controller if validation passed.
php artisan make:request GroupRequest
In app/Http/Requests/GroupRequest.php:
public function authorize()
{
// return true;
return request()->user()-isAdmin; // <-- example, but true if anyone can use this form
}
public function rules()
{
return [
'courses' => ['required_without:sections'],
'sections' => ['required_without:courses'],
'group_set_name' => ['required'],
'group_number' => ['required', 'integer', 'min:1'],
];
}
The best part is you can even manipulate the data in here (GroupRequest.php) after it has been validated:
public function validated()
{
$validated = $this->getValidatorInstance()->validate();
// EXAMPLE: hash password here then just use new hashed password in controller
$validated['password'] = Hash::make($validated['password']);
return $validated;
}
In your controller:
public function createUser(UserRequest $request) // <- in your case 'GroupRequest'
{
$validated = $request->validated(); // <-- already passed validation
$new_user = User::create($validated); // <-- password already hashed in $validated
return view('dashboard.users.show')->with(compact('user'));
}
In your case, if you use my GroupRequest block above, you can return to view in 1 line of code:
public function createGroups(GroupRequest $request)
{
return view('example.groups.show')->with($request->validated()); // <-- already an array
}
In you blade view file, you can then use your variables like {{ $group_set_name }} and {{ $group_number }}
I'm facing a little issue with Form Request Validation and how to handle it with one API route.
The resource that I need to create depends on an other resource.
(Here an EmailSettings belongs to a Tenant)
So the look of my route should be something like : /api/tenants/{id}/email_settings
And my request validation expects several fields including the tenantId :
public function rules() {
return [
'email' => 'bail|required|email|unique:email_settings,email',
'name' => 'bail|required',
'username' => 'bail|required',
'password' => 'bail|required'
'imapHost' => 'bail|required',
'imapPort' => 'bail|required',
'imapEncryption' => 'bail|required',
'imapValidateCert' => 'bail|required',
'smtpHost' => 'bail|required',
'smtpPort' => 'bail|required',
'smtpEncryption' => 'bail|required',
'tenantId' => 'bail|required',
];
}
And I send the request like this :
try {
const response = await this.tenantForm.post('/api/tenants')
let newTenant = helpers.getNewResourceFromResponseHeaderLocation(response)
let tenantId = parseInt(newTenant.id);
try {
await this.emailSettingsForm.post('/api/tenants/' + tenantId + '/email_settings')
this.requestAllTenants()
} catch ({response}) {
$('.second.modal').modal({blurring: true, closable: false}).modal('show');
}
} catch ({response}) {
$('.first.modal').modal({blurring: true}).modal('show');
}
So the tenantId is passed as a parameter and not in the request body to respect the REST convention.
But the problem is in my Controller, when I merge the data to create the resource, the validation has already took place only on body data before the merge.
public function store(EmailSettingValidation $request, $tenant_id) {
$emailSetting = $this->emailSettingService->create(
array_merge($request->all(), compact($tenant_id))
);
return $this->response->created($emailSetting);
}
So what is the best way to handle it properly ?
Pass the id in the body ? Seems messy
Use Validator to validate manually ? I would prefer to keep Form Validation
Remove the tenantId rule and check it manually ?
Any suggestions ?
Thank you
If you define your api route like this:
Roue::post('tenants/{tenant}/emails_settings', 'Controller#store');
and modify your controller method to type-hint the model with a variable name that matches your route definition:
public function store(EmailSettingValidation $request, Tenant $tenant) {}
then Laravel will automatically find the Tenant by ID and inject it into the controller, throwing a ModelNotFoundException (404) if it doesn't exist. That should take care of validating the id.
Authorization is another matter.
So the solution I found to trigger 404 is the following :
Remove the tenantId from EmailSettings Validation
Add a provider to add a custom error when the exception 'ModelNotFoundException' occurs like here
No query results for model in Laravel with Dingo - how to make a RESTful response on failure?
Try to throw this Exception with the findOrFail method if invalid ID :
public function store(EmailSettingValidation $request, $tenant_id) {
Tenant::findOrFail($tenant_id);
$emailSetting = $this->emailSettingService->create(
array_merge($request->all(), ['tenantId' => $tenant_id])
);
return $this->response->created($emailSetting);
}
Travis Britz and Guillaumehanotel each have half of your answer, but you're still missing a detail.
From Travis Britz- Yes, include the tenant_id on the URI so it gets injected into the controller.
From Guillaumehanotel- Also used the Eloquent findOrFail in that Id in your Controller (or whatever class the Controller is leveraging to do this, like a Repository or Service class).
The last piece you're missing though is handling the error. You can do this in the Controller if you like, but I generally like making it a rule for my entire system that the Illuminate\Database\Eloquent\ModelNotFoundException Exceptions that come out of findOrFail() should always result in a 404.
Go to app/Exceptions/Handler.php. I'm pretty sure Laravel auto-generates a meat and potatoes version of this file for you, but if you don't already have one, it should look something like this:
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
/**
* Class Handler
* #package App\Exceptions
*/
class Handler extends ExceptionHandler
{
/**
* Render an exception into an HTTP response.
*
* For our API, we need to override the call
* to the parent.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $e
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $error)
{
$exception = [
'title' => 'Internal Error',
'message' => $error->getMessage();
];
$statusCode = 500;
$headers = [
'Content-Type', 'application/json'
];
return response()->json($exception, $statusCode, $headers, JSON_PRETTY_PRINT);
}
}
Laravel basically has a system-wide try/catch that sends all errors through here first. That's how errors get rendered into something the browser can actually interpret when you're in debug-mode, rather than just kill the process outright. This also gives you the opportunity to apply a few special rules.
So all you need to do is tell Handler::render() to change the default error code that occurs when it sees the type of error that can only come from findOrFail(). (This kind of thing is why it's always good to make your own 'named exceptions', even if they do absolutely nothing except inherit the base \Exception class.)
Just add this just before render() returns anything:
if ($error instanceof Illuminate\Database\Eloquent\ModelNotFoundException) {
$statusCode = 404;
}
In a controller I am using two validation like this:
public function update(Request $request){
if( $request->hasFile('img1') ){
$request->validate(
[
'img1'=>'image'
]
);
}
if( $request->hasFile('img2') ){
$request->validate(
[
'img2'=>'image'
]
);
}
}
Now if I upload incorrect file types for both img1 & img2 Only the first validation is checked and laravel redirects user to the original form page. This way message for only first validation is displayed. Even though second file type was also incorrect. I want to make sure all validate methods are checked are executed before I get redirected to the page I came from i.e., the page containing form.
Also I can't put validate method for file in one if statement as img1 and img2 might not be present at the same time. Because user might just want to upload one file.
You need not invoke validate method more than once for validating a single request. In the validate method you can validate all the inputs from the request, and if the validation fails, then it will automatically return the page which the request came from. And also you need not check whether the request has the input or not, before the validation. It will automatically be managed by laravel.
In the following validation required is used to specify the field is mandatory, and it will only accept file types specified in mimes, and max is used to specify the maximum file size in kilobytes.
public function update(Request $request){
$request->validate([
'img1' => 'required | mimes:jpeg,jpg,png | max:1000',
'img2' => 'required | mimes:jpeg,jpg,png | max:1000',
]);
}
you can try
public function update(Request $request){
$request->validate([
'img1' => 'required_without:img2',
'img2' => 'required_without:img1'
]);
}
If have more than two fields and only one is required, use required_without_all:foo,bar,...
Is there a way to inject an error message so we can show it on a view with $errors instance, on a redirection?
$errors->all(); // e.g. to have it here
Which I tried:
return Redirect::to('dashboard')
->with('errors', 'Custom error');
But actually will throw an error:
Call to a member function all() on a non-object
Your example doesn't work since you're passing just a variable instead of an object.
If you want to add your own custom error message to other validation errors, you can use the add() method of Illuminate\Support\MessageBag class (since validation errors are returned as an instance of this class):
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
$errors = $validator->messages();
$errors->add('Error', 'Custom Error');
return Redirect::to('dashboard')->withErrors($errors);
}
Now your custom message should display alongside other validation errors.
Simple, one line solution
While what kajetrons said is completely true, it is unnecessary to create a validator if you just want to return an error message through the $errors object. This can be written more simply in one line:
return Redirect::to('dashboard')->withErrors(new \Illuminate\Support\MessageBag(['Custom error']));