Skip to content

Commit

Permalink
Add ability to nested validation rules
Browse files Browse the repository at this point in the history
  • Loading branch information
stevebauman committed Jan 19, 2022
1 parent 485da0f commit 9d19d1e
Show file tree
Hide file tree
Showing 5 changed files with 490 additions and 18 deletions.
55 changes: 55 additions & 0 deletions src/Illuminate/Validation/NestedRules.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace Illuminate\Validation;

use Illuminate\Support\Arr;

class NestedRules
{
/**
* The callback to execute.
*
* @var callable
*/
protected $callback;

/**
* Create a new nested rule instance.
*
* @param callable $callback
* @return void
*/
public function __construct(callable $callback)
{
$this->callback = $callback;
}

/**
* Compile the callback into an array of rules.
*
* @param string $attribute
* @param mixed $value
* @param mixed $data
* @return \stdClass
*/
public function compile($attribute, $value, $data = null)
{
$rules = call_user_func($this->callback, $attribute, $value, $data);

$parser = new ValidationRuleParser(
Arr::undot(Arr::wrap($data))
);

if (is_array($rules) && Arr::isAssoc($rules)) {
$nested = [];

foreach ($rules as $key => $rule) {
$nested[$attribute.'.'.$key] = $rule;
}

return $parser->explode($nested);
}

return $parser->explode([$attribute => $rules]);
}
}
13 changes: 12 additions & 1 deletion src/Illuminate/Validation/Rule.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ public static function when($condition, $rules, $defaultRules = [])
return new ConditionalRules($condition, $rules, $defaultRules);
}

/**
* Create a new nested rule set.
*
* @param callable $callback
* @return \Illuminate\Validation\NestedRules
*/
public static function nested($callback)
{
return new NestedRules($callback);
}

/**
* Get a dimensions constraint builder instance.
*
Expand Down Expand Up @@ -103,4 +114,4 @@ public static function unique($table, $column = 'NULL')
{
return new Unique($table, $column);
}
}
}
50 changes: 38 additions & 12 deletions src/Illuminate/Validation/ValidationRuleParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ protected function explodeRules($rules)

unset($rules[$key]);
} else {
$rules[$key] = $this->explodeExplicitRule($rule);
$rules[$key] = $this->explodeExplicitRule($rule, $key);
}
}

Expand All @@ -79,26 +79,34 @@ protected function explodeRules($rules)
* Explode the explicit rule into an array if necessary.
*
* @param mixed $rule
* @param string $attribute
* @return array
*/
protected function explodeExplicitRule($rule)
protected function explodeExplicitRule($rule, $attribute)
{
if (is_string($rule)) {
return explode('|', $rule);
} elseif (is_object($rule)) {
return [$this->prepareRule($rule)];
return Arr::wrap($this->prepareRule($rule, $attribute));
}

return array_map([$this, 'prepareRule'], $rule);
$attributes = array_fill(
array_key_first($rule), count($rule), $attribute
);

return array_map(
[$this, 'prepareRule'], $rule, $attributes
);
}

/**
* Prepare the given rule for the Validator.
*
* @param mixed $rule
* @param string $attribute
* @return mixed
*/
protected function prepareRule($rule)
protected function prepareRule($rule, $attribute)
{
if ($rule instanceof Closure) {
$rule = new ClosureValidationRule($rule);
Expand All @@ -111,6 +119,12 @@ protected function prepareRule($rule)
return $rule;
}

if ($rule instanceof NestedRules) {
return $rule->compile(
$attribute, $this->data[$attribute] ?? null, Arr::dot($this->data)
)->rules[$attribute];
}

return (string) $rule;
}

Expand All @@ -130,10 +144,22 @@ protected function explodeWildcardRules($results, $attribute, $rules)

foreach ($data as $key => $value) {
if (Str::startsWith($key, $attribute) || (bool) preg_match('/^'.$pattern.'\z/', $key)) {
foreach ((array) $rules as $rule) {
$this->implicitAttributes[$attribute][] = $key;

$results = $this->mergeRules($results, $key, $rule);
foreach (Arr::flatten((array) $rules) as $rule) {
if ($rule instanceof NestedRules) {
$compiled = $rule->compile($key, $value, $data);

$this->implicitAttributes = array_merge_recursive(
$compiled->implicitAttributes,
$this->implicitAttributes,
[$attribute => [$key]]
);

$results = $this->mergeRules($results, $compiled->rules);
} else {
$this->implicitAttributes[$attribute][] = $key;

$results = $this->mergeRules($results, $key, $rule);
}
}
}
}
Expand Down Expand Up @@ -177,7 +203,7 @@ protected function mergeRulesForAttribute($results, $attribute, $rules)
$merge = head($this->explodeRules([$rules]));

$results[$attribute] = array_merge(
isset($results[$attribute]) ? $this->explodeExplicitRule($results[$attribute]) : [], $merge
isset($results[$attribute]) ? $this->explodeExplicitRule($results[$attribute], $attribute) : [], $merge
);

return $results;
Expand All @@ -191,7 +217,7 @@ protected function mergeRulesForAttribute($results, $attribute, $rules)
*/
public static function parse($rule)
{
if ($rule instanceof RuleContract) {
if ($rule instanceof RuleContract || $rule instanceof NestedRules) {
return [$rule, []];
}

Expand Down Expand Up @@ -302,4 +328,4 @@ public static function filterConditionalRules($rules, array $data = [])
})->filter()->flatten(1)->values()->all()];
})->all();
}
}
}
Loading

0 comments on commit 9d19d1e

Please sign in to comment.