From 939b712f22690452de3862f99d994b11391d4fa6 Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Thu, 19 Jan 2023 16:33:32 +1100 Subject: [PATCH] Add missing validation rules. --- .../Concerns/ReplacesAttributes.php | 60 ++++++ .../Concerns/ValidatesAttributes.php | 85 ++++++++ src/Illuminate/Validation/Validator.php | 5 + tests/Validation/ValidationValidatorTest.php | 195 ++++++++++++++++++ 4 files changed, 345 insertions(+) diff --git a/src/Illuminate/Validation/Concerns/ReplacesAttributes.php b/src/Illuminate/Validation/Concerns/ReplacesAttributes.php index b585839e2eee..cb37e462980b 100644 --- a/src/Illuminate/Validation/Concerns/ReplacesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ReplacesAttributes.php @@ -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 $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 $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 $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 $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. * diff --git a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php index 03d4d391dbfd..0df035fcb27e 100644 --- a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php @@ -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 $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 $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 $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 $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 $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. * diff --git a/src/Illuminate/Validation/Validator.php b/src/Illuminate/Validation/Validator.php index 8a552fae6246..d0655031dd55 100755 --- a/src/Illuminate/Validation/Validator.php +++ b/src/Illuminate/Validation/Validator.php @@ -207,6 +207,11 @@ class Validator implements ValidatorContract 'Declined', 'DeclinedIf', 'Filled', + 'Missing', + 'MissingIf', + 'MissingUnless', + 'MissingWith', + 'MissingWithAll', 'Present', 'Required', 'RequiredIf', diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index 80cf2a4a9e5a..f7fbf9d9a99e 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -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();