Lines 100.00% 24 / 24
Methods 100.00% 5 / 5
Classes 100.00% 1 / 1
Name Lines Methods CRAP
 __construct 100.00% 2 / 2 100.00% 1 / 1 3
 defaultMessage 100.00% 3 / 3 100.00% 1 / 1 6
 validate 100.00% 11 / 11 100.00% 1 / 1 11
 [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
17final class Range extends Constraint
18{
19    /**
20     * Create a numeric range constraint.
21     *
22     * @param int|float|null $min The minimum value, or null for no minimum.
23     * @param int|float|null $max The maximum value, or null for no maximum.
24     * @throws \InvalidArgumentException If both $min and $max are null.
25     */
26    public function __construct(private readonly int|float|null $min = null, private readonly int|float|null $max = null)
27    {
28        if ($min === null && $max === null) {
29            throw new \InvalidArgumentException('Range constraint requires at least one of $min or $max.');
30        }
31    }
32
33    /**
34     * @return string|Closure(FieldContext): string The default error message.
35     */
36    #[Override]
37    protected function defaultMessage(): string|Closure|null
38    {
39        return fn(FieldContext $ctx) => match (true) {
40            $this->min !== null && $this->max === null => "The {$ctx->field} field must be at least {$this->min}.",
41            $this->min === null && $this->max !== null => "The {$ctx->field} field must be at most {$this->max}.",
42            default => "The {$ctx->field} field must be between {$this->min} and {$this->max}."
43        };
44    }
45
46    /**
47     * Validate that the value falls within the configured range.
48     *
49     * Numeric strings are cast to a number for the bounds check. The value is
50     * only normalized (stored in the result) once it passes the bounds check,
51     * so an out-of-range value is never stored as if it were valid.
52     * Non-numeric values always fail.
53     *
54     * @param FieldContext $ctx The context of the field being validated.
55     * @return bool True if the value is within range, false otherwise.
56     */
57    #[Override]
58    public function validate(FieldContext $ctx): bool
59    {
60        $value = $ctx->value;
61
62        if (is_string($value) && is_numeric($value)) {
63            $value = +$value;
64        }
65
66        if (!is_int($value) && !is_float($value)) {
67            return false;
68        }
69
70        $inRange = match (true) {
71            $this->min !== null && $value < $this->min => false,
72            $this->max !== null && $value > $this->max => false,
73            default => true
74        };
75
76        if ($inRange) {
77            $ctx->normalize($value);
78        }
79
80        return $inRange;
81    }
82}

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    }