Laravel validation: exists with additional column condition - custom validation rule
Asked Answered
C

9

53

Is there is a way of referencing another field when specifying the exists validation rule in Laravel? I want to be able to say that input a must exist in table a, input b must exist in table b AND the value for column x in table b must equal input a.

Best explained by example:

public $rules = array(
    'game_id' => 'required|exists:games,id',
    'team1_id' => 'required|exists:teams,id,game_id,<game_id input value here>',
    'team2_id' => 'required|exists:teams,id,game_id,<game_id input value here>'
);

So with my validation rules I want to be able to make sure that:

  • game_id exists within the games table (id field)
  • team1_id exists within the teams table (id field) and the game_id column (in the teams table) must equal the value of the game_id input.
  • As above for team2_id

So, if in my form, I entered 1 for game_id, I want to be able to ensure that the record within the teams table for both team1_id and team2_id have the value 1 for game_id.

I hope this makes sense.

Thanks

Carny answered 30/9, 2014 at 12:54 Comment(2)
I've been using this format since 5.3 and it works for me. I'm pretty sure it's valid, although I haven't seen it in their docs. $this->mymodel->create_rules['company_id'] = 'required|exists:companies,id,type_id,' . $id; This means the company_id must exists in its own table and the type_id field must be a value of $id.Garble
@Garble thank you so much I've tested your format and it work with me also (Laravel 7) I think that your comment is much better that the marked answerSepta
B
35

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'
);
Baumbaugh answered 30/9, 2014 at 13:45 Comment(2)
This seems to work perfectly, thanks. I'll have to read up on custom validation rules. Where should one place custom validation classes? CheersCarny
You probably have some structure (or should have) for your custom code, so put it there. In Laravel 5 you would create a new service provider, but, that's different story.Baumbaugh
M
53

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.

Masson answered 15/6, 2017 at 18:6 Comment(2)
@AaronHill, how did you do in laravel v5.5?Erhard
@Masson Why do you say it doesn't work on laravel 5.5? It still works in laravel 7.x.. Btw you don't need a closure you can just do ->where('activationCode', $activationCode) directlyAwl
B
35

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'
);
Baumbaugh answered 30/9, 2014 at 13:45 Comment(2)
This seems to work perfectly, thanks. I'll have to read up on custom validation rules. Where should one place custom validation classes? CheersCarny
You probably have some structure (or should have) for your custom code, so put it there. In Laravel 5 you would create a new service provider, but, that's different story.Baumbaugh
C
14

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.

Centimeter answered 30/9, 2014 at 13:23 Comment(0)
T
10

check NULL Condition

'game_id' => 'required|exists:games,id,another_column,NULL',

you can add more condition using (,) column name and value.

Tapis answered 1/6, 2021 at 11:29 Comment(0)
S
5

another way can you try these

public $rules = array(
   'game_id' => 'required|exists:games,id',
   'team1_id' => 'required|exists:teams,id,game_id,'.$request->game_id,
   'team2_id' => 'required|exists:teams,id,game_id,'.$request->game_id
);
Saintmihiel answered 17/2, 2021 at 10:47 Comment(0)
B
2

try this its works for me

'email'=>'required|unique:admintable,Email,'.$adminid.',admin_id',
Beckett answered 20/6, 2017 at 11:10 Comment(4)
Don't use this if the admin id comes from the request... This is susceptible to variable injection and somebody might trick your validation with some pipes and comas.. Or worse even lead to remote code executionAwl
hey @Awl call regex to just keep numbers and remove other characters for adminid before call for validation ;)Beckett
Or just use something of the like.. 'email' => ['required', 'unique' => ['admintable', 'Email', $adminid, 'admin_id']]Awl
In the instance that admin_id comes from the request you would likely have a validator for it anyway 'admin_id' => 'required|integer|exists:admins,id' so the argument is kind of moot.Foothold
O
1

If anybody else is still finding a better solution with Laravel 6 or higher you can simply add the field name with the "exist" rule, something like this.

$rules = $request->validate([
   'game_id' => 'required|exists:games,id',
   'team1_id' => 'required|exists:teams,id',
   'team2_id' => 'required|exists:teams,id'
]);

and to check if game_id is present on the "teams" table or not you can use IN: rule for that.

something like that:

$rules = $request->validate([
   'game_id' => 'required|exists:games,id',
   'team1_id' => 'required|exists:teams,id|in:games',
   'team2_id' => 'required|exists:teams,id|in:games',
]);

I hope that will help someone.

Osculation answered 11/8, 2020 at 5:56 Comment(0)
B
0

you could get some value in the Rules method like $this->value and then use rules like same for string against that value

Businesslike answered 5/6 at 16:50 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Cruz
B
-1
$pdf = $request->file('resume')->getClientOriginalName();
 
   if (file_exists(public_path('PDF/'.$pdf))) {
      
      return back()->withErrors(["resume" => "This File is Already Exits"])->withInput();
        
    }else{
        $request->resume->move(public_path('PDF'), $pdf);
    }

You have to add Like this Error message, This is Working for me

Bridgettbridgette answered 12/10, 2022 at 6:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.