Laravel Validation Rules - php

My need is just Laravel validation Rules.I want to use Laravel validation to check variables. and show custom errors by returning string in controller.(I dont use view, blade, session,... I just return string)
if(strlen($username) < 4) return '{"r": "US","msg": "username is short"}';
if(strlen($username) > 64) return '{"r": "UL","msg": "username is long"}';
if(strlen($address) > 200) return '{"r": "A","msg": "wrong address"}';
I want something like this:
if($validation->username->min has error)
return 'string:username is short';
if($validation->address->max has error)
return 'string:address is long';
if($validation->username->unique has error)
return 'string:username already exists';

Have a look at the official documentation of validation in Laravel. You don't have to handle every case manually. Validator::make() will generate a validator object for you. The first parameter will take your data as an associative array. The second argument will define all rules as desired. As a third, optional parameter you may define alternative error messages if you don't like the default ones. The will be returned in the errors() method in case something isn't valid.
$validator = Validator::make($yourDataArray, [
'username' => 'min:4|max:64|exists:table,username',
'address' => 'max:64'
], [
'min' => ':attribute is too short.',
'exists' => ':attribute already exists.
]);
if ($validator->fails()) {
return $validator->errors()->all();
}
If you don't want to get an array with all errors at once, you can get the state of each field like so:
if ($validator->errors()->has('username')) { // Username field is invalid
return $validator->errors()->first('username'); // Get the first error
}
And if you want to know what rule exactly failed, you can use something like that:
if(isset($validator->failed()['username']['Max'])) {
return 'Username is too long.';
}

Related

Laravel 9 API return error codes instead of error messages on validation

I am trying to make Laravel return error CODES instead of messages along with some extra data depending on the validation error.
Quick examples:
If I have a "unique" validation rule I would expect the result for that field to be:
<field>: {
"code": "unique_rule_error",
"message": "The username has already been taken."
}
If I have, let's say a min rule:
<field>: {
"code": "min_rule_error",
"value": "100",
"message": "The username must be at least 100 characters."
}
In both examples, the "message" field is unnecessary, I am just keeping it there for the time being.
What I have currently working and almost returning the desired results:
App\Validators\RestValidator (custom validator, extending the base Validator class)
class RestValidator extends Validator
{
public function addFailure($attribute, $rule, $parameters = [])
{
if (!$this->messages) {
$this->passes();
}
$attribute = str_replace(
[$this->dotPlaceholder, '__asterisk__'],
['.', '*'],
$attribute
);
if (in_array($rule, $this->excludeRules)) {
return $this->exclude`enter code here`Attribute($attribute);
}
$message = $this->getMessage($attribute, $rule);
$message = $this->makeReplacements($message, $attribute, $rule, $parameters);
$customMessage = new MessageBag();
$customMessage->merge(['code' => strtolower($rule . '_rule_error')]);
if ($rule !== 'Unique') {
$parts = explode(':', $this->currentRule);
if (count($parts) >= 2) {
$boundaries = explode(',', $parts[1]);
$boundaries_count = count($boundaries);
if ($boundaries_count == 1) {
$customMessage->merge(['value' => $boundaries[0]]);
}
if ($boundaries_count > 1) {
$customMessage->merge(['lower' => $boundaries[0]]);
}
if ($boundaries_count >= 2) {
$customMessage->merge(['upper' => $boundaries[1]]);
}
}
}
$customMessage->merge(['message' => $message]);
$this->messages->add($attribute, $customMessage);
}
}
AppServiceProvider
public function boot()
{
//
Validator::resolver(function ($trnslator, $data, $rules, $messages) {
return new RestValidator($trnslator, $data, $rules, $messages);
});
}
This gives me the results I want with all rules I have currently tried, except when I use the Password validation:
Password::min(8)->mixedCase()->numbers()->symbols()
This causes a rather strange behavior that I can't explain, the result is the following:
"password": [
{
"code": "confirmed_rule_error",
"message": "The password confirmation does not match."
},
"{\"code\":\"min_rule_error\",\"value\":\"8\",\"message\":\"The password must be at least 8 characters.\"}",
"The password must contain at least one uppercase and one lowercase letter.",
"The password must contain at least one symbol."
]
The complete password validation code is:
'password' => ['required', 'confirmed', Password::min(8)->mixedCase()->numbers()->symbols()]
I want to return error codes like "unique_rule_error" because I will be translating the messages in the front-end with Vue and I don't want to keep track of a locale on the API side.
The problem seems to be that Laravel handles responses for strings and classed defined rules in different sections of code.
Laravel 9.34.0 has this chunk of code in Illuminate\Validation\Validator#603-607.
In your code, the "confirmed" rule is defined as string, hence in #603, the if statement is false, and eventually in #612 the addFailure method is called in RestValidator.
The next rule would be Password::... which is indeed an instance of RuleContract thus an early return is executed preventing the #612 addFailure method in RestValidator to be executed.
Checking the validateUsingCustomRule method in this same Illuminate\Validation\Validator class we find that in #829 an add method is called to populate error messages, you may want to add your logic there as well.

LARAVEL - Form validation return back to view instead of returning JSON?

I don't remember this is always been so freakin' hard to do in Laravel, but how to return back to form page blade.php instead of displaying the errors as JSON object on the browser?
Controller
public function create()
{
return view('view.to.form');
}
public function store(CreateModelRequest $request)
{
Model::create($request->validated());
}
// CreateModelRequest
protected function failedValidation(Validator $validator)
{
return back()->withErrors($validator);
}
I've tried countless other "solutions" as well, but no matter what,
the failed form request return raw JSON to the browser:
{
message: 'The given data was invalid',
errors: {
first_name: ['The first name field is required.'],
last_name: ['The last name field is required.'],
email: ['The email field is required.'],
},
}
}
Ok, I found one way to return back to the form but damn this is awful:
// Controller's store method
$validator = Validator::make($request->all(), [
// rules
]);
if ($validator->fails()) {
return back()->withErrors($validator->errors());
}
I'd like to extract that code into Request class but apparently we can't return a view and therefore we can't use them outside of API endpoints. Please, correct me if I'm wrong.

Lumen provide a code for validation errors

Currently in lumen when you use the $this->validate($request, $rules) function inside of a controller it will throw a ValidationException with error for your validation rules(if any fail of course).
However, I need to have a code for every validation rule. We can set custom messages for rules, but I need to add a unique code.
I know there's a "formatErrorsUsing" function, where you can pass a formatter. But the data returned by the passed argument to it, has already dropped the names of the rules that failed, and replaced them with their messages. I of course don't want to string check the message to determine the code that should go there.
I considered setting the message of all rules to be "CODE|This is the message" and parsing out the code, but this feels like a very hacked solution. There has to be a cleaner way right?
I've solved this for now with the following solution:
private function ruleToCode($rule) {
$map = [
'Required' => 1001,
];
if(isset($map[$rule])) {
return $map[$rule];
}
return $rule;
}
public function formatValidationErrors(Validator $validator) {
$errors = [];
foreach($validator->failed() as $field => $failed) {
foreach($failed as $rule => $params) {
$errors[] = [
'code' => $this->ruleToCode($rule),
'field' => $field,
];
}
}
return $errors;
}

Laravel - same custom error message for multiple fields

Im using a form request for validation and want to customize my errors. since I have a lot of fields to validate,I want to know if it is possible to use the same error message for multiple fields that have the same validation rule.
My actual form request looks like :
class CreateServerRequest extends Request
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'srv_prefix' => 'required|regex:/^[A-Z][-_A-Z0-9]*$/',
//20 more to go...
];
}
public function messages()
{
return [
'srv_prefix.required' => 'required.',
'srv_prefix.regex' => 'nope, bad format.'
];
}
}
I dont like the idea of adding as many lines of errors as fields (some fields may have 2 validation rules..) is there any way to tell laravel if validation rule = required then show this type of error regardless of the field ?
You can use just the validation name as the key for the message array, if you want all messages for that particular validation to be the same:
public function messages()
{
return [
'required' => 'The field :attribute is required.',
'regex' => 'nope, bad format.'
];
}
You can use :attribute as a placeholder that will be replaced with the field name, if you need that to be part of the error message. The documentation for this is in the Validation Custom Error Messages section, not in the Form Request Validation one.

Laravel "Serialization of 'Closure' is not allowed"

When I go to store a dataset in Laravel, I sometimes get this error and haven't found a solution to it.
Serialization of 'Closure' is not allowed
Open: ./vendor/laravel/framework/src/Illuminate/Session/Store.php
*/
public function save()
{
$this->addBagDataToSession();
$this->ageFlashData();
$this->handler->write($this->getId(), serialize($this->attributes));
$this->started = false;
Here is the function being called when the error occurs:
public function store()
{
$data = Input::all();
$validator = array('first_name' =>'required', 'last_name' => 'required', 'email' => 'email|required_without:phone', 'phone' => 'numeric|size:10|required_without:email', 'address' => 'required');
$validate = Validator::make($data, $validator);
if($validate->fails()){
return Redirect::back()->with('message', $validate);
} else {
$customer = new Customer;
foreach (Input::all() as $field => $value) {
if($field == '_token') continue;
$customer->$field = $value;
}
$customer->save();
return View::make('admin/customers/show')->withcustomer($customer);
}
}
What is causing this serialization error?
Just replace following line:
return Redirect::back()->with('message', $validate);
with this:
return Redirect::back()->withErrors($validate);
Also, you may use something like this (To repopulate the form with old values):
return Redirect::back()->withErrors($validate)->withInput();
In the view you can use $errors variable to get the error messages, so if you use $errors->all() then you'll get an array of error messages and to get a specific error you may try something like this:
{{ $errors->first('email') }} // Print (echo) the first error message for email field
Also, in the following line:
return View::make('admin/customers/show')->withcustomer($customer);
You need to change the dynamic method to withCustomer not withcustomer, so you'll be able to access $customer variable in your view.
return Redirect::back()->with('message', $validate);
You are telling Laravel to serialize the entire validator object into session. To redirect with errors, use the withErrors method:
return Redirect::back()->withErrors($validate);
This will take the error messages out of the validator and flash those to session prior to redirecting. The way you're doing it now you're trying to store the entire class in Session which is causing your error.
Another issue I see is that I don't think there's a withcustomer method on the View class:
return View::make('admin/customers/show')->withcustomer($customer);
Try changing that to either just with:
return View::make('admin/customers/show')->with('customer', $customer);
or make sure to capitalize the Customer portion:
return View::make('admin/customers/show')->withCustomer($customer);
See also this question.

Categories