Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

trevorg's avatar

[L5] Where to add custom validation function?

I'm using the new FormRequest functionality, and it works great, but I'm wondering, what is the best way to add a custom validation function?

I would like to have a custom validatePhone() function that validates a phone number.

And then in my EditAccountRequest class I can use it in the rules:

 public function rules()
    {
        return [
            'phone' => ['sometimes','required','phone']
        ];
    }

But I'm not sure where to put the validatePhone() function, and how to tie it into the Validator instance.

I thought maybe doing it this way would work:

$this->getValidatorInstance()->addExtension('phone',...));

But I can't figure it out. Has anyone else successfully done this with Laravel 5?

0 likes
25 replies
bestmomo's avatar

You can create a method validator in your form request :

public function validator($factory)
{ 

}

This method must return a factory. Look at this method in parent :

protected function getValidatorInstance()
{
    $factory = $this->container->make('Illuminate\Validation\Factory');

    if (method_exists($this, 'validator'))
    {
        return $this->container->call([$this, 'validator'], compact('factory'));
    }

    return $factory->make(
        $this->formatInput(), $this->container->call([$this, 'rules']), $this->messages()
    );
}

So you can do all special validation in this function. But for a phone number simple regex must be sufficient in casual way.

For an extension of validator there are these 2 methods in Factory to register them :

/**
 * Register a custom validator extension.
 *
 * @param  string  $rule
 * @param  \Closure|string  $extension
 * @param  string  $message
 * @return void
 */
public function extend($rule, $extension, $message = null)
{
    $this->extensions[$rule] = $extension;

    if ($message) $this->fallbackMessages[snake_case($rule)] = $message;
}

/**
 * Register a custom implicit validator extension.
 *
 * @param  string   $rule
 * @param  \Closure|string  $extension
 * @param  string  $message
 * @return void
 */
public function extendImplicit($rule, $extension, $message = null)
{
    $this->implicitExtensions[$rule] = $extension;

    if ($message) $this->fallbackMessages[snake_case($rule)] = $message;
}

But I didn't try to add one.

2 likes
trevorg's avatar

@bestmomo Yes I've seen that first method, but I figure there must be a better way. The methods you reference in Factory I think are what I should use, but I'm not sure how exactly.

Jeffberry's avatar

This is how I do it, this seems like a simpler solution than @bestmomo's and also it will allow you to use the same validation helper in all requests instead of defining it directly in your request class. There probably are better ways to do this though.

I have a file app/validators.php which has the following (including my phone validation function):

<?php

/** @var \Illuminate\Validation\Factory $validator */

$validator->extend(
    'valid_password',
    function ($attribute, $value, $parameters)
    {
        return preg_match('/^[a-zA-Z0-9!@#$%\/\^&\*\(\)\-_\+\=\|\[\]{}\\\\?\.,<>`\'":;]+$/u', $value);
    }
);

$validator->extend(
    'phone_number',
    function ($attribute, $value, $parameters)
    {
        return strlen(preg_replace('#^.*([0-9]{3})[^0-9]*([0-9]{3})[^0-9]*([0-9]{4})$#', '$1$2$3', $value)) == 10;
    }
);

And then in your AppServiceProvider (or any registered provider):

    public function boot(Factory $validator)
    {
        require_once app_path() . '/validators.php';
    }

Then your validation rule would be:

 public function rules()
    {
        return [
            'phone' => ['sometimes','required','phone_number']
        ];
    }
1 like
bestmomo's avatar

Not sure it's a good way to extend a special validation rule for all request. I prefer select the requests that need it. Anyway there is an abstract class Request in Requests directory to collect all shared stuff, better to extend the validator there.

But I wonder about the utility of this special rule. It's almost always possible to simply use a regex to match the goal. For this phone number :

$rules = [
    'phone' => ['required', 'regex:/my nice regex/'] 
];

To get custom validation for one request just add custom rule in FormRequest constructor, for example :

use Illuminate\Validation\Factory;

...

/**
 * Create a new FormRequest instance.
 *
 * @return void
 */
public function __construct(Factory $factory)
{
    $factory->extend(
        'phone',
        function ($attribute, $value, $parameters)
        {
             return preg_match("/^([0-9\s\-\+\(\)]*)$/", $value);
        },
        'Bad number format'
    );
}

If you want it working for many FormRequest, you must put the custom validation in Request class and change inheritance.

3 likes
Jeffberry's avatar

I don't see why it's 'not good' to have it available to all requests. All other validation rules are. The benefit I see of having it as a rule instead of a regex pattern is for re-usable code, cleanliness of code, and so the rule can be re-used elsewhere throughout the models.

I choose to put mine in a validators.php like the example above because that's how routes are done as well. It makes it easy for the rest of my team to be able to see all validation rules and easily change or add new ones.

1 like
bestmomo's avatar

I especially wanted to say that add validation rules for all requests even those that dont need validation. But I said "I'm not sure", there are so many ways to do things with code ! But I think Taylor has created the Request.php in Requests directory for some shared setup reason...

Jeffberry's avatar

You're correct, there's a million ways to solve the same problem :)

I guess I just see the validator and the request object as different things, because in the IoC they are. To me it doesn't make sense to extend the validator in the request, because the request is meant for handling input while the validator can be used for many things. Many of my data sources come from APIs or through parsing simple data (CSV, XML, etc). I need to validate all of these sources as well. Using the request object would not make sense here and if my phone validation was inside the request class, I would need to duplicate that phone validation in my data parsing code.

That is just my opinion though, obviously your approach is easier and more direct, it's just limiting for my needs.

1 like
bestmomo's avatar

Your argument makes sense. Maybe a nice place to extend validation is in a ServiceProvider.

1 like
Jeffberry's avatar

Yes, indeed the service provider would be a good place to do these extensions. I only chose to use a separate file because it makes it easier for the other members of my team. They don't all fully understand or even need to understand the service providers.

1 like
trevorg's avatar

@bestmomo and @Jeffberry A service provider does sound like a good solution as well. I'm curious if Laracasts could do a video on more in-depth validation and sanitizing techniques, maybe as a series for Laravel 5.

3 likes
pmall's avatar
# app/wherever-you-want/CustomValidator.php

<?php

class CustomValidator extends Illuminate\Validation\Validator {

    public function validateFoo($attribute, $value, $parameters)
    {
        return $value == 'foo';
    }

}
<?php

class AppServiceProvider extends ServiceProvider{

  public function boot()
  {
    // ...
    Validator::resolver(function($translator, $data, $rules, $messages)
    {
      return new CustomValidator($translator, $data, $rules, $messages);
    });
    // ...
  }

}

http://laravel.com/docs/4.2/validation#custom-validation-rules

2 likes
RavanScafi's avatar

I was also trying to figure out the best way. I still don't know, but this is how I solved it, at least for now:

I added this to my UpdateUserRequest to validate if the old password is valid before changing it to a new password. By doing this way, I need no other rule in rules() method.

    /**
     * Overrides parent function to append custom validation.
     *
     * @return Validator
     */
    public function getValidatorInstance()
    {
        $validator = parent::getValidatorInstance();

        $validator->after(function () use ($validator)
        {
            if ( ! $this->validateCurrentPassword())
            {
                $validator->errors()->add('current_password', trans('messages.validation.password'));
            }
        });

        return $validator;
    }


    /**
     * Check the password against the database if it's filled.
     *
     * @return bool
     */
    public function validateCurrentPassword()
    {
        if (empty($this->get('current_password')) && empty($this->get('password')))
        {
            return true;
        }

        return \Auth::validate([
            'email' => $this->route()->getParameter('users')->email,
            'password' => $this->get('current_password')
        ]);
    }
1 like
bestmomo's avatar

I think @pmall is on the right way, always good to come back to documentation. So I made it on one of my projects with tags rules that was in FormRequest with a regex. So I made the boot in AppServiceProvider :

class AppServiceProvider extends ServiceProvider {

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Validator::resolver(function($translator, $data, $rules, $messages)
        {
            return new Validation($translator, $data, $rules, $messages);
        });
    }

    ...

}

Created my validation class :

<?php namespace app\Services;

use Illuminate\Validation\Validator;

class Validation extends Validator {

    public function validateTags($attribute, $value, $parameters)
    {
        return preg_match("/^[A-Za-z0-9-éèàù]{1,50}?(,[A-Za-z0-9-éèàù]{1,50})*$/", $value);
    }

} 

Add messages for all languages in validation.php. And set my new rule in FormRequest :

public function rules()
{
    return [
        ...
        'tags' => 'tags'
    ];
}

All is working fine and it's clean.

3 likes
filipzelic's avatar

@bestmomo how would you add new method in Validation class that needs to validate implicit rule? When using closure I can do Validator::extendImplicit('foo', 'FooValidator@validate');

bestmomo's avatar

@filipzelic

I'd try something like that :

<?php namespace app\Services;

use Illuminate\Validation\Validator;

class Validation extends Validator {

    /**
     * My validation rules that imply the field is required.
     *
     * @var array
     */
    protected $myImplicitRules = ['something'];

    /**
     * Determine if a given rule implies the attribute is required.
     *
     * @param  string  $rule
     * @return bool
     */
    protected function isImplicit($rule)
    {
        return in_array($rule, array_merge($this->implicitRules, $this->myImplicitRules));
    }

    public function validateSomething($attribute, $value, $parameters)
    {
        return //
    }

} 
1 like
filipzelic's avatar

Have you tried this code? For some reason validateSomething method is never called

bestmomo's avatar

Did you add the resolver in some provider ?

use App\Services\Validation;
...
Validator::resolver(function($translator, $data, $rules, $messages)
{
    return new Validation($translator, $data, $rules, $messages);
});
1 like
filipzelic's avatar

Sure I did. After I debug the code, found out that in_array function always returned false. First letter of the rule name must be upper case. Thanks very much for the help

pmall's avatar

@filipzelic more straightforward way, just override the $implicitRules attribute in your custom validator but dont forget to keep the classic implicit rules :

  protected $implicitRules = [
    'Required',
    'RequiredWith',
    'RequiredWithAll',
    'RequiredWithout',
    'RequiredWithoutAll',
    'RequiredIf',
    'Accepted',
    'YourValidationName',
    // etc...
  ];

joemottershaw's avatar

@Jeffberry I've created a validation rule for my application using your validators.php file method (saved mine in the Http directory along with routes.php) and adding it to the boot() method of the AppServiceProvider.php file. I was just wondering if you knew how to replace :placeholders to parameter values. For example, I've got the following in my validators.php file:

/**
* Require a certain number of parameters to be present.
*
* @param  int    $count
* @param  array  $parameters
* @param  string  $rule
* @return void
* @throws \InvalidArgumentException
*/

    function requireParameterCount($count, $parameters, $rule) {

        if (count($parameters) < $count):
            throw new InvalidArgumentException("Validation rule $rule requires at least $count parameters.");
        endif;

    }


/**
* Validate the width of an image is less than a maximum value.
*
* @param  string  $attribute
* @param  mixed   $value
* @param  array   $parameters
* @return bool
*/

    $validator->extend('image_width_max', function ($attribute, $value, $parameters) {

        requireParameterCount(1, $parameters, 'image_width_max');

        list($width, $height) = getimagesize($value);

        if ($width > $parameters[0]):
            return false;
        endif;

        return true;

    }, 'The :attribute may not be greater than :image_width_max pixels wide.');

I stole the requireParameterCount() function from the Illuminate\Validation\Validator.php file for anyone wondering where that came from. Everything works fine when I use 'image' => 'required|image|image_width_max:800 for my form request file, except I don't know how to replace :image_width_max with the value declared in the form request file. So in this case it would say: "The image may not be greater than 800 pixels wide."

I've seen loads of replace* functions in the same file I found the requireParameterCount() function but I'm not sure how to get them to work in my own validator.php file. And extend() only accepts the 3 parameters, the validation name, function and error message and the error message parameter doesn't have access to the functions parameters such as $parameter[0].

Any ideas at all?

2 likes
matalina's avatar

I know this is an old topic but I wanted to add. For overriding implicit extensions (which is what I was searching for specifically) in 5.1 there is the addImplicitExtensions method in the Validator class. There is no need to override the array itself:

$this->addImplicitExtensions([
            'require_at_least_one' => 'App\Validators\CustomValidator@validateRequireAtLeastOne']
        );
1 like
Giolf's avatar

@bestmomo yes but i think the best thing it's to create a specific ServiceProvider instead to use the AppServiceProvider ... u could create something like ValidatorServiceProvider.

it's more elegant.

2 likes

Please or to participate in this conversation.