diff --git a/src/Rule.php b/src/Rule.php index aaa197cb5..81d3652e9 100644 --- a/src/Rule.php +++ b/src/Rule.php @@ -17,6 +17,11 @@ abstract class Rule private bool $skipOnEmpty = false; private bool $skipOnError = true; + /** + * @var callable|null + */ + private $when = null; + /** * Validates the value * @@ -31,7 +36,10 @@ final public function validate($value, DataSetInterface $dataSet = null, bool $p return new Result(); } - if ($this->skipOnError && $previousRulesErrored) { + if ( + ($this->skipOnError && $previousRulesErrored) || + (is_callable($this->when) && !call_user_func($this->when, $value, $dataSet)) + ) { return new Result(); } @@ -82,6 +90,32 @@ public function translateMessage(string $message, array $arguments = []): string ); } + /** + * Add a PHP callable whose return value determines whether this rule should be applied. + * By default rule will be always applied. + * + * The signature of the callable should be `function ($value, DataSetInterface $dataSet): bool`, where $value and $dataSet + * refer to the value validated and the data set in which context it is validated. The callable should return + * a boolean value. + * + * The following example will enable the validator only when the country currently selected is USA: + * + * ```php + * function ($value, DataSetInterface $dataSet) { + return $dataSet->getAttributeValue('country') === Country::USA; + } + * ``` + * + * @param callable $callback + * @return $this + */ + public function when(callable $callback): self + { + $new = clone $this; + $new->when = $callback; + return $new; + } + public function skipOnError(bool $value): self { $new = clone $this; diff --git a/tests/RulesTest.php b/tests/RulesTest.php index df2b437c3..4afc38ee1 100644 --- a/tests/RulesTest.php +++ b/tests/RulesTest.php @@ -56,6 +56,22 @@ static function ($value): Result { $this->assertCount(1, $result->getErrors()); } + public function testWhenValidate() + { + $rules = new Rules( + [ + (new Number())->min(10), + (new Number())->min(10)->when(fn() => false)->skipOnError(false), + (new Number())->min(10)->skipOnError(false) + ] + ); + + $result = $rules->validate(1); + + $this->assertFalse($result->isValid()); + $this->assertCount(2, $result->getErrors()); + } + public function testSkipOnError() { $rules = new Rules(