Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.
Merged
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
6 changes: 4 additions & 2 deletions src/ChainedValidatorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
}
5 changes: 5 additions & 0 deletions src/Exception/AllException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

namespace ProgrammatorDev\YetAnotherPhpValidator\Exception;

class AllException extends ValidationException {}
63 changes: 63 additions & 0 deletions src/Rule/All.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace ProgrammatorDev\YetAnotherPhpValidator\Rule;

use ProgrammatorDev\YetAnotherPhpValidator\Exception\AllException;
use ProgrammatorDev\YetAnotherPhpValidator\Exception\UnexpectedValueException;
use ProgrammatorDev\YetAnotherPhpValidator\Exception\ValidationException;
use ProgrammatorDev\YetAnotherPhpValidator\Rule\Util\AssertIsValidatableTrait;
use Symfony\Component\OptionsResolver\OptionsResolver;

class All extends AbstractRule implements RuleInterface
{
use AssertIsValidatableTrait;

private array $options;

/**
* @param RuleInterface[] $constraints
*/
public function __construct(
private readonly array $constraints,
array $options = []
)
{
$resolver = new OptionsResolver();

$resolver->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()
]
);
}
}
}
2 changes: 1 addition & 1 deletion src/Rule/Choice.php
Original file line number Diff line number Diff line change
Expand Up @@ -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))
);
}

Expand Down
17 changes: 12 additions & 5 deletions src/Rule/Range.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand All @@ -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: [
Expand Down
2 changes: 1 addition & 1 deletion src/Rule/Util/AssertIsComparableTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
Expand Down
22 changes: 22 additions & 0 deletions src/Rule/Util/AssertIsValidatableTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace ProgrammatorDev\YetAnotherPhpValidator\Rule\Util;

use ProgrammatorDev\YetAnotherPhpValidator\Exception\UnexpectedValueException;
use ProgrammatorDev\YetAnotherPhpValidator\Rule\RuleInterface;

trait AssertIsValidatableTrait
{
private function assertIsValidatable(array $constraints): bool
{
foreach ($constraints as $constraint) {
if (!$constraint instanceof RuleInterface) {
throw new UnexpectedValueException(
\sprintf('Expected constraint of type "RuleInterface", "%s" given.', get_debug_type($constraint))
);
}
}

return true;
}
}
6 changes: 4 additions & 2 deletions src/StaticValidatorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

interface StaticValidatorInterface
{
public static function notBlank(array $options = []): ChainedValidatorInterface;
public static function all(array $constraints, array $options = null): ChainedValidatorInterface;

public static function choice(array $constraints, bool $multiple = false, ?int $minConstraint = null, ?int $maxConstraint = null, array $options = []): ChainedValidatorInterface;

public static function greaterThan(mixed $constraint, array $options = []): ChainedValidatorInterface;

Expand All @@ -14,7 +16,7 @@ public static function lessThan(mixed $constraint, array $options = []): Chained

public static function lessThanOrEqual(mixed $constraint, array $options = []): ChainedValidatorInterface;

public static function choice(array $constraints, bool $multiple = false, ?int $minConstraint = null, ?int $maxConstraint = null, array $options = []): ChainedValidatorInterface;
public static function notBlank(array $options = []): ChainedValidatorInterface;

public static function range(mixed $minConstraint, mixed $maxConstraint, array $options = []): ChainedValidatorInterface;
}
2 changes: 1 addition & 1 deletion src/Validator.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
/**
* @mixin StaticValidatorInterface
*/
class Validator
class Validator implements RuleInterface
{
private array $rules;

Expand Down
93 changes: 93 additions & 0 deletions tests/AllTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

namespace ProgrammatorDev\YetAnotherPhpValidator\Test;

use ProgrammatorDev\YetAnotherPhpValidator\Exception\AllException;
use ProgrammatorDev\YetAnotherPhpValidator\Rule\All;
use ProgrammatorDev\YetAnotherPhpValidator\Rule\GreaterThan;
use ProgrammatorDev\YetAnotherPhpValidator\Rule\NotBlank;
use ProgrammatorDev\YetAnotherPhpValidator\Test\Util\TestRuleFailureConditionTrait;
use ProgrammatorDev\YetAnotherPhpValidator\Test\Util\TestRuleMessageOptionTrait;
use ProgrammatorDev\YetAnotherPhpValidator\Test\Util\TestRuleSuccessConditionTrait;
use ProgrammatorDev\YetAnotherPhpValidator\Test\Util\TestRuleUnexpectedValueTrait;
use ProgrammatorDev\YetAnotherPhpValidator\Validator;

class AllTest extends AbstractTest
{
use TestRuleUnexpectedValueTrait;
use TestRuleFailureConditionTrait;
use TestRuleSuccessConditionTrait;
use TestRuleMessageOptionTrait;

public static function provideRuleUnexpectedValueData(): \Generator
{
yield 'invalid constraint' => [
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".'
];
}
}
2 changes: 1 addition & 1 deletion tests/ChoiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down