I have been using the following validation for my form in Laravel:
public function isValid($data, $rules)
{
$validation = Validator::make($data, $rules);
if($validation->passes()){
return true;
}
$this->messages = $validation->messages();
return false;
}
The rules passed to it are simple:
$rules = [
'name' => 'required',
'type' => 'required'
];
And $data is the input post data. Now I need to add a custom validation extension to this, specifically to make sure that the value of input field round2 is greater than the value of input field round1. Looking at the docs, I have tried the following syntax which I think should be correct, but I keep getting an error.
$validation->extend('manual_capture', function($attribute, $value, $parameters)
{
return $value > $parameters[0];
});
Then I could call this with $attribute = 'round1', $value = $data['round1'] and $parameters = [$data['round2']].
The error is Method [extend] does not exist. - I'm not sure if my understanding of this whole concept is correct, so can someone tell me how to make it work? The docs only have about 2 paragraphs about this.
Put the following in your route.php
Validator::extend('manual_capture', function($attribute, $value, $parameters)
{
return $value > $parameters[0];
});
Additional documentation here
Then use it like so:
$rules = [ 'foo' => 'manual_capture:30'];
Related
I am using the Lumen Framework, which utilizes the Laravel Validation
I wanted to create a Validator Rule to make the Request->input() json only contain specific keys at the root like "domain" and "nameservers". Not more and not less.
Example passing the rule:
{
"domain":"domain.tld",
"nameservers":
{...}
}
Example not passing the rule:
{
"domain":"domain.tld",
"nameservers":
{...},
"Hack":"executeSomething()"
}
I tried to use to use several default validation rules to achieve this but wasnt successful.
My approach was now to put the request in another array like this
$checkInput['input'] = $request->all();
to make the validator validate the "root" keys.
Now this is my Approach:
create the validator
$checkInput['input'] = $request->all();
$validator = Validator::make($checkInput, [
'input' => [
'onlyContains:domain,nameservers'
],
]);
creating the rule
Validator::extend('onlyContains', function($attribute, $value, $parameters, $validator){
$input = $validator->getData();
$ok = 0;
foreach ($parameters as $key => $value) {
if (Arr::has($input, $attribute . '.' . $value)) {
$ok++;
}
}
if (sizeof(Arr::get($input, $attribute)) - $ok > 0) {
return false;
}
return true;
});
It seems i got the desired result, but i am asking if there is maybe smarter solution to this with the default rules provided by Laravel/Lumen.
You are trying to do a blacklisting approach blocking out fields that are not intended. A simple approach, that is utilized a lot, is to only fetch out the validated. Also you are trying to do logic, that goes against normal validation logic, to do it a field at a time.
This is also a good time, to learn about FormRequest and how you can get that logic, into a place where it makes more sense.
public function route(MyRequest $request) {
$input = $request->validated();
}
With this approach, you will only ever have the validated fields in the $input variable. As an extra bonus, this approach will make your code way easier to pick up by other Laravel developers. Example form request below.
public class MyRequest extends FormRequest
{
public function rules()
{
return [
'domain' => ['required', 'string'],
'nameservers' => ['required', 'array'],
];
}
}
You should use prohibited rule.
For eg:
$allowedKeys = ['domain', 'nameservers'];
$inputData = $request->all();
$inputKeys = array_keys($inputData);
$diffKeys = array_diff($inputKeys, $allowedKeys);
$rules = [];
foreach($diffKeys as $value) {
$rules[$value] = ['prohibited'];
}
I'm trying to add custom validation logic for file uploads for my admin panel. Right now my file fields can return either Illuminate\Http\UploadedFile or string|null if the file is not uploaded or changed or whatever. What I'm doing is, I created a custom rule that looks like this:
'image' => [
'required',
'admin_file:mimes:jpeg;png,dimensions:min_width=800;min_height=600'
]
I then parse all the arguments I pass, and the thing is, I naturally want all of them applied only if my value is an instance of UploadedFile. I use the following code for my custom validation:
<?php
class AdminFileValidator
{
public function validate($attribute, $value, $parameters, Validator $validator)
{
$rules = implode(
"|",
array_map(function($item) {
return str_replace(";", ",", $item);
}, $parameters)
);
$validator->sometimes($attribute, $rules, function() use ($value) {
return $value instanceof UploadedFile;
});
return true;
}
}
The problem is with adding additional rules to an attribute via sometimes doesn't work that way. The added rules are not being processed by a validator.
Is there any way to validate these rules without revalidating the whole thing manually?
What I see is that your are using sometimes inside of a rule. From my perspective you need to take it out, even better without use a custom class.
Using Validator object:
$validator = Validator::make($data, [
'image' => 'required',
]);
$validator->sometimes('image', 'mimes:jpeg;png,dimensions:min_width=800', function($value) {
return $value instanceof UploadedFile;
});
If you are using a Request class you could override the function getValidatorInstance in order apply the conditional rules:
protected function getValidatorInstance(){
$validator = parent::getValidatorInstance();
$validator->sometimes('image', 'mimes:jpeg;png,dimensions:min_width=800', function($value) {
return $value instanceof UploadedFile;
});
return $validator;
}
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;
}
The Issue
Consider this:
protected $rules = array(
'mobile_number' => 'phone:AUTO,mobile,:country_code'
);
In the above example, the value of country_code needs to change dependant on a variable defined prior to the validation taking place.
With this in mind, is it possible to pass a variable into a Laravel Validation rule?
Please bear in mind, this is how I call my validation:
if(!$this->some_validator->with($data)->passes()){
// Get the validation errors and throw the exception
$error_info = $this->some_validator->formatErrorMessages();
throw new ExampleException(ExampleExceptionType::$VALIDATION_ERROR,$error_info);
}
You are allowed to create custom validation rules.
It looks like this:
Validator::extend('country_code', function($attribute, $value, $parameters, $validator) {
if ($value === 'US') {
return false;
}
return true;
});
And then just use it like this:
protected $rules = array(
'mobile_number' => 'phone:AUTO,mobile|country_code'
);
After re-reading your message I realized that you meant a bit different thing. But take a look at the $parameters argument.
And read documentation. It's quite well covered there https://laravel.com/docs/5.3/validation#custom-validation-rules
With the new way of doing this, if you extend a new rule and you have a variable that you want to pass to the closure, you can do it like this:
Validator::extend(nameOfTheRule, function ($attribute, $value, $parameters, $validator) use ($priorVariable) {
//code
}
In below rules, I have my custom validation customRule: *date*
$rules = [
'my_date' => 'required|date_format: Y-m-d|customRule: someDate',
];
Inside my custom validation rules extension, I need to access the date_format attribute of the rule:
Validator::extend('customRule', function($attribute, $value, $parameters) {
$format = $attribute->getRules()['date_format']; // I need something like this
return $format == 'Y-m-d';
});
How can I get the rule value of certain attribute on an extended validator?
You can't access other rules. Validators are to be independent units - the only data they should use is:
value of field being validated
values passed to this validation rule as parameters
values of other attributes of object being validated
It seems that what you need is a custom validator that would wrap what is date_format and customRule doing:
Validator::extend('custom_date_format', function($attribute, $value, $parameters) {
$format = $parameters[0];
$someDate = $parameters[1];
$validator = Validator::make(['value' => $value], ['value' => 'date_format:' . $format]);
//validate dateformat
if ($validator->fails()) {
return false;
}
//validate custom rule using $format and $someDate and return true if passes
});
Once you have it, you can use it like that:
$rules = [
'my_date' => 'required|custom_date_format:Y-m-d,someDate',
];