Conditional validation on Custom form request - php

on my controller I have:
public function store(ProductRequest $request)
The request:
class ProductRequest extends Request
{
public function rules()
{
return [
'name' => 'required|min:3',
'perTypeTime' => 'sometimes|required',
'per_type_id' => 'required'
];
}
}
I want to change the perTypeTime rule above to be conditional depending on if per_type_id field == 1.
If I initiated the validator in the controller I believe I could do something like the below:
$v = Validator::make($data, [
'per_type_id' => 'required|email'
]);
$v->sometimes('perTypeTime', 'required|max:500', function($input)
{
return $input->per_type_id == 1;
});
Is there a way to do this, while keeping my custom request. I like how this approach keeps the controller cleaner.
I can't seem to access the validator on the request object. Is there a way to specify this condition inside the request itself, or to access the validator from the request object, or is there another way?

You can do that
I want to change the perTypeTime rule above to be conditional depending on if per_type_id field == 1.
within your rules() method in your ProductRequest.
For details see required_if:anotherfield,value in the Laravel documentation validation rules.
public function rules()
{
return [
'name' => 'required|min:3',
'perTypeTime' => 'required_if:per_type_id,1',
'per_type_id' => 'required'
];
}

Laravel 5.3, in your request file you can add:
use Illuminate\Validation\Factory;
...
public function validator(Factory $factory)
{
$validator = $factory->make($this->input(), $this->rules());
$validator->sometimes('your_sometimes_field', 'your_validation_rule', function($input) {
return $input->your_sometimes_field !== null;
});
return $validator;
}

Actually this answer https://stackoverflow.com/a/41842050/3922975 is not the best.
We don't have to replace default validator with our own (because we are not changing anything). In that solution we hope validation factory will always require only two attributes ($this->input(), $this->rules()) which is actually not true even in time of writing.
This is a default validator used by Laravel:
$factory->make(
$this->validationData(),
$this->container->call([$this, 'rules']),
$this->messages(),
$this->attributes()
);
As you can see it is much different from that Artem Verbo used.
Better solution is to create withValidator method in your ProductRequest class:
use Illuminate\Contracts\Validation\Validator;
...
public function withValidator(Validator $validator)
{
$validator->sometimes('your_sometimes_field', 'your_validation_rule', function ($input) {
return $input->your_sometimes_field !== null;
});
}

Related

Laravel9: route parameter always missing in validation

I am using Laravel v9.2.1 + Laravel Sanctum v2.14.1
I got a route
DELETE /api/v1/auth/tokens/{token}
for example (the token is an uuid)
DELETE http://example.com/api/v1/auth/tokens/5fcfa274-81d8-4e9f-8feb-207db77531af
And I am sure it works as expected via php artisan route:list
Before handling by the Controller, it should be validated by a FormRequest
app/Http/Controllers/V1/Auth/TokensController.php
namespace App\Http\Controllers\V1\Auth;
use App\Http\Requests\V1\Auth\Tokens\{
DestroyRequest,
};
class TokensController extends Controller
{
public function destroy(DestroyRequest $request) {
$request->user()->tokens()->where('id', $request->token)->first()->delete();
return response()->noContent();
}
}
app/Http/Requests/V1/Auth/Tokens/DestroyRequest.php
class DestroyRequest extends FormRequest
{
public function rules()
{
return [
'token' => [
'required',
'string',
'regex:/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i',
Rule::exists('personal_access_tokens')->where(function ($query) {
return $query->where('tokenable_id', $this->user()->id);
}),
]
];
}
}
But what I only got is The token field is required
I had already pass the token, why the 'required' rule still working?
What I tried
Only if I pass the token parameter like below, it will work
DELETE /api/auth/tokens/something?token=test_regex_is_working
I try to dd($this->token) in app/Http/Requests/V1/Auth/Tokens/DestroyRequest.php, it works as expected.
i might try going about it differently as the token isn't really user input
In the routes file:
Route::delete('/api/v1/auth/tokens/{token}', [TokensController::class, 'destroy'])->whereUuid('token');
In the FormRequest something maybe like this:
public function authorize()
{
return DB::table('personal_access_tokens')
->where('tokenable_id', Auth::id())
->where('token', $this->route('token'))
->exists()
}
You might need to add the following in the FormRequest class:
protected function prepareForValidation()
{
$this->merge(['token' => $this->route('token')]);
}
I believe URL parameters are not included in the request directly.
With the help of both #RawSlugs and #Aaron T, thank them a lot!
app/Http/Requests/V1/Auth/Tokens/DestroyRequest.php
protected function prepareForValidation() {
$this->merge(['token' => $this->route('token')]);
}
public function authorize() {
return $this->user()->tokens()->where('id', $this->token)->exists();
}
// But since the authorize() will validate the request before rules(), this will be useless
public function rules() {
return [
'token' => [
'required',
'string',
'regex:/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i',
]
];
}
I'm using Laravel Sanctum.

How to access request payload/post data inside rules() function - Laravel Form Request

I am trying to access a data called "section" in my rules() function inside my FormRequest Validator Class. What I am trying to do is check what is the value of "section" and return different rules. But apparently, I am not able to access the value of the data/payload.
I have checked the answer from here, but it didn't work for me : Laravel Form Request Validation with switch statement
Here is my code :
FormController.php
class FormController extends Controller
{
public function verify(DynamicFormRequest $request , $section)
{
return response()->json(['success' => 'everything is ok']);
}
}
DynamicFormRequest.php
class DynamicFormRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
error_log($this->request->get('section'));//retruns null and depricated
error_log($this->request->input('section')); //this is absolutely wronge but , i tried it anyways
error_log($this->request->section); //retruns null
error_log($this->section); //retruns null
switch ($this->request->get('section')) {
case '1':
return [
'item_name' => 'required',
];
break;
case 'Type2':
return [
'item_favorite' => 'required',
];
break;
}
}
}
Please help to make me understand whats wronge
If you are using route model binding you can use $this->section right off the bat.
So this should work assuming your routes are set up in the correct format:
error_log($this->section);
Route (Something like....):
Route::post('section/{section}', [SectionController::class, 'update']);
This question could help: Stack Q: Laravel: Access Model instance in Form Request when using Route/Model binding
Lastly, Hard to know it's working without your other code. Have you dumped out $request to check section is in there?
you can use required_if:anotherfield,value to check the value for other fields
return [
'item_name' => 'required_if:section,1',
'item_favorite' => 'required_if:section,Type2'
]
https://laravel.com/docs/9.x/validation#rule-required-if
I hope it's helpful

Make Laravel's notIn validation rule case insensitive

I am storing an array of strings in my database (db column type is JSON). There is a form that allows users to add a value to this array. I want to make sure there are no duplicates in this array. The notIn validation rule appears be the simplest solution to prevent duplicates but it is case sensitive. So when using notIn I am not able to prevent identical strings that have different capitalization.
$this->validate(request(), [
'choice' => [
'required',
Rule::notIn($choices)
]
]);
Does anyone have recommendation on how I should fix this validation so that the string comparison is case insensitive?
You could lowercase your input data as well as your current data like this:
$input = request()->all();
$input['choice'] = array_map("strtolower", $input['choice']);
request()->validate($input, [
'choice' => [
'required',
Rule::notIn(array_map("strtolower", $choices))
]
]);
Thanks Ramy Herria, I was able to expand his answer to also work in a FormRequest class:
protected function validationData()
{
$all = parent::validationData();
//Convert request value to lowercase
$all['choice'] = strtolower($all['choice']);
return $all;
}
public function rules()
{
$choices = $this->route('modelName')->choices;
return [
'choice' => [
'required',
//Also convert array to lowercase
Rule::notIn(array_map('strtolower', $choices))
]
];
}
You can write your own validation rule class:
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Validation\Concerns\ValidatesAttributes;
use Illuminate\Validation\Rules\In;
class CaseInsensitiveInRule extends In implements Rule
{
use ValidatesAttributes;
private const FORMAT_FUNCTION = 'strtoupper';
public function __construct(array $values)
{
$this->values = array_map(self::FORMAT_FUNCTION, $values);
}
public function passes($attribute, $value)
{
$value = call_user_func(self::FORMAT_FUNCTION, $value);
return $this->validateIn($attribute, $value, $this->values);
}
public function message()
{
return __('validation.invalid_value');
}
}
and next you can create a object in your request class
public function rules(): array
{
return [
'status' => new CaseInsensitiveInRule(['active', 'deleted'])
];
}
i know it is a little late, but for others i will suggest to use prepareForValidation method inside custom request class; like follow
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class RegisterRequest extends FormRequest
{
protected function prepareForValidation()
{
$this->merge([
'choices' => strtolower($this->choices),
]);
}
}
this way user input for choices are always lower case and the request itself is modified too.

Dynamic FormRequest Validation (Laravel)

Is it possible to create a dynamic FormRequest validation in my function? See sample code below.
public function store(Request $request)
{
Model::create($request->all());
return redirect(url('/'));
}
What I mean is that I will change the "Request" parameter to the variable $formRequest.
My goal is that I would like to create different validation rules for a dynamic set of data of a single model.
If I could achieve this with other ways, please let me know. Thank you!
Edit:
Sample scenario:
I have a form that has fields of First Name, Middle Name and Last Name.
First Rule:
public function rules()
{
return [
'firstname' => 'required',
'middlename' => 'required',
'lastname' => 'required'
];
}
Second Rule:
public function rules()
{
return [
'firstname' => 'required',
'lastname' => 'required'
];
}
Where in the second rule only requires first and last name.
I just want to know if there are other ways of doing this rather than creating multiple store methods and adding more routes.
Skipping FormRequest and using the validate method on the $request instance can achieve this. Laracasts even has a lesson on it.
public function store(Request $request) {
$rules = [/*...*/];
$attributes = $request->validate($rules);
Model::create($attributes);
return redirect(url('/'));
}
You can create a custom request:
php artisan make:request CustomRequest
This will generate this class:
class CustomRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
//
];
}
}
The authorize() method will determine if the request can be validated in the first place.
The rules() method will return the validation rules for the current request.
And then in your controller function:
public function yourfunction(CustomRequest $request)
In the validation rules you can simply add the "sometimes" rule. You can find it here https://laravel.com/docs/5.7/validation#conditionally-adding-rules
public function rules()
{
return [
'firstname' => 'required',
'middlename' => 'sometimes|required',
'lastname' => 'required'
];
}

Trying to shift the Validation from Controller to Request Class in Laravel 5.2.15

I have a very simple Rule method in request class like below.
public function rules()
{
return [
'Subject' => 'required|max:50',
'Description' => 'required|max:500',
'DepartmentID' => 'required|integer|min:1',
'PriorityID' => 'required|integer|min:1'
];
}
Inside Controller Action method, below is the code.
private function SaveChanges(\App\Http\Requests\TicketRequest $request) {
$v = \Validator::make($request->all(), [
]);
$DepartmentAdmins = $this->getDepartmentAdmins();
//Check if department admin missing then no need to add the record
if($DepartmentAdmins == null || count($DepartmentAdmins) == 0) {
$v->errors()->add('MissingAdmins', 'Department admin missing.');
return redirect()->back()->withErrors($v->errors());
}
}
Question:
As we can see in the rule method there are 4 form fields. Is there any way to shift the check for Department Admin existence from Controller Action method to request class?
Laravel's Request has after hook that can be run after normal validation completes. This is how you can use it in your case:
namespace App\Http\Requests;
use App\Http\Requests\Request;
use App\Models\Property;
use Illuminate\Validation\Validator;
class SomeRequest extends Request
{
/**
* Get the validator instance for the request.
*
* #return Validator
*/
protected function getValidatorInstance()
{
$instance = parent::getValidatorInstance();
$instance->after(function ($validator) {
$this->validateDepartmentAdmins($validator);
});
return $instance;
}
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'Subject' => 'required|max:50',
'Description' => 'required|max:500',
'DepartmentID' => 'required|integer|min:1',
'PriorityID' => 'required|integer|min:1'
];
}
/**
* #param Validator $validator
*/
public function validateDepartmentAdmins(Validator $validator)
{
$DepartmentAdmins = $this->getDepartmentAdmins();
//Check if department admin missing then no need to add the record
if($DepartmentAdmins == null || count($DepartmentAdmins) == 0) {
$validator->errors()->add('MissingAdmins', 'Department admin missing.');
}
}
That way you won't have to do any validation in your SaveChanges controller method.
This code is used in Laravel 5.1, but I believe it will work the same in 5.2.
The Form Request Class basically has two methods. "authorize" and "rules". the best way to shift the check for Department Admin existense is to add your own custom validator(for example named "adminCountValidator") and implement your logic for checking the number of administrators there. Then use yoir newly defined validator in "rules" method like this:
public function rules()
{
return [
'Subject' => 'required|max:50',
'Description' => 'required|max:500',
'DepartmentID' => 'required|integer|min:1|adminCountValidator',
'PriorityID' => 'required|integer|min:1'
];
}
if you define a custome validation rule, you can also define the associated error message and your controller action will be much more cleaner. here is the link for defining your own custom validator
custom-validation-rules
here is a sample code for adding a custom validator within a service provider
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Validator::extend('adminCountValidator', function($attribute, $value, $parameters, $validator) {
/*
implement your getDepartmentAdmins()
function here and return true or false
*/
});
}

Categories