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?

Like our articles?
Check out our Laravel online courses!

13 COMMENTS

    • Hi Pavan.
      At first I thought so too, just tested it now. Your suggested rule doesn’t check the situation if BOTH fields have values – in this case it will validate as successful, and this is exactly what I want to check in this article.

      Thanks for raising the suggestion anyway!

  1. How this validation be see if phone OR email is need to added?

    – If email added – okey – true
    – if phone added – okey – true
    – if phone or email is added – true
    – if phone and email is NOT added – false – return with error ?

    What you are thing for this?

    Thx!

  2. Great tut. thanks. I’ve signed up.

    In my custom validators I’ve added my messages as a parameter rather than in the messages field #HTH

    “`
    Validator::extend(‘alpha_num_dash_spaces’, function($attribute, $value, $parameters)
    {
    return preg_match(‘/(^[A-Za-z0-9- ]+$)+/’, $value);

    }, ‘Please use text, numbers, dashes or spaces’);
    “`

  3. I think this is not necessary solution for the topic’s title.
    Because one of fields is REQUIRED when we will miss both, we also will have ‘Success”. To avoid this we also must set rule for email field:

    ’email’ => ‘required_without:phone’

  4. I agree with @cijic, The title is misleading as “requiring” a field implies it cannot be empty, whilst you are solving a problem where you specifically want only one of the fields to be present and the other to be absent as in a XOR gate.
    You can play with the require_if condition from laravel in this case.

    Nevertheless I appreciate your solution as it illustrates the way of thinking, however I am also considering an alternative by using a closure to extend the validator instead.

    public function extendValidation(ValidatorInterface $validator)
    {
    $validator->extend(’empty_with’, function($attribute, $value, $parameters) {
    //implement
    });
    }

  5. Hello I like this tut, but as the methods 2 works fine here, I don’t see why I am unable to verify a bit changed code from it like this:

    public function validateEmptyWith($attribute, $value, $parameters)
    {
    return ($value != ” || $this->getValue($parameters[0]) != ”) ;
    }

    I know this looks much as required_with, but what I want is to get only one custom message on $value field instead of 2 on both of $value and $parameters fields. Here is the message:

    “‘custom’ => [
    ‘value_a’ => [
    ’empty_with’ => ‘You either have to set value_a or value_b’,
    ],
    ]”

    with this rules:
    rules=>[
    value_a =>empty_with:value_b,
    ]

  6. Hi, I did as you said in the post, but i get an error that says

    “Method Illuminate\Validation\Validator::validateEmptyWith does not exist”

    Is there something else o gotta do? like register the validator or something? like we do for example with middlewares.

LEAVE A REPLY

Please enter your comment!
Please enter your name here