I had previously asked a question here in regards for setting up in-place editing with Laravel 5 and AJAX. I hadn't updated it because I had managed offline to figure out what was wrong with it.
While the table is capable of editing user rows in-place, I'm now trying to add validation on top of it, intending on utilizing Laravel's built-in validator. However, for some reason it doesn't seem to be working. When I try to pass in the failed validator back through the JSON, it spits out every possible error I'm checking for. It's as if the validator is treating every input as empty, which doesn't make sense, as the rest of the function is clearly taking in the inputs as intended.
The code snippets in my previous question are still mostly relevant, but there have been updates to HomeController.php, as can be seen below:
public function updateTable(Users $users){
$user = request()->input('user');
$first_name = request()->input('first_name');
$last_name = request()->input('last_name');
$validator = Validator::make(request()->all(), [
'firstName' => 'required|alpha',
'lastName' => 'required|alpha'
], [
'firstName.required' => 'You need to give a first name!',
'firstName.alpha' => 'A first name can only contain letters!',
'lastName.required' => 'You need to give a last name!',
'lastName.alpha' => 'A last name can only contain letters!'
]);
if ($validator->fails()) {
return response()->json($validator, 404);
}
$employees->editUser($user, $first_name, $last_name);
return response()->json($user);
}
So I realized the issue was twofold. First, what I was trying to return when the validator failed was incorrect. Rather than simply passing the whole validator, I needed to simply pass its messages, like so:
if ($validator->fails()) {
return response()->json($validator->messages(), 404);
}
The second issue actually had to do with calling "request()->all()". I had assumed the array obtained would have worked, but for some reason it didn't. When I created a new array based on the values in "request()" it was able to get the validator results I was expecting.
Related
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 }}
Ive read some things about this on laracasts and Stackoverflow.
I have an update function with validation:
public function update(Customer $customer, StoreCustomer $request)
{
$customer->update($request->validated());
exit();
}
And the validation rules:
public function rules()
{
return [
'code' => 'required|unique:customers,code',
]
}
Now I tried to add a 3rd argument after the unique, so if it would exist it would continue. I tried it like this:
public function rules(Customer $customer)
{
return [
'code' => 'required|unique:customers,code,'.$customer->code,
]
}
but that doesn't seem to do anything. It seems to work if you do the validation in my controller itself, but this looks way cleaner. Any solutions?
If you want to ignore the current customer, you need to change the $customer->code to $customer->id, assuming your primary key is id.
unqiue validation documentation
Ignoring current customer:
'code' => 'required|unique:customers,code,'.$customer->id,
Your Form Request is a Request. It gets filled with the data from the current Request. You can pull the customer from your route as it is bound as a parameter currently. $this->route('customer')
public function rules()
{
return [
'code' => 'required|unique:customers,code,'. $this->route('customer')->code,
// perhaps this should be ignoring by id though?
'code' => 'required|unique:customers,code,'. $this->route('customer')->id,
];
}
Using method injection here could only give you a binding, if one was registered with the container for that exact class, or a new instance of that class, the case here. There is no link between that class name and the concept that there might be a route parameter that currently contains a Model that happens to be of that class.
It seems that the right way to do this was
'code' => 'required|unique:customers,code,'.$this->route('customer')->code.',code',
Since the $customer parameter isnt available in rules() you needed to get the customer another way.
I think the best way do this with Rule:unique
return [
'code' => ['required', Rule::unique('customers', 'code')->whereNot('code', $customer->code)]
]
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,...
Default Laravel Validation class allows strange emails. Here is the Validation rules that I defined:
return Validation::make($data, [
'email' => 'required|string|email|max:100|unique:customers,email'
]);
When I tried to use some strange email like:
aaaa?#%&'#şğüçi̇ö.com it passes the validation. However the non latin characters on the email is converted before DB insert. So the email address on the database doesn't match with the original one.
In order to prevent this I want to disallow the usage of non-latin characters after the # symbol. I tried the custom rule which is:
public function passes($attribute, $value)
{
return filter_var($value, FILTER_VALIDATE_EMAIL)
&& preg_match('/#.+\./', $value);
}
but it is not working. It would be good to get some help on this.
Edit 1
Thanks for your responses! But apparently the reason that the custom validator not taking action is that Laravel sanitizes all input data before any manipulation. That's why after it converts the non-latin characters, preg_replace() returns 1 all the time since there is no non-latin characters on the input. First of all I need to find a solution to this and prevent Laravel to sanitize the input.
From your question I understand you already created a custom Validation Rule and use it like
...
'email' => [
'required',
'string',
...
new ValidateLatinEmail()
]
As you can see here, your RegEx is the problem with that validation
This one should work:
public function passes($attribute, $value)
{
return filter_var($value, FILTER_VALIDATE_EMAIL)
&& preg_match('/#[\x00-\x7F]*\./', $value);
}
Was tryin to validate my registration form calling the validation method from the basemodel in my controller
The method
public function postSIgnup ()
{
$validation = User::validate(Input::all());
}
Routes
Route::post('register', array('before=>'csrf', 'uses'=>'UsersController#postSignup'));
Help mi solve this problem
You can't just say 'validate my whole form'.
The reason this error occurs is because you are trying to use the validation method from Laravel.
Basic Form Validation in Laravel
First you want to grab all your form data/content.
$input = Input::all();
Secondly you can setup some rules. Those rules can be set in an array which Laravel will use.
Make sure the names are correctly spelled. (Those are the ones u used in your form-template.)
$rules = array(
'real_name' => 'Required|Min:3|Max:80|Alpha',
'email' => 'Required|Between:3,64|Email|Unique:users',
'age' => 'Integer|Min:18',
'password' =>'Required|AlphaNum|Between:4,8|Confirmed',
'password_confirmation'=>'Required|AlphaNum|Between:4,8'
);
To make use of the validator you have to create anew instance first:
You attach the form input and the rules you have set above.
When they match you can save your form to your database or what else you would like to do with it. That will probably look like:
$validator = Validator::make($input,$rules);
Cool,
We can check now if the Validator passes or not...
if($validator->fails()){
$messages = $validator->messages();
return Redirect::to('yourpage')->withErrors($messages);
}else{
// Handle your data...
}