diff --git a/src/Illuminate/Foundation/Http/FormRequest.php b/src/Illuminate/Foundation/Http/FormRequest.php index 8c2da9699600..e7aa757c8c5b 100644 --- a/src/Illuminate/Foundation/Http/FormRequest.php +++ b/src/Illuminate/Foundation/Http/FormRequest.php @@ -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; @@ -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); + } } diff --git a/src/Illuminate/Validation/MergeRules.php b/src/Illuminate/Validation/MergeRules.php new file mode 100644 index 000000000000..f8f0e9f31d83 --- /dev/null +++ b/src/Illuminate/Validation/MergeRules.php @@ -0,0 +1,58 @@ +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; + } +} diff --git a/src/Illuminate/Validation/Rule.php b/src/Illuminate/Validation/Rule.php index 549a8c6d3bbe..1ff13c06e35e 100644 --- a/src/Illuminate/Validation/Rule.php +++ b/src/Illuminate/Validation/Rule.php @@ -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. * diff --git a/src/Illuminate/Validation/ValidationRuleParser.php b/src/Illuminate/Validation/ValidationRuleParser.php index 293849d220af..c67315531648 100644 --- a/src/Illuminate/Validation/ValidationRuleParser.php +++ b/src/Illuminate/Validation/ValidationRuleParser.php @@ -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]); @@ -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; } @@ -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]; diff --git a/tests/Validation/ValidationRuleParserTest.php b/tests/Validation/ValidationRuleParserTest.php index dab4ec5c342f..92b389f53015 100644 --- a/tests/Validation/ValidationRuleParserTest.php +++ b/tests/Validation/ValidationRuleParserTest.php @@ -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); + } }