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

9

52

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?

Ossifrage answered 1/3, 2015 at 12:48 Comment(0)
G
50

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

Genuine answered 1/3, 2015 at 14:43 Comment(2)
How do I get custom error messages to work here? Simply adding a $messages array in the model doesn't seem to do the trick.Carlson
@Carlson in your Request class you can add a new method messages() and return the array of custom messages. eg: return [ 'field_name.custom_validator_name' => 'Your error message'];Itis
S
55

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',
        ];
    }
}
Sapphire answered 14/7, 2015 at 23:1 Comment(0)
G
50

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

Genuine answered 1/3, 2015 at 14:43 Comment(2)
How do I get custom error messages to work here? Simply adding a $messages array in the model doesn't seem to do the trick.Carlson
@Carlson in your Request class you can add a new method messages() and return the array of custom messages. eg: return [ 'field_name.custom_validator_name' => 'Your error message'];Itis
D
21

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!'
        ];
    }
}
Doorsill answered 11/3, 2015 at 1:54 Comment(1)
This is actually baked in as of 5.3.23 named withValidator: github.com/laravel/framework/commit/…Aldose
S
12

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]
];
Stemma answered 11/4, 2018 at 7:11 Comment(4)
When I used it with JS validation (github.com/proengsoft/laravel-jsvalidation), I am getting the error : trim() expects parameter 1 to be string, object given . I solved it by adding a "public function __toString(){return 'NumericArray ';}" in the NumericArray class. github.com/mpociot/laravel-apidoc-generator/issues/247Flocculus
When you call new NumericArray, is there a way to pass an extra parameter? For example, I have a Custom Rule Object, and right now it checks against one model (Address), but I would like to pass the model as a parameter: public function passes($attribute, $value) { return !is_null(\App\Address::whereUuid($value)->where('user_id', request()->user()->id)->first()); }Mcreynolds
@kyleRidolfo maybe you can define the constructor while initializing pass your model like item => [new NumericArray($model)] and in rule class NumericArray read it in constructor and initialize a class property that you can use in passes method: private $classProperty; public function __construct($model) { $this->classProperty = $model; }Stemma
@GaneshKarki Ah yes that makes perfect sense, not sure what I was thinking! :) Thank you!Mcreynolds
H
11

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

Hutchison answered 25/11, 2019 at 14:4 Comment(2)
anyway, this didn't workWert
Btw we can also access other request parameter value within the 'addExtension' function using $validator->getData()['another_request_parameter']: $validator->addExtension('not_equal', function ($attribute, $value, $parameters, $validator) { return $value != $validator->getData()['another_request_parameter']; });Sulfapyridine
G
6

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;
}
Gnomon answered 1/3, 2015 at 13:1 Comment(12)
Do you really need to do it this way? As far as I know using Validator::extend() in a service provider should be enough to make the rule globally available.Genuine
Could you please post sample code to use in service provider.I haven't got anything by reading laravel docs :(Ossifrage
@ShihabudheenMuhammed Here's a good example from the laracasts forumGenuine
@Genuine I created but I getting error message is Method [validatePasscheck] does not exist. How can I resolve the same?Ossifrage
@ShihabudheenMuhammed have you registered the service provider? You need to add it in config/app.php under providers.Genuine
@Genuine Thank you for answer.I added those lines but now I getting another error is "Class translator does not exist". :(Ossifrage
@ShihabudheenMuhammed translator?? Did you do $this->app['translator'] instead of $this->app['validator']?Genuine
No..my code is public function register() { $this->app['validator']->make('test', function ($attribute, $value, $parameters) { return false; }); }Ossifrage
@ShihabudheenMuhammed Oh yeah boot is better anyways ;)Genuine
Thank you for your help :) but I don't know the difference between boot and register :(Ossifrage
laracasts.com/discuss/channels/general-discussion/… finally I got the difference also.Ossifrage
This answer is WAYYYYYYYYYYY better than creating an entire service provider, especially when you only need to utilize the rule once.Stupidity
L
4

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'
];
Lava answered 29/8, 2016 at 16:11 Comment(1)
Although the actual titled question of the OP is not answered in this answer. This answer is a much better solution for the OP's actual scenario.Maes
W
1

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

  1. Games started in 1896
  2. Year can’t be bigger than current year
  3. 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.

Washday answered 19/11, 2018 at 12:43 Comment(1)
Example from laraveldaily.com/how-to-create-custom-validation-rules-laravelSkutchan
E
0

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.

Ehman answered 17/9, 2017 at 13:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.