Laravel validation: exists with additional column condition - custom validation rule

From Laravel 5.3+ you can add a custom where clause to the exists and unique rules.

Here is my scenario: I have an email verification table and I want to ensure that a passed machine code and activation code exist on the same row.

Be sure to use Illuminate\Validation\Rule;

$activationCode = $request->activation_code;                                   

$rules = [                                                                     
    'mc' => [                                                                  
        'required',                                                            
        Rule::exists('email_verifications', 'machineCode')                     
        ->where('activationCode', $activationCode),                                                                    
    ],                                                                         
    'activation_code' => 'required|integer|min:5',                             
    'operating_system' => 'required|alpha_num|max:45'                          
];

The first argument in the exists method is the table and the second is the custom column name I'm using for the 'mc' field. I pass the second column I want to check in a where clause.

This is pretty handy, because now I no longer need a custom Validation rule.


You want a custom validation rule, and I would create a separate class for this. But for brevity here's pretty much the same using inline closure:

// give it meaningful name, I'll go with game_fixture as an example
Validator::extend('game_fixture', function ($attribute, $value, $parameters, $validator) 
{
    if (count($parameters) < 4)
    {
        throw new \InvalidArgumentException("Validation rule game_fixture requires 4 parameters.");
    }

    $input    = $validator->getData();
    $verifier = $validator->getPresenceVerifier();

    $collection = $parameters[0];
    $column     = $parameters[1];
    $extra      = [$parameters[2] => array_get($input, $parameters[3])];

    $count = $verifier->getMultiCount($collection, $column, (array) $value, $extra);

    return $count >= 1;
});

Then use simply this:

$rules = array(
    'game_id' => 'required|exists:games,id',

    // last parameter here refers to the 'game_id' value passed to the validator
    'team1_id' => 'required|game_fixture:teams,id,game_id,game_id',
    'team2_id' => 'required|game_fixture:teams,id,game_id,game_id'
);

As your rules are model property you need to make some change for them before running validator.

You could change your rules to:

public $rules = array(
    'game_id' => 'required|exists:games,id',
    'team1_id' => 'required|exists:teams,id,game_id,{$game_id}',
    'team2_id' => 'required|exists:teams,id,game_id,{$game_id}'
);

and now you will need to use loop to insert correct value instead of {$game_id} string.

I can show you how I did it in my case for editing rule:

public function validate($data, $translation, $editId = null)
{
    $rules = $this->rules;

    $rules = array_intersect_key($rules, $data);

    foreach ($rules as $k => $v) {
        $rules[$k] = str_replace('{,id}',is_null($editId) ? '' : ','.$editId , $v);
    }

    $v = Validator::make($data, $rules, $translation);

    if ($v->fails())
    {
        $this->errors = $v->errors();
        return false;
    }

    return true;
}

You can do the same in your case changing {$game_id} into $data['game_id'] (in my case I changed {,id} into ,$editId

EDIT

Of course If you didn't have $rules set as property you could simply do:

$rules = array(
    'game_id' => 'required|exists:games,id',
    'team1_id' => 'required|exists:teams,id,game_id,'.$data['game_id'],
    'team2_id' => 'required|exists:teams,id,game_id,'.$data['game_id']
);

in place where you have your data set.