Lines 100.00% 23 / 23
Methods 100.00% 6 / 6
Classes 100.00% 1 / 1
Name Lines Methods CRAP
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 of 100.00% 1 / 1 100.00% 1 / 1 1
 defaultMessage 100.00% 1 / 1 100.00% 1 / 1 1
 validate 100.00% 12 / 12 100.00% 1 / 1 5
 [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
18final class Any extends Constraint
19{
20    /**
21     * @param array<int, Constraint> $constraints The constraints to apply.
22     */
23    private function __construct(private readonly array $constraints) {}
24
25    /**
26     * Create an "any" combinator from one or more constraints.
27     *
28     * @param Constraint ...$constraints The constraints of which at least one must pass.
29     * @return self A new Any instance.
30     */
31    public static function of(Constraint ...$constraints): self
32    {
33        return new self($constraints);
34    }
35
36    /**
37     * No message of its own — failed alternatives' messages are recorded
38     * directly on the result.
39     *
40     * @return string|\Closure(FieldContext): ?string|null Always null.
41     */
42    #[Override]
43    protected function defaultMessage(): string|\Closure|null
44    {
45        return null;
46    }
47
48    /**
49     * Validate that at least one wrapped constraint passes.
50     *
51     * Each alternative is validated in isolation against a fresh, throwaway
52     * {@see Result}, so a losing branch's errors *and* values never leak into
53     * the final result. If an alternative passes, its result is committed via
54     * {@see Result::merge()} and the field passes. If none pass, every failed
55     * alternative's message is recorded on the result, so the caller sees all
56     * acceptable options.
57     *
58     * @param FieldContext $ctx The context of the field being validated.
59     * @return bool True if at least one constraint passes, false otherwise.
60     */
61    #[Override]
62    public function validate(FieldContext $ctx): bool
63    {
64        $failed = [];
65
66        foreach ($this->constraints as $constraint) {
67            [$passed, $branch] = $ctx->branch($constraint);
68
69            if ($passed) {
70                // This alternative passed — commit its result so the winning
71                // branch's normalized values are preserved, and discard the
72                // failed branches' errors.
73                $ctx->result->merge($branch);
74                return true;
75            }
76
77            $failed[] = $constraint;
78        }
79
80        // No alternative passed — record every failed alternative's message
81        // so the caller sees all acceptable options.
82        foreach ($failed as $constraint) {
83            $message = $constraint->message($ctx);
84            if ($message !== null) {
85                $ctx->result->addError($ctx->field, $message);
86            }
87        }
88
89        return false;
90    }
91}

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    }