Laravel Custom Validation: one of the fields required, but not both

Laravel Validation mechanism has a lot of rules provided - a field can be required, integer, IP address, timezone etc. But sometimes there is a need for a special rule which is not in that list. One example of this is when you have two fields and you need only one of them to be filled. One, or another, but NOT BOTH. Laravel doesn't have a rule for that, so let's create one!

Basically, we need to extend Validator class. Let's start with a simple example.

1. Simple Validation - extending Validator directly in AppServiceProvider

There is a file that comes by default with Laravel - app\Providers\AppServiceProvider.php. In it's main method boot() we add our new Validation rule. Let's say we need a field to start with "+44" (phone number with UK prefix):

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Validator;

class AppServiceProvider extends ServiceProvider
{

    public function boot()
    {
        Validator::extend('uk_phone', function($attribute, $value, $parameters) {
            return substr($value, 0, 3) == '+44';
        });
    }

}

Don't forget to add use Validator to be able to, well, use it within the class.

So, what do we have here:

  • uk_phone - a rule name which we will actually use later in our Validation Request class;
  • Method should return TRUE if validation rule passed, in case of failure returns FALSE;
  • In this case we're using only onf of method parameters $value, parameter $attribute means name of the field to be validated, and $parameters are used for more complicated rules, like in default Laravel we have min:X or same:field.

Ok, so let's actually use our rule. We have a form in Blade:

@if ($errors->any())
    <ul>{!! implode('', $errors->all('<li style="color:red">:message</li>')) !!}</ul>
@endif

{!! Form::open(['url' => url('form_store')]) !!}
{!! Form::text('phone', old('phone'), ['placeholder' => 'Enter UK phone number']) !!}
{!! Form::submit('Save') !!}

Visual result:

0724_laravel_custom_validation_01

Now, let's say that URL /form_store points to Controller method like this:

public function postForm(CreateUserRequest $request) {
    return 'Success!';
}

Basically, we will catch the error in CreateUserRequest class, or return success text.

Here's how it looks in our app\Http\Requests\CreateUserRequest.php:

namespace App\Http\Requests;

class CreateUserRequest extends Request {

    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'phone' => 'required|uk_phone',
        ];
    }
}

As you can see, we've added our rule uk_phone as a second rule to the field phone - in addition to being required, it will also check out +44 prefix. Let's see how the error looks if we don't follow that rule:

0724_laravel_custom_validation_02

Yay, it's working! We caught an error! But wait, why error text is so unpretty? Well, Laravel doesn't know what our rule actually means, so it has no idea what message to show.
We have to specify it ourselves - luckily, it's very easy. We go to resources\lang\en\validation.php and add our message to custom array almost at the bottom:

'custom' => [
    'phone' => [
        'uk_phone' => 'Please enter UK phone number which starts with +44',
    ],
],

You can also read more about custom validation messages and their translation in this article I've written recently.

So, we're done with a simple example. Now, let's return to our main goal - validation of two fields related to each other.

2. More complex example - our own Validator class

Let's expand our form to two fields - phone and email, and we want ONLY ONE of them to be filled - but not both.

{!! Form::open(['url' => url('form_store')]) !!}
{!! Form::text('phone', old('phone'), ['placeholder' => 'Phone number']) !!}
{!! Form::email('email', old('email'), ['placeholder' => 'Email']) !!}
{!! Form::submit('Save') !!}
0724_laravel_custom_validation_03


To do that, we are gonna take the code out of AppServiceProvider into a separate class. Let's create it in app\Providers\MyValidator.php:

namespace App\Providers;

use Illuminate\Validation\Validator;

class MyValidator extends Validator {

    public function validateEmptyWith($attribute, $value, $parameters)
    {
        return ($value != '' && $this->getValue($parameters[0]) != '') ? false : true;
    }

}

So, we have a new rule called empty_with (notice CamelCase in function name validateEmptyWith) which will return false if both fields are filled in.
The main field is defined by $value, and the second one to compare with is captured by method $this->getValue($parameters[0]). Remember parameters I was mentioning earlier? So this is how it looks in Request class:

public function rules()
{
    return [
        'phone' => 'empty_with:email',
    ];
}

And this is how it looks:

0724_laravel_custom_validation_04

Finally, of course, we need to add validation error text to the resources\lang\en\validation.php:

'custom' => [
    'phone' => [
        'empty_with' => 'You have to fill in one of the fields, but not both',
    ],
],

So here we go, this is how you create custom Validation rules with Laravel! Pretty easy, right?

No comments or questions yet...

Like our articles?

Become a Premium Member for $129/year or $29/month
What else you will get:
  • 59 courses (1056 lessons, total 44 h 09 min)
  • 78 long-form tutorials (one new every week)
  • access to project repositories
  • access to private Discord

Recent Premium Tutorials