Lines
100.00%
32 / 32
Methods
100.00%
6 / 6
Classes
100.00%
1 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| __construct | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| defaultMessage | 100.00% | 5 / 5 | 100.00% | 1 / 1 | 5 | ||
| validate | 100.00% | 12 / 12 | 100.00% | 1 / 1 | 6 | ||
| [Lucent\Validation\Concerns\RecordsConstraintFailure] recordConstraintFailure | 100.00% | 6 / 6 | 100.00% | 1 / 1 | 3 | ||
| [Lucent\Validation\Constraint] withMessage | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 1 | ||
| [Lucent\Validation\Constraint] message | 100.00% | 6 / 6 | 100.00% | 1 / 1 | 3 | ||
| 30 | final class Each extends Constraint | |
| 31 | { | |
| 32 | use RecordsConstraintFailure; | |
| 33 | ||
| 34 | /** | |
| 35 | * Create an "each" combinator around a single constraint. | |
| 36 | * | |
| 37 | * @param Constraint $inner The constraint applied to every element. | |
| 38 | * @param int|null $maxItems Optional maximum number of elements. When set, | |
| 39 | * validation fails if the array has more elements than this, before | |
| 40 | * any element is processed. | |
| 41 | */ | |
| 42 | public function __construct( | |
| 43 | private readonly Constraint $inner, | |
| 44 | private readonly ?int $maxItems = null, | |
| 45 | ) {} | |
| 46 | ||
| 47 | /** | |
| 48 | * @return string|\Closure(FieldContext): ?string|null The default error message. | |
| 49 | */ | |
| 50 | #[Override] | |
| 51 | protected function defaultMessage(): string|\Closure|null | |
| 52 | { | |
| 53 | return fn(FieldContext $ctx) => $ctx->childFailed | |
| 54 | ? null | |
| 55 | : ($this->maxItems !== null && is_array($ctx->value) && count($ctx->value) > $this->maxItems | |
| 56 | ? "The {$ctx->field} must contain at most {$this->maxItems} items." | |
| 57 | : "The {$ctx->field} must be an array."); | |
| 58 | } | |
| 59 | ||
| 60 | /** | |
| 61 | * Validate every element of the array value. | |
| 62 | * | |
| 63 | * Fails (returns false) when the value is not an array, when it exceeds | |
| 64 | * the configured `$maxItems` bound, or when any element fails its | |
| 65 | * constraint. When an element fails, its specific error is already | |
| 66 | * recorded on the result at its dotted path, so {@see defaultMessage()} | |
| 67 | * returns null to avoid a redundant generic error. | |
| 68 | * | |
| 69 | * @param FieldContext $ctx The context of the field being validated. | |
| 70 | * @return bool True if the value is an array and every element passes. | |
| 71 | */ | |
| 72 | #[Override] | |
| 73 | public function validate(FieldContext $ctx): bool | |
| 74 | { | |
| 75 | $ctx->childFailed = false; | |
| 76 | ||
| 77 | if (!is_array($ctx->value)) { | |
| 78 | return false; | |
| 79 | } | |
| 80 | ||
| 81 | if ($this->maxItems !== null && count($ctx->value) > $this->maxItems) { | |
| 82 | return false; | |
| 83 | } | |
| 84 | ||
| 85 | // Declare the field as a container so only validated elements are | |
| 86 | // included in the result; each element is seeded with its raw value | |
| 87 | // so it survives even when the inner constraint does not normalize. | |
| 88 | $ctx->result->ensureContainer($ctx->field); | |
| 89 | ||
| 90 | foreach ($ctx->value as $index => $element) { | |
| 91 | $child = $ctx->child($index, $element, true); | |
| 92 | $child->seedRaw(); | |
| 93 | ||
| 94 | if (!$this->recordConstraintFailure($this->inner, $child)) { | |
| 95 | $ctx->childFailed = true; | |
| 96 | } | |
| 97 | } | |
| 98 | ||
| 99 | return !$ctx->childFailed; | |
| 100 | } | |
| 101 | } |
From Lucent\Validation\Concerns\RecordsConstraintFailure
| 18 | trait RecordsConstraintFailure | |
| 19 | { | |
| 20 | /** | |
| 21 | * Validate a constraint and record its error if it fails. | |
| 22 | * | |
| 23 | * Runs the constraint against the context. On failure, resolves the | |
| 24 | * constraint's message and records it on the result at the context's | |
| 25 | * field path, unless the message is null (meaning a child already | |
| 26 | * recorded its specific error). | |
| 27 | * | |
| 28 | * @param Constraint $constraint The constraint to validate. | |
| 29 | * @param FieldContext $ctx The context of the field being validated. | |
| 30 | * @return bool True if the constraint passed, false otherwise. | |
| 31 | */ | |
| 32 | protected function recordConstraintFailure(Constraint $constraint, FieldContext $ctx): bool | |
| 33 | { | |
| 34 | if ($constraint->validate($ctx)) { | |
| 35 | return true; | |
| 36 | } | |
| 37 | ||
| 38 | $message = $constraint->message($ctx); | |
| 39 | if ($message !== null) { | |
| 40 | $ctx->result->addError($ctx->field, $message); | |
| 41 | } | |
| 42 | ||
| 43 | return false; | |
| 44 | } | |
| 45 | } |
Inherited from Lucent\Validation\Constraint
| 38 | final public function withMessage(string|\Closure $message): static | |
| 39 | { | |
| 40 | $this->customMessage = $message; | |
| 41 | return $this; | |
| 42 | } |
| 56 | final public function message(FieldContext $ctx): ?string | |
| 57 | { | |
| 58 | $message = $this->customMessage ?? $this->defaultMessage(); | |
| 59 | ||
| 60 | if ($message === null) { | |
| 61 | return null; | |
| 62 | } | |
| 63 | ||
| 64 | if ($message instanceof \Closure) { | |
| 65 | return call_user_func($message, $ctx); | |
| 66 | } | |
| 67 | ||
| 68 | return $message; | |
| 69 | } |