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

[10.x] Adds more implicit validation rules for present based on other fields #48908

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
4 changes: 4 additions & 0 deletions src/Illuminate/Translation/lang/en/validation.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@
'uncompromised' => 'The given :attribute has appeared in a data leak. Please choose a different :attribute.',
],
'present' => 'The :attribute field must be present.',
'present_if' => 'The :attribute field must be present when :other is :value.',
'present_unless' => 'The :attribute field must be present unless :other is :value.',
'present_with' => 'The :attribute field must be present when :values is present.',
'present_with_all' => 'The :attribute field must be present when :values are present.',
'prohibited' => 'The :attribute field is prohibited.',
'prohibited_if' => 'The :attribute field is prohibited when :other is :value.',
'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.',
Expand Down
62 changes: 62 additions & 0 deletions src/Illuminate/Validation/Concerns/ReplacesAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,68 @@ protected function replaceMimes($message, $attribute, $rule, $parameters)
return str_replace(':values', implode(', ', $parameters), $message);
}

/**
* Replace all place-holders for the present_if rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replacePresentIf($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 present_unless rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replacePresentUnless($message, $attribute, $rule, $parameters)
{
return str_replace([':other', ':value'], [
$this->getDisplayableAttribute($parameters[0]),
$this->getDisplayableValue($parameters[0], $parameters[1]),
], $message);
}

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

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

/**
* Replace all place-holders for the required_with rule.
*
Expand Down
80 changes: 80 additions & 0 deletions src/Illuminate/Validation/Concerns/ValidatesAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -1725,6 +1725,86 @@ public function validatePresent($attribute, $value)
return Arr::has($this->data, $attribute);
}

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

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

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

return true;
}

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

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

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

return true;
}

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

if (Arr::hasAny($this->data, $parameters)) {
return $this->validatePresent($attribute, $value, $parameters);
}

return true;
}

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

if (Arr::has($this->data, $parameters)) {
return $this->validatePresent($attribute, $value, $parameters);
}

return true;
}

/**
* Validate that an attribute passes a regular expression check.
*
Expand Down
4 changes: 4 additions & 0 deletions src/Illuminate/Validation/Validator.php
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ class Validator implements ValidatorContract
'MissingWith',
'MissingWithAll',
'Present',
'PresentIf',
'PresentUnless',
'PresentWith',
'PresentWithAll',
'Required',
'RequiredIf',
'RequiredIfAccepted',
Expand Down
128 changes: 128 additions & 0 deletions tests/Validation/ValidationValidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,134 @@ public function testValidatePresent()
$this->assertTrue($v->passes());
}

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

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

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

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

$v = new Validator($trans, ['bar' => 1, 'foo' => [['name' => 'a']]], ['foo.*.id' => 'present_if:bar,1']);
$this->assertFalse($v->passes());
$this->assertSame('The foo.0.id field must be present when bar is 1.', $v->errors()->first('foo.0.id'));

$v = new Validator($trans, ['bar' => 1, 'foo' => [['id' => '', 'name' => 'a']]], ['foo.*.id' => 'present_if:bar,1']);
$this->assertTrue($v->passes());

$v = new Validator($trans, ['bar' => 1, 'foo' => [['id' => null, 'name' => 'a']]], ['foo.*.id' => 'present_if:bar,1']);
$this->assertTrue($v->passes());

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

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

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

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

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

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

$v = new Validator($trans, ['bar' => 2, 'foo' => [['name' => 'a']]], ['foo.*.id' => 'present_unless:bar,1']);
$this->assertFalse($v->passes());
$this->assertSame('The foo.0.id field must be present unless bar is 1.', $v->errors()->first('foo.0.id'));

$v = new Validator($trans, ['bar' => 2, 'foo' => [['id' => '', 'name' => 'a']]], ['foo.*.id' => 'present_unless:bar,1']);
$this->assertTrue($v->passes());

$v = new Validator($trans, ['bar' => 2, 'foo' => [['id' => null, 'name' => 'a']]], ['foo.*.id' => 'present_unless:bar,1']);
$this->assertTrue($v->passes());

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

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

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

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

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

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

$v = new Validator($trans, ['foo' => [['name' => 'a']], 'bar' => 2], ['foo.*.id' => 'present_with:bar']);
$this->assertFalse($v->passes());
$this->assertSame('The foo.0.id field must be present when bar is present.', $v->errors()->first('foo.0.id'));

$v = new Validator($trans, ['foo' => [['id' => '']], 'bar' => 2], ['foo.*.id' => 'present_with:bar']);
$this->assertTrue($v->passes());

$v = new Validator($trans, ['foo' => [['id' => null]], 'bar' => 2], ['foo.*.id' => 'present_with:bar']);
$this->assertTrue($v->passes());

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

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

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

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

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

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

$v = new Validator($trans, ['foo' => [['name' => 'a']], 'bar' => 2, 'baz' => 1], ['foo.*.id' => 'present_with_all:bar,baz']);
$this->assertFalse($v->passes());
$this->assertSame('The foo.0.id field must be present when bar / baz are present.', $v->errors()->first('foo.0.id'));

$v = new Validator($trans, ['foo' => [['id' => '']], 'bar' => 2, 'baz' => 1], ['foo.*.id' => 'present_with_all:bar,baz']);
$this->assertTrue($v->passes());

$v = new Validator($trans, ['foo' => [['id' => null]], 'bar' => 2, 'baz' => 1], ['foo.*.id' => 'present_with_all:bar,baz']);
$this->assertTrue($v->passes());

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

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

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