diff --git a/src/ChainedValidatorInterface.php b/src/ChainedValidatorInterface.php index 444a018..f5f1857 100644 --- a/src/ChainedValidatorInterface.php +++ b/src/ChainedValidatorInterface.php @@ -15,7 +15,9 @@ public function validate(mixed $value): bool; // --- Rules --- - public function notBlank(array $options = []): ChainedValidatorInterface; + public function all(array $constraints, array $options = null): ChainedValidatorInterface; + + public function choice(array $constraints, bool $multiple = false, ?int $minConstraint = null, ?int $maxConstraint = null, array $options = []): ChainedValidatorInterface; public function greaterThan(mixed $constraint, array $options = []): ChainedValidatorInterface; @@ -25,7 +27,7 @@ public function lessThan(mixed $constraint, array $options = []): ChainedValidat public function lessThanOrEqual(mixed $constraint, array $options = []): ChainedValidatorInterface; - public function choice(array $constraints, bool $multiple = false, ?int $minConstraint = null, ?int $maxConstraint = null, array $options = []): ChainedValidatorInterface; + public function notBlank(array $options = []): ChainedValidatorInterface; public function range(mixed $minConstraint, mixed $maxConstraint, array $options = []): ChainedValidatorInterface; } \ No newline at end of file diff --git a/src/Exception/AllException.php b/src/Exception/AllException.php new file mode 100644 index 0000000..4f688c4 --- /dev/null +++ b/src/Exception/AllException.php @@ -0,0 +1,5 @@ +setDefaults(['message' => 'At "{{ key }}": {{ message }}']); + + $resolver->setAllowedTypes('message', 'string'); + + $this->options = $resolver->resolve($options); + } + + public function assert(mixed $value, string $name): void + { + $this->assertIsValidatable($this->constraints); + + if (!\is_array($value)) { + throw new UnexpectedValueException( + \sprintf('Expected value of type "array", "%s" given.', get_debug_type($value)) + ); + } + + try { + foreach ($value as $key => $input) { + foreach ($this->constraints as $constraint) { + $constraint->assert($input, $name); + } + } + } + catch (ValidationException $exception) { + throw new AllException( + message: $this->options['message'], + parameters: [ + 'value' => $value, + 'name' => $name, + 'key' => $key, + 'message' => $exception->getMessage() + ] + ); + } + } +} \ No newline at end of file diff --git a/src/Rule/Choice.php b/src/Rule/Choice.php index 284e642..dc5e712 100644 --- a/src/Rule/Choice.php +++ b/src/Rule/Choice.php @@ -39,7 +39,7 @@ public function assert(mixed $value, string $name): void { if ($this->multiple && !\is_array($value)) { throw new UnexpectedValueException( - \sprintf('Expected value of type "array" when multiple, "%s" given', get_debug_type($value)) + \sprintf('Expected value of type "array" when using multiple choices, "%s" given', get_debug_type($value)) ); } diff --git a/src/Rule/Range.php b/src/Rule/Range.php index ee2f76e..928d62e 100644 --- a/src/Rule/Range.php +++ b/src/Rule/Range.php @@ -22,9 +22,7 @@ public function __construct( { $resolver = new OptionsResolver(); - $resolver->setDefaults([ - 'message' => 'The "{{ name }}" value should be between "{{ minConstraint }}" and "{{ maxConstraint }}", "{{ value }}" given.' - ]); + $resolver->setDefaults(['message' => 'The "{{ name }}" value should be between "{{ minConstraint }}" and "{{ maxConstraint }}", "{{ value }}" given.']); $resolver->setAllowedTypes('message', 'string'); @@ -35,13 +33,22 @@ public function assert(mixed $value, string $name): void { $this->assertIsComparable($this->minConstraint, $this->maxConstraint); - if (!Validator::greaterThan($this->minConstraint)->validate($this->maxConstraint)) { + if ( + !Validator + ::greaterThan($this->minConstraint) + ->validate($this->maxConstraint) + ) { throw new UnexpectedValueException( 'Max constraint value must be greater than min constraint value.' ); } - if (!Validator::greaterThanOrEqual($this->minConstraint)->lessThanOrEqual($this->maxConstraint)->validate($value)) { + if ( + !Validator + ::greaterThanOrEqual($this->minConstraint) + ->lessThanOrEqual($this->maxConstraint) + ->validate($value) + ) { throw new RangeException( message: $this->options['message'], parameters: [ diff --git a/src/Rule/Util/AssertIsComparableTrait.php b/src/Rule/Util/AssertIsComparableTrait.php index 353a041..494d18e 100644 --- a/src/Rule/Util/AssertIsComparableTrait.php +++ b/src/Rule/Util/AssertIsComparableTrait.php @@ -22,7 +22,7 @@ private function assertIsComparable(mixed $value1, mixed $value2): bool throw new UnexpectedValueException( \sprintf( - 'Cannot compare a type "%s" with a type "%s"', + 'Cannot compare a type "%s" with a type "%s".', get_debug_type($value1), get_debug_type($value2) ) diff --git a/src/Rule/Util/AssertIsValidatableTrait.php b/src/Rule/Util/AssertIsValidatableTrait.php new file mode 100644 index 0000000..2499111 --- /dev/null +++ b/src/Rule/Util/AssertIsValidatableTrait.php @@ -0,0 +1,22 @@ + [ + new All([new NotBlank(), 'invalid']), + [1, 2, 3], + '/Expected constraint of type "RuleInterface", "(.*)" given./' + ]; + yield 'invalid value type' => [ + new All([new NotBlank()]), + 'invalid', + '/Expected value of type "array", "(.*)" given./' + ]; + yield 'unexpected value propagation' => [ + new All([new GreaterThan(10)]), + ['a'], + '/Cannot compare a type "(.*)" with a type "(.*)"./' + ]; + } + + public static function provideRuleFailureConditionData(): \Generator + { + $exception = AllException::class; + $message = '/At "(.*)": The "(.*)" value should not be blank, "(.*)" given./'; + + yield 'constraint' => [ + new All([new NotBlank()]), + [1, 2, ''], + $exception, + $message + ]; + yield 'validator' => [ + new All([(new Validator(new NotBlank()))]), + [1, 2, ''], + $exception, + $message + ]; + } + + public static function provideRuleSuccessConditionData(): \Generator + { + yield 'constraints' => [ + new All([new NotBlank(), new GreaterThan(1)]), + [2, 3, 4] + ]; + yield 'validators' => [ + new All([ + (new Validator(new NotBlank())), + (new Validator(new GreaterThan(1))) + ]), + [2, 3, 4] + ]; + yield 'constraints and validators' => [ + new All([ + new NotBlank(), + (new Validator(new GreaterThan(1))) + ]), + [2, 3, 4] + ]; + } + + public static function provideRuleMessageOptionData(): \Generator + { + yield 'constraint' => [ + new All( + constraints: [new NotBlank()], + options: [ + 'message' => 'The "{{ name }}" value "{{ value }}" failed at key "{{ key }}".' + ] + ), [1, 2, ''], 'The "test" value "[1, 2, ]" failed at key "2".' + ]; + } +} \ No newline at end of file diff --git a/tests/ChoiceTest.php b/tests/ChoiceTest.php index 74e71c6..628dc33 100644 --- a/tests/ChoiceTest.php +++ b/tests/ChoiceTest.php @@ -19,7 +19,7 @@ class ChoiceTest extends AbstractTest public static function provideRuleUnexpectedValueData(): \Generator { $constraints = [1, 2, 3, 4, 5]; - $multipleMessage = '/Expected value of type "array" when multiple, "(.*)" given/'; + $multipleMessage = '/Expected value of type "array" when using multiple choices, "(.*)" given/'; $constraintMessage = '/Max constraint value must be greater than or equal to min constraint value./'; yield 'multiple not array' => [new Choice($constraints, true), 1, $multipleMessage];