Adding validation rule only if all other rules pass, or stop validating entire set of attributes on the first error in Laravel 5.7 - laravel

I want to allow a user to create a folder on the local storage disk. So the form that is sent to the server quite is simple and has three attributes:
new-folder-name - that is the name of the folder to be created,
relative-path - a path to the directory inside which the new directory should be created relative to an asset root directory, and
asset_id - the id of an asset, I need this id to get the asset's root directory.
The thing is when I validate these attributes I need to also check if the folder the user is going to create already exists. For this purpose I made a rule called FolderExists. So, before I run FolderExists, I have to be sure all other rules have passed successfully because my custom rule should accept relative-path and asset_id to be able to build the path to check against.
Here is my rules() function, I'm doing validation in custom form request:
public function rules()
{
return [
'asset_id' => ['bail', 'required', 'exists:assets,id'],
'relative-path' => ['bail', 'required', 'string'],
'new-folder-name' => ['bail', 'required', 'string', 'min:3', new FolderName, new FolderExists($this->input('asset_id'), $this->input('relative-path')]
];
}
So my question is:
Is it possible to add FolderExists only if all other validation rules pass?
Or maybe it's possible to stop entire validation when the validator encounters first error?
Both options should be fine here.
Thank you!

I have finally found the solution myself. Here is what I ended up with.
To achieve the desired result I created another validator in withValidator() method of my custom form request, this second validator will handle only the FolderExists rule and only if the previous validation fails.
public function rules()
{
return [
'asset-id' => ['bail', 'required', 'integer', 'exists:assets,id'],
'relative-path' => ['bail', 'required', 'string'],
'new-folder-name' => ['bail', 'required', 'string', 'min:3', 'max:150', new FolderName]
];
}
public function withValidator($validator)
{
if (!$validator->fails())
{
$v = Validator::make($this->input(),[
'new-folder-name' => [new FolderExists($this->input('asset-id'), $this->input('relative-path'))]
]);
$v->validate();
}
}
If our main validator passes, we make another validator and pass only FolderExists rule with its arguments, that have already been validated, and call validate() method. That's it.

Related

Array Validation: unique validation on multiple columns

I am trying to check unique validation on three columns employee_id,designation_id,station_id but the data are coming as an array which is making my situation unique and different from other SO questions/answers. I already checked few question like below: checks unique validation on multiple columns
But in my case, I can't get the value as they are inside an array. I also tried to implement Custom Rule or Request but in vain. For all the attempts, I am failing to get the field value such as $request->employee_id as they are inside an array for my case. May be I'm not trying it right.
Controller Code:
$this->validate($request, [
'posting.*.employee_id' => 'required,unique: // what to do here ??',
'posting.*.designation_id' => 'required',
'posting.*.station_id' => 'required',
'posting.*.from_date' => 'required|date',
]);
I am trying to validate uniqueness for both create and update (along with ignore $this->id facility) but don't know how to implement it here for array. It would be no problem if there was no array. Any help/suggestion/guide is much appreciated. Thanks in advance.
You can do this by creating a rule i.e UniquePosting so your controller code would look like
$this->validate($request, [
'posting' => ['required'],
'posting.*' => ['required', new UniquePosting()],
'posting.*.employee_id' => 'required',
'posting.*.designation_id' => 'required',
'posting.*.station_id' => 'required',
'posting.*.from_date' => 'required|date',
]);
Now inside your UniquePosting rule passes function will look like
public function passes($attribute, $value) {
$exists = Posting::where(['employee_id' => $value['employee_id'], 'designation_id' => $value['designation_id'],'station_id' => $value['station_id')->exists();
return !$exists;
}
Add any change if needed, overall that's the concept for testing uniqueness of the whole array.

Laravel validation rules shared between Form request and Validator in command

I have a set of validation rules in a FormRequest, Laravel 6.X like so:
{
return [
'rule1' => 'required|numeric',
'rule2' => 'required|numeric',
...,
'ruleN.*.rule1' => ''required|string|max:50'
];
}
This works just fine for every AJAX HTTP request against the endpoint for it was created. However, I also need to run the same process (Validate this time not against a Request but an array of inputs) via a CLI command, where this FormRequest cannot be used, as it won't be a Request.
By trying to inject the FormRequest into a Command like:
public function handle(CustomFormRequest $validator)
{
...
}
the command execution always fails (Of course), on every run, as the type of input isn't a Request.
Googling a bit I ended up with a solution of the shape of (Rules will be the same ofc):
$validator = Validator::make([
'rule1' => $firstName,
'rule2' => $lastName,
], [
'rule1' => ['required'],
'rule2' => ['required'],
]);
However I'd like to share the set of rules in a common place (A file for example) and that both the FormRequest AND the Validator class can take the same rules, without writing in both places (Form Request and Command), to keep them the same under any changes.
Is there way of writing the "rules" array in a common place (A file for example) and import them into both places? Maybe a base class would work, but it doesn't look right to do it, as they don't share anything else than that. I thought of a Trait, but I'm hesitant tht could be the best solution.

Skip Laravel's FormRequest Validation

I've recently added HaveIBeenPwned to my form request class to check for cracked passwords. Given that this makes an external API call, is there a way for me to skip either this validation rule or the FormRequest class altogether during testing?
Here's the request I make in my test.
$params = [
'first_name' => $this->faker->firstName(),
'last_name' => $this->faker->lastName(),
'email' => $email,
'password' => '$password',
'password_confirmation' => '$password',
'terms' => true,
'invitation' => $invitation->token
];
$response = $this->json('POST', '/register-invited', $params);
The functionality I'm testing resides on a controller. In my test I POST an array of data that passes through a FormRequest with the following rules.
public function rules()
{
return [
'first_name' => 'required|string|max:70',
'last_name' => 'required|string|max:70',
'email' =>
'required|email|unique:users,email|max:255|exists:invitations,email',
'password' => 'required|string|min:8|pwned|confirmed',
'is_trial_user' => 'nullable|boolean',
'terms' => 'required|boolean|accepted',
];
}
I want to override the 'pwned' rule on the password so I can just get to the controller without having to worry about passing validation.
With the information provided I'd say you are executing an integration test which does an actual web request. In such a context I'd say it's fine for your test suite to connect to a 3rd party since that's part of 'integrating'.
In case you still prefer to mock the validation rule you could swap out the Validator using either the swap
$mock = Mockery::mock(Validator::class);
$mock->shouldReceive('some-method')->andReturn('some-result');
Validator::swap($mock);
Or by replacing its instance in the service container
$mock = Mockery::mock(Validator::class);
$mock->shouldReceive('some-method')->andReturn('some-result');
App:bind($mock);
Alternatively you could mock the Cache::remember() call which is an interal part of the Pwned validation rule itself. Which would result into something like
Cache::shouldReceive('remember')
->once()
->andReturn(new \Illuminate\Support\Collection([]));

Laravel: Validate input only if available in DOM

I created a form with the following fields:
Name
Email
Country
City
Address
If the user selects a country that has states (ex. United States) then the form transforms to:
Name
Email
Country
State
City
Address
To validate this I created a separate form request like so:
public function rules()
{
return [
'name' => 'required|max:255',
'email' => 'required|email,
'country_id' => 'required|integer',
'state_id' => 'nullable|integer',
'city_id' => 'required|integer',
'address' => 'required',
];
}
The problem is that if I leave it like that, then if I don't select a state it will pass validation.
If i make it:
'state_id' => 'sometimes|nullable|integer',
Then again it passes validation.
If I make it:
'state_id' => 'required|nullable|integer',
It will not pass validation, but then again it will throw a validation error if there is no state field in the form.
I read a lot of articles about this but nothing seems to solve it for me.
PS1: I want to solve this in the form request, not in the controller. I assume that an
if($request->has('states')){...}
can help, but then again, i would like to keep everything tidy in the form request.
PS2: I am using VueJS and Axios to add/remove states from the form. The whole form is actually a Vue component.
Any clues?
Thank you in advance!
You can conditionally add rules via the sometimes method on Validator.
$v->sometimes('state_id', 'required|integer', function ($input) {
return in_array($input->countries, [1,2,3,4...]
});
You could use the required_with line of parameters, but because the validation is based on the value of the input instead of just the presence, the custom validation rule is probably your best bet.
Per https://laravel.com/docs/5.7/validation#conditionally-adding-rules

Laravel 5 validation rules

previously I have used validation within a Request class e.g.
public function rules()
{
return [
'userName' => 'required', 'min:3',
'userEmail' => 'required|email',
'departmentId' => 'required',
'slug' => 'required',
];
}
But I now have another form but I can't see any options within the documentation that might help me.
Basically, lets say I have a form with the same fields as the validation above. The only time validation should fail is if ALL fields contain absolutely no data. So if I put something like "hi" within the slug input and submit, it should pass the validation.
Would something like this be possible?
Thanks
You can probably use the required_without_all validation rule.
http://laravel.com/docs/5.1/validation#rule-required-without-all
The field under validation must be present only when all of the other
specified fields are not present.
It would give you something like
public function rules()
{
return [
'userName' => 'required_without_all:userEmal,departmentId,slug','min:3',
'userEmail' => 'required_without_all:userName,departmentId,slug|email'
...
];
}
But it's not very handy if you have a lot of fields.
If you have to deal with many fields, creating a custom validator might be a better solution.
http://laravel.com/docs/5.1/validation#custom-validation-rules

Resources