Make Laravel's notIn validation rule case insensitive - php

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.

Related

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'
];
}

Fractal transformer with different Serializers for nested items and collections

To change the JSON output send by a Laravel 5.4 RESTful API, I make use of the Fractal package by thephpleague. Because pagination will be added in the future, it is important that collections make use of the default DataArraySerializer and single items use the ArraySerializer. It is also needed that deeper nested objects are given the same structure. How can I achieve this (globally or not)?
class TreeTransformer extends TransformerAbstract {
protected $defaultIncludes = [
'type',
'branches'
];
public function transform(Tree $tree) {
return [
'id' => (int)$tree->id,
'name' => (string)$tree->name
];
}
public function includeType(Tree $tree) {
return $this->item($tree->type, new TypeTransformer()); // Should be ArraySerializer
}
public function includeBranches(Tree $tree) {
return $this->collection($tree->branches, new BranchTransformer()); // Should stay DataArraySerializer
}
}
Unfortunately, I think what you are trying to do is not possible yet. More information here: https://github.com/thephpleague/fractal/issues/315
You can still change the serializer for an entire output like this: https://github.com/spatie/fractalistic#using-a-serializer. But it is not what you want to achieve.
Actually you can.
It may look quite more verbose, but the trick is just not using $defaultIncludes. Use the fractal helper instead.
class TreeTransformer extends TransformerAbstract {
public function transform(Tree $tree) {
return [
'id' => (int)$tree->id,
'name' => (string)$tree->name,
'type' => $this->serializeType($tree)
'branches' => $this->serializeBranches($tree)
];
}
private function serializeType(Tree $tree) {
return fractal()
->serializeWith(new ArraySerializer())
->collection($tree->type)
->transformWith(TypeTransformer::class)
->toArray(); // ArraySerializer
}
private function serializeBranches(Tree $tree) {
return fractal()
->serializeWith(new DataArraySerializer())
->collection($tree->branches)
->transformWith(BranchesTransformer::class)
->toArray(); // DataArraySerializer
}
}
It's working for me with ArraySerializer. Didn't try DataArraySerializer.

Conditional validation on Custom form request

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;
});
}

Laravel 5.1 How to use the same form request rules for two methods when a field is required in one but not in the other method?

I have a form with 3 file input fields and the user should upload at least one file. I have a form request in which I'm validating them as follows:
public function rules()
{
$this->prepInput();
return [
'comment' => 'max:2000',
'source' => 'different:target',
'file1'=>'required_without_all:file2,file3|between:1,15360|mimes:txt,pdf',
'file2'=>'required_without_all:file1,file3|between:1,15360|mimes:txt,pdf',
'file3'=>'required_without_all:file1,file2|between:1,15360|mimes:txt,pdf'
];
}
To update the same form, I'm using update method in my controller which is almost the same as store method. The only difference is that files are not required in the update form. Is there any way to use the same form request with the store and update methods and apply the required rule optionally?
I'm basically using an abstract base class for both of my requests, and then add any required rules in subclasses. This will preserve DRY and gives a flexible way to add rules. For example:
abstract class CompanyBaseRequest extends FormRequest
{
...
public function rules()
{
return [
'name' => ['required', 'string', 'max:255'],
'category_id' => ['required', 'exists:company_categories,id'],
'short_description' => ['required', 'string', 'max:2000'],
'video' => ['nullable', 'file', 'mimes:mp4', 'max:30000'],
];
}
}
And then two subclasses:
class CompanyStoreRequest extends CompanyBaseRequest
{
...
public function rules()
{
return array_merge(parent::rules(), [
'logo' => ['required', 'file', 'mimes:png,jpg,jpeg', 'max:1024'],
]);
}
}
class CompanyUpdateRequest extends CompanyBaseRequest
{
...
public function rules()
{
return array_merge(parent::rules(), [
'logo' => ['nullable', 'file', 'mimes:png,jpg,jpeg', 'max:1024'],
]);
}
}
You should use one of these subclasses where needed, and they both will contain rules from the base class and rules from themselves.
This is better from the accepted answer because the forms themselves are explicitly saying what they do in their name, and don't just work with one condition (which is not clear what they check).
As you are doing like using a method $this->prepInput(); I suggest you change a little bit code to reuse.
You have to create named route for both the routes create & edit. I assume you are using resourceful routing
Change your code like bellow
public function isEditRequestCalled()
{
return app('router')->getCurrentRoute()->getName() == 'YOUR_EDIT_ROUTE_NAME';
}
and in your request method you change like this
public function rules()
{
$this->prepInput();
return $this->isEditRequestCalled() ? [
//YOUR EDIT RULES GOES HERE
] : [//YOUR CREATE RULES GOES HERE
'comment' => 'max:2000',
'source' => 'different:target',
'file1'=>'required_without_all:file2,file3|between:1,15360|mimes:txt,pdf',
'file2'=>'required_without_all:file1,file3|between:1,15360|mimes:txt,pdf',
'file3'=>'required_without_all:file1,file2|between:1,15360|mimes:txt,pdf'
];
}
I used the following trick and it worked:
public function rules()
{
$this->prepInput();
$rules= [
'comment' => 'max:2000',
'source' => 'different:target',
'file1'=>'required_without_all:file2,file3|between:1,15360|mimes:txt,pdf',
'file2'=>'required_without_all:file1,file3|between:1,15360|mimes:txt,pdf',
'file3'=>'required_without_all:file1,file2|between:1,15360|mimes:txt,pdf'
];
if($this->myprojects){
$rules['file1'] = 'between:1,15360|mimes:txt,pdf';
$rules['file2'] = 'between:1,15360|mimes:txt,pdf';
$rules['file3'] = 'between:1,15360|mimes:txt,pdf';
}
return $rules;
}
Here my route information are as follows:
myprojects/{myprojects}/edit | myprojects.edit | App\Http\Controllers\MyProjectsController#edit
So the id of the my myprojects entity is $this->myprojects. If it is null it is creating a myprojects, if it has a value it is updating the corresponding myprojects.
I use separate Rule classes, which basically just store the $rules and $messages I need for use and reuse in FormRequest classes.
class RulePrep
{
/**
* #var array
*/
public $rules = [];
/**
* #var array
*/
public $messages = [];
}
class RuleProjects
{
/**
* #var array
*/
public $rules = [];
/**
* #var array
*/
public $messages = [];
}
You could try that?
You'd need separate FormRequest classes but it's perhaps neater than all bundled into one with the conditional logic in there.

How add Custom Validation Rules when using Form Request Validation in Laravel 5

I am using form request validation method for validating request in laravel 5.I would like to add my own validation rule with form request validation method.My request class is given below.I want to add custom validation numeric_array with field items.
protected $rules = [
'shipping_country' => ['max:60'],
'items' => ['array|numericarray']
];
My cusotom function is given below
Validator::extend('numericarray', function($attribute, $value, $parameters) {
foreach ($value as $v) {
if (!is_int($v)) {
return false;
}
}
return true;
});
How can use this validation method with about form request validation in laravel5?
While the above answer is correct, in a lot of cases you might want to create a custom validation only for a certain form request. You can leverage laravel FormRequest and use dependency injection to extend the validation factory. I think this solution is much simpler than creating a service provider.
Here is how it can be done.
use Illuminate\Validation\Factory as ValidationFactory;
class UpdateMyUserRequest extends FormRequest {
public function __construct(ValidationFactory $validationFactory)
{
$validationFactory->extend(
'foo',
function ($attribute, $value, $parameters) {
return 'foo' === $value;
},
'Sorry, it failed foo validation!'
);
}
public function rules()
{
return [
'username' => 'foo',
];
}
}
Using Validator::extend() like you do is actually perfectly fine you just need to put that in a Service Provider like this:
<?php namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class ValidatorServiceProvider extends ServiceProvider {
public function boot()
{
$this->app['validator']->extend('numericarray', function ($attribute, $value, $parameters)
{
foreach ($value as $v) {
if (!is_int($v)) {
return false;
}
}
return true;
});
}
public function register()
{
//
}
}
Then register the provider by adding it to the list in config/app.php:
'providers' => [
// Other Service Providers
'App\Providers\ValidatorServiceProvider',
],
You now can use the numericarray validation rule everywhere you want
The accepted answer works for global validation rules, but many times you will be validating certain conditions that are very specific to a form. Here's what I recommend in those circumstances (that seems to be somewhat intended from Laravel source code at line 75 of FormRequest.php):
Add a validator method to the parent Request your requests will extend:
<?php namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Validator;
abstract class Request extends FormRequest {
public function validator(){
$v = Validator::make($this->input(), $this->rules(), $this->messages(), $this->attributes());
if(method_exists($this, 'moreValidation')){
$this->moreValidation($v);
}
return $v;
}
}
Now all your specific requests will look like this:
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
class ShipRequest extends Request {
public function rules()
{
return [
'shipping_country' => 'max:60',
'items' => 'array'
];
}
// Here we can do more with the validation instance...
public function moreValidation($validator){
// Use an "after validation hook" (see laravel docs)
$validator->after(function($validator)
{
// Check to see if valid numeric array
foreach ($this->input('items') as $item) {
if (!is_int($item)) {
$validator->errors()->add('items', 'Items should all be numeric');
break;
}
}
});
}
// Bonus: I also like to take care of any custom messages here
public function messages(){
return [
'shipping_country.max' => 'Whoa! Easy there on shipping char. count!'
];
}
}
Custom Rule Object
One way to do it is by using Custom Rule Object, this way you can define as many rule as you want without need to make changes in Providers and in controller/service to set new rules.
php artisan make:rule NumericArray
In NumericArray.php
namespace App\Rules;
class NumericArray implements Rule
{
public function passes($attribute, $value)
{
foreach ($value as $v) {
if (!is_int($v)) {
return false;
}
}
return true;
}
public function message()
{
return 'error message...';
}
}
Then in Form request have
use App\Rules\NumericArray;
.
.
protected $rules = [
'shipping_country' => ['max:60'],
'items' => ['array', new NumericArray]
];
Alternatively to Adrian Gunawan's solution this now also can be approached like:
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreBlogPost extends FormRequest
{
public function rules()
{
return [
'title' => ['required', 'not_lorem_ipsum'],
];
}
public function withValidator($validator)
{
$validator->addExtension('not_lorem_ipsum', function ($attribute, $value, $parameters, $validator) {
return $value != 'lorem ipsum';
});
$validator->addReplacer('not_lorem_ipsum', function ($message, $attribute, $rule, $parameters, $validator) {
return __("The :attribute can't be lorem ipsum.", compact('attribute'));
});
}
}
You need to override getValidatorInstance method in your Request class, for example this way:
protected function getValidatorInstance()
{
$validator = parent::getValidatorInstance();
$validator->addImplicitExtension('numericarray', function($attribute, $value, $parameters) {
foreach ($value as $v) {
if (!is_int($v)) {
return false;
}
}
return true;
});
return $validator;
}
You don't need to extend the validator to validate array items, you can validate each item of a array with "*" as you can see in
Array Validation
protected $rules = [
'shipping_country' => ['max:60'],
'items' => ['array'],
'items.*' => 'integer'
];
All answers on this page will solve you the problem, but... But the only right way by the Laravel conventions is solution from Ganesh Karki
One example:
Let’s take an example of a form to fill in Summer Olympic Games events – so year and city. First create one form.
<form action="/olimpicyear" method="post">
Year:<br>
<input type="text" name="year" value=""><br>
City:<br>
<input type="text" name="city" value=""><br><br>
<input type="submit" value="Submit">
</form>
Now, let’s create a validation rule that you can enter only the year of Olympic Games. These are the conditions
Games started in 1896
Year can’t be bigger than current year
Number should be divided by 4
Let’s run a command:
php artisan make:rule OlympicYear
Laravel generates a file app/Rules/OlympicYear.php. Change that file to look like this:
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class OlympicYear implements Rule
{
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
// Set condition that should be filled
return $value >= 1896 && $value <= date('Y') && $value % 4 == 0;
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
// Set custom error message.
return ':attribute should be a year of Olympic Games';
}
}
Finally, how we use this class? In controller's store() method we have this code:
public function store(Request $request)
{
$this->validate($request, ['year' => new OlympicYear]);
}
If you want to create validation by Laravel conventions follow tutorial in link below. It is easy and very well explained. It helped me a lot. Link for original tutorial is here Tutorial link.
For me works the solution that give us lukasgeiter, but with a difference that we create a class with our custom validations ,like this, for laravel 5.2.* The next example is for add a validation to a range of date in where the second date has to be equals or more big that the first one
In app/Providers create ValidatorExtended.php
<?php
namespace App\Providers;
use Illuminate\Validation\Validator as IlluminateValidator;
class ValidatorExtended extends IlluminateValidator {
private $_custom_messages = array(
"after_or_equal" => ":attribute debe ser una fecha posterior o igual a
:date.",
);
public function __construct( $translator, $data, $rules, $messages = array(),
$customAttributes = array() ) {
parent::__construct( $translator, $data, $rules, $messages,
$customAttributes );
$this->_set_custom_stuff();
}
protected function _set_custom_stuff() {
//setup our custom error messages
$this->setCustomMessages( $this->_custom_messages );
}
/**
* La fecha final debe ser mayor o igual a la fecha inicial
*
* after_or_equal
*/
protected function validateAfterOrEqual( $attribute, $value, $parameters,
$validator) {
return strtotime($validator->getData()[$parameters[0]]) <=
strtotime($value);
}
} //end of class
Ok. now lets create the Service Provider. Create ValidationExtensionServiceProvider.php inside app/Providers, and we code
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Validator;
class ValidationExtensionServiceProvider extends ServiceProvider {
public function register() {}
public function boot() {
$this->app->validator->resolver( function( $translator, $data, $rules,
$messages = array(), $customAttributes = array() ) {
return new ValidatorExtended( $translator, $data, $rules, $messages,
$customAttributes );
} );
}
} //end of class
Now we to tell Laravel to load this Service Provider, add to providers array at the end in config/app.php and
//Servicio para extender validaciones
App\Providers\ValidationExtensionServiceProvider::class,
now we can use this validation in our request in function rules
public function rules()
{
return [
'fDesde' => 'date',
'fHasta' => 'date|after_or_equal:fDesde'
];
}
or in Validator:make
$validator = Validator::make($request->all(), [
'fDesde' => 'date',
'fHasta' => 'date|after_or_equal:fDesde'
], $messages);
you have to notice that the name of the method that makes the validation has the prefix validate and is in camel case style validateAfterOrEqual but when you use the rule of validation every capital letter is replaced with underscore and the letter in lowercase letter.
All this I take it from https://www.sitepoint.com/data-validation-laravel-right-way-custom-validators// here explain in details. thanks to them.

Categories