Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[9.x] Add "missing" validation rules #45717

Merged
merged 1 commit into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/Illuminate/Validation/Concerns/ReplacesAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,66 @@ protected function replaceMaxDigits($message, $attribute, $rule, $parameters)
return str_replace(':max', $parameters[0], $message);
}

/**
* Replace all place-holders for the missing_if rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceMissingIf($message, $attribute, $rule, $parameters)
{
$parameters[1] = $this->getDisplayableValue($parameters[0], Arr::get($this->data, $parameters[0]));

$parameters[0] = $this->getDisplayableAttribute($parameters[0]);

return str_replace([':other', ':value'], $parameters, $message);
}

/**
* Replace all place-holders for the missing_unless rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceMissingUnless($message, $attribute, $rule, $parameters)
{
return $this->replaceMissingIf($message, $attribute, $rule, $parameters);
}

/**
* Replace all place-holders for the missing_with rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceMissingWith($message, $attribute, $rule, $parameters)
{
return str_replace(':values', implode(' / ', $this->getAttributeList($parameters)), $message);
}

/**
* Replace all place-holders for the missing_with_all rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceMissingWithAll($message, $attribute, $rule, $parameters)
{
return $this->replaceMissingWith($message, $attribute, $rule, $parameters);
}

/**
* Replace all place-holders for the multiple_of rule.
*
Expand Down
85 changes: 85 additions & 0 deletions src/Illuminate/Validation/Concerns/ValidatesAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -1504,6 +1504,91 @@ public function validateMinDigits($attribute, $value, $parameters)
return ! preg_match('/[^0-9]/', $value) && $length >= $parameters[0];
}

/**
* Validate that an attribute is missing.
*
* @param string $attribute
* @param mixed $value
* @param array<int, int|string> $parameters
* @return bool
*/
public function validateMissing($attribute, $value, $parameters)
{
return ! Arr::has($this->data, $attribute);
}

/**
* Validate that an attribute is missing when another attribute has a given value.
*
* @param string $attribute
* @param mixed $value
* @param array<int, int|string> $parameters
* @return bool
*/
public function validateMissingIf($attribute, $value, $parameters)
{
$this->requireParameterCount(2, $parameters, 'missing_if');

[$values, $other] = $this->parseDependentRuleParameters($parameters);

if (in_array($other, $values, is_bool($other) || is_null($other))) {
return $this->validateMissing($attribute, $value, $parameters);
}

return true;
}

/**
* Validate that an attribute is missing unless another attribute has a given value.
*
* @param string $attribute
* @param mixed $value
* @param array<int, int|string> $parameters
* @return bool
*/
public function validateMissingUnless($attribute, $value, $parameters)
{
$this->requireParameterCount(2, $parameters, 'missing_unless');

[$values, $other] = $this->parseDependentRuleParameters($parameters);

if (! in_array($other, $values, is_bool($other) || is_null($other))) {
return $this->validateMissing($attribute, $value, $parameters);
}

return true;
}

/**
* Validate that an attribute is missing when any given attribute is present.
*
* @param string $attribute
* @param mixed $value
* @param array<int, int|string> $parameters
* @return bool
*/
public function validateMissingWith($attribute, $value, $parameters)
{
$this->requireParameterCount(1, $parameters, 'missing_with');

return ! Arr::hasAny($this->data, $parameters);
}

/**
* Validate that an attribute is missing when all given attributes are present.
*
* @param string $attribute
* @param mixed $value
* @param array<int, int|string> $parameters
* @return bool
*/
public function validateMissingWithAll($attribute, $value, $parameters)
{
$this->requireParameterCount(1, $parameters, 'missing_with');

return ! Arr::has($this->data, $parameters);
}

/**
* Validate the value of an attribute is a multiple of a given value.
*
Expand Down
5 changes: 5 additions & 0 deletions src/Illuminate/Validation/Validator.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ class Validator implements ValidatorContract
'Declined',
'DeclinedIf',
'Filled',
'Missing',
'MissingIf',
'MissingUnless',
'MissingWith',
'MissingWithAll',
'Present',
'Required',
'RequiredIf',
Expand Down
195 changes: 195 additions & 0 deletions tests/Validation/ValidationValidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2234,6 +2234,201 @@ public function testValidateDeclined()
$this->assertTrue($v->passes());
}

public function testValidateMissing()
{
$trans = $this->getIlluminateArrayTranslator();
$trans->addLines(['validation.missing' => 'The :attribute field must be missing.'], 'en');

$v = new Validator($trans, ['foo' => 'yes'], ['foo' => 'missing']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => ''], ['foo' => 'missing']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => ' '], ['foo' => 'missing']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => null], ['foo' => 'missing']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => []], ['foo' => 'missing']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => new class implements Countable
{
public function count(): int
{
return 0;
}
}, ], ['foo' => 'missing']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing.', $v->errors()->first('foo'));

$v = new Validator($trans, ['bar' => 'bar'], ['foo' => 'missing']);
$this->assertTrue($v->passes());
}

public function testValidateMissingIf()
{
$trans = $this->getIlluminateArrayTranslator();
$trans->addLines(['validation.missing_if' => 'The :attribute field must be missing when :other is :value.'], 'en');

$v = new Validator($trans, ['foo' => 'yes', 'bar' => '1'], ['foo' => 'missing_if:bar,1']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing when bar is 1.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => '', 'bar' => '1'], ['foo' => 'missing_if:bar,1']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing when bar is 1.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => ' ', 'bar' => '1'], ['foo' => 'missing_if:bar,1']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing when bar is 1.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => null, 'bar' => '1'], ['foo' => 'missing_if:bar,1']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing when bar is 1.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => [], 'bar' => '1'], ['foo' => 'missing_if:bar,1']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing when bar is 1.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => new class implements Countable
{
public function count(): int
{
return 0;
}
}, 'bar' => '1', ], ['foo' => 'missing_if:bar,1']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing when bar is 1.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => 'foo', 'bar' => '2'], ['foo' => 'missing_if:bar,1']);
$this->assertTrue($v->passes());
}

public function testValidateMissingUnless()
{
$trans = $this->getIlluminateArrayTranslator();
$trans->addLines(['validation.missing_unless' => 'The :attribute field must be missing unless :other is :value.'], 'en');

$v = new Validator($trans, ['foo' => 'yes', 'bar' => '2'], ['foo' => 'missing_unless:bar,1']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing unless bar is 2.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => '', 'bar' => '2'], ['foo' => 'missing_unless:bar,1']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing unless bar is 2.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => ' ', 'bar' => '2'], ['foo' => 'missing_unless:bar,1']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing unless bar is 2.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => null, 'bar' => '2'], ['foo' => 'missing_unless:bar,1']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing unless bar is 2.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => [], 'bar' => '2'], ['foo' => 'missing_unless:bar,1']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing unless bar is 2.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => new class implements Countable
{
public function count(): int
{
return 0;
}
}, 'bar' => '2', ], ['foo' => 'missing_unless:bar,1']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing unless bar is 2.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => 'foo', 'bar' => '1'], ['foo' => 'missing_unless:bar,1']);
$this->assertTrue($v->passes());
}

public function testValidateMissingWith()
{
$trans = $this->getIlluminateArrayTranslator();
$trans->addLines(['validation.missing_with' => 'The :attribute field must be missing when :values is present.'], 'en');

$v = new Validator($trans, ['foo' => 'yes', 'bar' => '2'], ['foo' => 'missing_with:baz,bar']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing when baz / bar is present.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => '', 'bar' => '2'], ['foo' => 'missing_with:baz,bar']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing when baz / bar is present.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => ' ', 'bar' => '2'], ['foo' => 'missing_with:baz,bar']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing when baz / bar is present.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => null, 'bar' => '2'], ['foo' => 'missing_with:baz,bar']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing when baz / bar is present.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => [], 'bar' => '2'], ['foo' => 'missing_with:baz,bar']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing when baz / bar is present.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => new class implements Countable
{
public function count(): int
{
return 0;
}
}, 'bar' => '2', ], ['foo' => 'missing_with:baz,bar']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing when baz / bar is present.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => 'foo', 'qux' => '1'], ['foo' => 'missing_with:baz,bar']);
$this->assertTrue($v->passes());
}

public function testValidateMissingWithAll()
{
$trans = $this->getIlluminateArrayTranslator();
$trans->addLines(['validation.missing_with_all' => 'The :attribute field must be missing when :values are present.'], 'en');

$v = new Validator($trans, ['foo' => 'yes', 'bar' => '2', 'baz' => '2'], ['foo' => 'missing_with_all:baz,bar']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing when baz / bar are present.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => '', 'bar' => '2', 'baz' => '2'], ['foo' => 'missing_with_all:baz,bar']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing when baz / bar are present.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => ' ', 'bar' => '2', 'baz' => '2'], ['foo' => 'missing_with_all:baz,bar']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing when baz / bar are present.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => null, 'bar' => '2', 'baz' => '2'], ['foo' => 'missing_with_all:baz,bar']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing when baz / bar are present.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => [], 'bar' => '2', 'baz' => '2'], ['foo' => 'missing_with_all:baz,bar']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing when baz / bar are present.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => new class implements Countable
{
public function count(): int
{
return 0;
}
}, 'bar' => '2', 'baz' => '2', ], ['foo' => 'missing_with_all:baz,bar']);
$this->assertFalse($v->passes());
$this->assertSame('The foo field must be missing when baz / bar are present.', $v->errors()->first('foo'));

$v = new Validator($trans, ['foo' => [], 'bar' => '2', 'qux' => '2'], ['foo' => 'missing_with_all:baz,bar']);
$this->assertTrue($v->passes());
}

public function testValidateDeclinedIf()
{
$trans = $this->getIlluminateArrayTranslator();
Expand Down