Skip to content
Closed
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
25 changes: 25 additions & 0 deletions src/Illuminate/Foundation/Http/FormRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidatesWhenResolvedTrait;
use Illuminate\Validation\ValidationException;

Expand Down Expand Up @@ -253,4 +254,28 @@ public function setContainer(Container $container)

return $this;
}

/**
* Merge rules based on a given condition.
*
* @param callable|bool $condition
* @param callable|array $rules
* @return \Illuminate\Validation\MergeRules
*/
protected function mergeWhen($condition, $rules)
{
return Rule::mergeWhen($condition, $rules);
}

/**
* Create a new conditional rule set.
*
* @param callable|bool $condition
* @param array|string $rules
* @return \Illuminate\Validation\ConditionalRules
*/
protected function when($condition, $rules)
{
return Rule::when($condition, $rules);
}
}
58 changes: 58 additions & 0 deletions src/Illuminate/Validation/MergeRules.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

namespace Illuminate\Validation;

class MergeRules
{
/**
* The boolean condition indicating if the rules should be merged.
*
* @var callable|bool
*/
protected $condition;

/**
* The rules to be added to be merged.
*
* @var callable|array
*/
protected $rules;

/**
* Create a new conditional rules instance.
*
* @param callable|bool $condition
* @param callable|array $rules
* @return void
*/
public function __construct($condition, $rules)
{
$this->condition = $condition;
$this->rules = $rules;
}

/**
* Determine if the conditional rules should be added.
*
* @param array $data
* @return bool
*/
public function passes(array $data = [])
{
return is_callable($this->condition)
? call_user_func($this->condition, $data)
: $this->condition;
}

/**
* Get the rules.
*
* @return array
*/
public function rules()
{
return is_callable($this->rules)
? call_user_func($this->rules)
: $this->rules;
}
}
12 changes: 12 additions & 0 deletions src/Illuminate/Validation/Rule.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ public static function when($condition, $rules)
return new ConditionalRules($condition, $rules);
}

/**
* Merge rules based on a given condition.
*
* @param callable|bool $condition
* @param callable|array $rules
* @return MergeRules
*/
public static function mergeWhen($condition, $rules)
{
return new MergeRules($condition, $rules);
}

/**
* Get a dimensions constraint builder instance.
*
Expand Down
18 changes: 17 additions & 1 deletion src/Illuminate/Validation/ValidationRuleParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,14 @@ public function explode($rules)
*/
protected function explodeRules($rules)
{
$mergeable = [];

foreach ($rules as $key => $rule) {
if (Str::contains($key, '*')) {
if ($rule instanceof MergeRules) {
$mergeable[$key] = $rule;

unset($rules[$key]);
} elseif (Str::contains($key, '*')) {
$rules = $this->explodeWildcardRules($rules, $key, [$rule]);

unset($rules[$key]);
Expand All @@ -72,6 +78,12 @@ protected function explodeRules($rules)
}
}

foreach ($mergeable as $rule) {
$rules = array_merge_recursive($rules, $this->explodeRules(
ValidationRuleParser::filterConditionalRules($rule->rules(), $this->data)
));
}

return $rules;
}

Expand Down Expand Up @@ -285,6 +297,10 @@ protected static function normalizeRule($rule)
public static function filterConditionalRules($rules, array $data = [])
{
return collect($rules)->mapWithKeys(function ($attributeRules, $attribute) use ($data) {
if ($attributeRules instanceof MergeRules) {
return [$attribute => $attributeRules->passes($data) ? $attributeRules : null];
}

if (! is_array($attributeRules) &&
! $attributeRules instanceof ConditionalRules) {
return [$attribute => $attributeRules];
Expand Down
56 changes: 56 additions & 0 deletions tests/Validation/ValidationRuleParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,60 @@ public function test_conditional_rules_are_properly_expanded_and_filtered()
'city' => ['required', 'min:2'],
], $rules);
}

public function testMergeRules()
{
$rules = [
'name' => 'required|string',
'city' => 'required|min:2',
'airports.*' => ['required', 'string'],

Rule::mergeWhen(true, [
'city' => 'string',
'country' => 'required|string|size:2',

'airports.*' => [
Rule::when(function ($input) {
return $input['country'] === 'US';
}, 'in:NYC'),
Rule::when(function ($input) {
return $input['country'] === 'NL';
}, 'in:AMS'),
],

Rule::mergeWhen(function ($input) {
return $input['country'] === 'US';
}, [
'state' => 'required|size:2',
]),

Rule::mergeWhen(function ($input) {
return $input['country'] === 'NL';
}, [
'province' => 'required|size:2',
]),
]),

Rule::mergeWhen(false, [
'notincluded' => ['required', 'size:2'],
]),
];

$data = [
'country' => 'US',
'airports' => ['NYC', 'AMS'],
];

$response = (new ValidationRuleParser($data))
->explode(ValidationRuleParser::filterConditionalRules($rules, $data));

$this->assertEquals([
'name' => ['required', 'string'],
'city' => ['required', 'min:2', 'string'],
'country' => ['required', 'string', 'size:2'],
'state' => ['required', 'size:2'],
'airports.0' => ['required', 'string', 'in:NYC'],
'airports.1' => ['required', 'string', 'in:NYC'],
], $response->rules);
}
}