Lines
100.00%
34 / 34
Methods
100.00%
7 / 7
Classes
100.00%
1 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| __construct | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 2 | ||
| defaultMessage | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| validate | 100.00% | 9 / 9 | 100.00% | 1 / 1 | 3 | ||
| resolveCase | 100.00% | 8 / 8 | 100.00% | 1 / 1 | 5 | ||
| allowedValues | 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 | ||
| 23 | final class Enum extends Constraint | |
| 24 | { | |
| 25 | /** | |
| 26 | * Create an enum constraint. | |
| 27 | * | |
| 28 | * @param class-string<\UnitEnum> $enum The enum class whose cases are allowed. | |
| 29 | * @throws \InvalidArgumentException If the given class is not an enum. | |
| 30 | */ | |
| 31 | public function __construct(private readonly string $enum) | |
| 32 | { | |
| 33 | if (!enum_exists($enum)) { | |
| 34 | throw new \InvalidArgumentException("Enum constraint requires a valid enum class, got '{$enum}'."); | |
| 35 | } | |
| 36 | } | |
| 37 | ||
| 38 | /** | |
| 39 | * @return string|Closure(FieldContext): string The default error message. | |
| 40 | */ | |
| 41 | #[Override] | |
| 42 | protected function defaultMessage(): string|Closure|null | |
| 43 | { | |
| 44 | return fn(FieldContext $ctx) => "The {$ctx->field} must be one of: " . implode(', ', $this->allowedValues()); | |
| 45 | } | |
| 46 | ||
| 47 | /** | |
| 48 | * Validate and normalize a value against the enum's cases. | |
| 49 | * | |
| 50 | * A value that is already an instance of the enum passes as-is. A backed | |
| 51 | * enum also accepts its backing value (string or int); a pure enum | |
| 52 | * accepts its case name. On success the value is normalized to the enum | |
| 53 | * case. | |
| 54 | * | |
| 55 | * @param FieldContext $ctx The context of the field being validated. | |
| 56 | * @return bool True if the value matches an enum case, false otherwise. | |
| 57 | */ | |
| 58 | #[Override] | |
| 59 | public function validate(FieldContext $ctx): bool | |
| 60 | { | |
| 61 | $value = $ctx->value; | |
| 62 | ||
| 63 | if ($value instanceof $this->enum) { | |
| 64 | // Store the instance so it is retrievable from the result (raw | |
| 65 | // object values are not seeded by the validator). | |
| 66 | $ctx->normalize($value); | |
| 67 | return true; | |
| 68 | } | |
| 69 | ||
| 70 | $case = $this->resolveCase($value); | |
| 71 | ||
| 72 | if ($case === null) { | |
| 73 | return false; | |
| 74 | } | |
| 75 | ||
| 76 | $ctx->normalize($case); | |
| 77 | return true; | |
| 78 | } | |
| 79 | ||
| 80 | /** | |
| 81 | * Resolve a raw value to an enum case, or null if it matches none. | |
| 82 | * | |
| 83 | * @param mixed $value The raw value. | |
| 84 | * @return \UnitEnum|null The matching enum case, or null. | |
| 85 | */ | |
| 86 | private function resolveCase(mixed $value): ?\UnitEnum | |
| 87 | { | |
| 88 | foreach ($this->enum::cases() as $case) { | |
| 89 | if ($case instanceof \BackedEnum) { | |
| 90 | if ($value === $case->value) { | |
| 91 | return $case; | |
| 92 | } | |
| 93 | continue; | |
| 94 | } | |
| 95 | ||
| 96 | if ($value === $case->name) { | |
| 97 | return $case; | |
| 98 | } | |
| 99 | } | |
| 100 | ||
| 101 | return null; | |
| 102 | } | |
| 103 | ||
| 104 | /** | |
| 105 | * The human-readable list of allowed values for the error message. | |
| 106 | * | |
| 107 | * @return list<string> The backing values (or case names) of the enum. | |
| 108 | */ | |
| 109 | private function allowedValues(): array | |
| 110 | { | |
| 111 | $values = []; | |
| 112 | ||
| 113 | foreach ($this->enum::cases() as $case) { | |
| 114 | $values[] = $case instanceof \BackedEnum | |
| 115 | ? (string) $case->value | |
| 116 | : $case->name; | |
| 117 | } | |
| 118 | ||
| 119 | return $values; | |
| 120 | } | |
| 121 | } |
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 | } |