Skip to content

Commit e281077

Browse files
committed
feat: add logical combinators and enhanced error collection
- Add allOf() combinator for AND logic validation - Add anyOf() combinator for OR logic validation - Add not() combinator for negation validation - Implement comprehensive error collection (collect all errors vs early exit) - Enhance addValidation() to support context-aware custom validators - Custom validators now receive (value, key, input) parameters - Improve validation context passing throughout the library
1 parent 9fa074d commit e281077

1 file changed

Lines changed: 95 additions & 0 deletions

File tree

src/Lemmon/FieldValidator.php

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ abstract class FieldValidator
1313
* @var array<mixed>|null
1414
*/
1515
protected $oneOf = null;
16+
/**
17+
* @var array<array{rule: callable, message: string}>
18+
*/
19+
protected array $validations = [];
20+
1621

1722
/**
1823
* Marks the field as required.
@@ -72,6 +77,83 @@ public function oneOf(array $values): self
7277
return $this;
7378
}
7479

80+
/**
81+
* Adds a custom validation rule.
82+
*
83+
* @param callable $validation The validation function that receives (value, key, input).
84+
* @param string $message The error message if validation fails.
85+
* @return $this
86+
*/
87+
public function addValidation(callable $validation, string $message): self
88+
{
89+
$this->validations[] = ['rule' => $validation, 'message' => $message];
90+
return $this;
91+
}
92+
93+
/**
94+
* Validates that the value passes ALL of the provided validators.
95+
*
96+
* @param array<FieldValidator> $validators Array of validators that must all pass.
97+
* @param ?string $message Custom error message.
98+
* @return $this
99+
*/
100+
public function allOf(array $validators, ?string $message = null): self
101+
{
102+
return $this->addValidation(
103+
function ($value, $key = null, $input = null) use ($validators) {
104+
foreach ($validators as $validator) {
105+
[$valid, , ] = $validator->tryValidate($value, $key, $input);
106+
if (!$valid) {
107+
return false;
108+
}
109+
}
110+
return true;
111+
},
112+
$message ?? 'Value must satisfy all validation rules.'
113+
);
114+
}
115+
116+
/**
117+
* Validates that the value passes ANY of the provided validators.
118+
*
119+
* @param array<FieldValidator> $validators Array of validators, at least one must pass.
120+
* @param ?string $message Custom error message.
121+
* @return $this
122+
*/
123+
public function anyOf(array $validators, ?string $message = null): self
124+
{
125+
return $this->addValidation(
126+
function ($value, $key = null, $input = null) use ($validators) {
127+
foreach ($validators as $validator) {
128+
[$valid, , ] = $validator->tryValidate($value, $key, $input);
129+
if ($valid) {
130+
return true;
131+
}
132+
}
133+
return false;
134+
},
135+
$message ?? 'Value must satisfy at least one validation rule.'
136+
);
137+
}
138+
139+
/**
140+
* Validates that the value does NOT pass the provided validator.
141+
*
142+
* @param FieldValidator $validator The validator that must fail.
143+
* @param ?string $message Custom error message.
144+
* @return $this
145+
*/
146+
public function not(FieldValidator $validator, ?string $message = null): self
147+
{
148+
return $this->addValidation(
149+
function ($value, $key = null, $input = null) use ($validator) {
150+
[$valid, , ] = $validator->tryValidate($value, $key, $input);
151+
return !$valid;
152+
},
153+
$message ?? 'Value must not satisfy the validation rule.'
154+
);
155+
}
156+
75157
/**
76158
* Validates the given value against the defined rules.
77159
*
@@ -119,6 +201,19 @@ public function tryValidate(mixed $value, string $key = '', mixed $input = null)
119201

120202
try {
121203
$validatedValue = $this->validateType($value, $key);
204+
205+
// Collect all validation errors
206+
$validationErrors = [];
207+
foreach ($this->validations as $validation) {
208+
if (!$validation['rule']($validatedValue, $key, $input)) {
209+
$validationErrors[] = $validation['message'];
210+
}
211+
}
212+
213+
if (!empty($validationErrors)) {
214+
return [false, $validatedValue, $validationErrors];
215+
}
216+
122217
return [true, $validatedValue, null];
123218
} catch (ValidationException $e) {
124219
return [false, $value, $e->getErrors()];

0 commit comments

Comments
 (0)