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
30final 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

18trait 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    }