Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
100.00% |
64 / 64 |
|
93.41% |
85 / 91 |
|
53.25% |
41 / 77 |
|
88.89% |
24 / 27 |
CRAP | |
0.00% |
0 / 1 |
| Field | |
100.00% |
64 / 64 |
|
93.41% |
85 / 91 |
|
53.25% |
41 / 77 |
|
100.00% |
27 / 27 |
283.46 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| rules | |
100.00% |
3 / 3 |
|
100.00% |
4 / 4 |
|
33.33% |
1 / 3 |
|
100.00% |
1 / 1 |
3.19 | |||
| label | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| prepare | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| finalize | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| empty | |
100.00% |
4 / 4 |
|
100.00% |
7 / 7 |
|
25.00% |
1 / 4 |
|
100.00% |
1 / 1 |
6.80 | |||
| default | |
100.00% |
5 / 5 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| nullable | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| optional | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| strict | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| coerce | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| hasDefault | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| defaultValue | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| isNullable | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| isOptional | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| coercionMode | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| treatsMissingAsEmpty | |
100.00% |
1 / 1 |
|
75.00% |
3 / 4 |
|
50.00% |
1 / 2 |
|
100.00% |
1 / 1 |
1.12 | |||
| isBlank | |
100.00% |
4 / 4 |
|
100.00% |
6 / 6 |
|
50.00% |
2 / 4 |
|
100.00% |
1 / 1 |
4.12 | |||
| message | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| messages | |
100.00% |
3 / 3 |
|
100.00% |
4 / 4 |
|
33.33% |
1 / 3 |
|
100.00% |
1 / 1 |
3.19 | |||
| messageOverrides | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| name | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| type | |
100.00% |
1 / 1 |
|
100.00% |
4 / 4 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| applyPreparation | |
100.00% |
3 / 3 |
|
100.00% |
4 / 4 |
|
66.67% |
2 / 3 |
|
100.00% |
1 / 1 |
2.15 | |||
| applyFinalization | |
100.00% |
4 / 4 |
|
100.00% |
4 / 4 |
|
66.67% |
2 / 3 |
|
100.00% |
1 / 1 |
2.15 | |||
| matchesEmpty | |
100.00% |
5 / 5 |
|
83.33% |
15 / 18 |
|
62.50% |
5 / 8 |
|
100.00% |
1 / 1 |
9.58 | |||
| messageKey | |
100.00% |
7 / 7 |
|
88.24% |
15 / 17 |
|
22.22% |
6 / 27 |
|
100.00% |
1 / 1 |
22.94 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Celemas\Sire; |
| 6 | |
| 7 | /** @api */ |
| 8 | final class Field |
| 9 | { |
| 10 | private const int FLAG_HAS_DEFAULT = 1; |
| 11 | private const int FLAG_NULLABLE = 2; |
| 12 | private const int FLAG_OPTIONAL = 4; |
| 13 | |
| 14 | private ?string $label = null; |
| 15 | /** @var list<callable> */ |
| 16 | private array $preparers = []; |
| 17 | /** @var list<callable> */ |
| 18 | private array $finalizers = []; |
| 19 | /** @var list<Blank> */ |
| 20 | private array $empty = [Blank::Missing]; |
| 21 | private int $flags = 0; |
| 22 | private mixed $default = null; |
| 23 | private ?CoercionMode $coercionMode = null; |
| 24 | /** @var array<string, string> */ |
| 25 | private array $messages = []; |
| 26 | /** @var list<string> */ |
| 27 | public private(set) array $rules = []; |
| 28 | |
| 29 | public function __construct( |
| 30 | public readonly string $field, |
| 31 | public readonly string|Contract\Validator $type, |
| 32 | ) {} |
| 33 | |
| 34 | public function rules(string ...$rules): static |
| 35 | { |
| 36 | foreach ($rules as $rule) { |
| 37 | $this->rules[] = $rule; |
| 38 | } |
| 39 | |
| 40 | return $this; |
| 41 | } |
| 42 | |
| 43 | public function label(string $label): static |
| 44 | { |
| 45 | $this->label = $label; |
| 46 | |
| 47 | return $this; |
| 48 | } |
| 49 | |
| 50 | /** @param callable $callback */ |
| 51 | public function prepare(callable $callback): static |
| 52 | { |
| 53 | $this->preparers[] = $callback; |
| 54 | |
| 55 | return $this; |
| 56 | } |
| 57 | |
| 58 | /** @param callable(mixed, array<string, mixed>): mixed $callback */ |
| 59 | public function finalize(callable $callback): static |
| 60 | { |
| 61 | $this->finalizers[] = $callback; |
| 62 | |
| 63 | return $this; |
| 64 | } |
| 65 | |
| 66 | public function empty(Blank|string ...$empty): static |
| 67 | { |
| 68 | $this->empty = []; |
| 69 | |
| 70 | foreach ($empty as $value) { |
| 71 | $this->empty[] = $value instanceof Blank ? $value : Blank::from($value); |
| 72 | } |
| 73 | |
| 74 | return $this; |
| 75 | } |
| 76 | |
| 77 | public function default(mixed $value): static |
| 78 | { |
| 79 | $this->default = $value; |
| 80 | $this->flags |= self::FLAG_HAS_DEFAULT; |
| 81 | |
| 82 | if ($value === null) { |
| 83 | $this->nullable(); |
| 84 | } |
| 85 | |
| 86 | return $this; |
| 87 | } |
| 88 | |
| 89 | public function nullable(): static |
| 90 | { |
| 91 | $this->flags |= self::FLAG_NULLABLE; |
| 92 | |
| 93 | return $this; |
| 94 | } |
| 95 | |
| 96 | public function optional(): static |
| 97 | { |
| 98 | $this->flags |= self::FLAG_OPTIONAL; |
| 99 | |
| 100 | return $this; |
| 101 | } |
| 102 | |
| 103 | public function strict(): static |
| 104 | { |
| 105 | $this->coercionMode = CoercionMode::Strict; |
| 106 | |
| 107 | return $this; |
| 108 | } |
| 109 | |
| 110 | public function coerce(): static |
| 111 | { |
| 112 | $this->coercionMode = CoercionMode::Coerce; |
| 113 | |
| 114 | return $this; |
| 115 | } |
| 116 | |
| 117 | public function hasDefault(): bool |
| 118 | { |
| 119 | return ($this->flags & self::FLAG_HAS_DEFAULT) !== 0; |
| 120 | } |
| 121 | |
| 122 | public function defaultValue(): mixed |
| 123 | { |
| 124 | return $this->default; |
| 125 | } |
| 126 | |
| 127 | public function isNullable(): bool |
| 128 | { |
| 129 | return ($this->flags & self::FLAG_NULLABLE) !== 0; |
| 130 | } |
| 131 | |
| 132 | public function isOptional(): bool |
| 133 | { |
| 134 | return ($this->flags & self::FLAG_OPTIONAL) !== 0; |
| 135 | } |
| 136 | |
| 137 | /** @internal */ |
| 138 | public function coercionMode(CoercionMode $default): CoercionMode |
| 139 | { |
| 140 | return $this->coercionMode ?? $default; |
| 141 | } |
| 142 | |
| 143 | public function treatsMissingAsEmpty(): bool |
| 144 | { |
| 145 | return in_array(Blank::Missing, $this->empty, true); |
| 146 | } |
| 147 | |
| 148 | public function isBlank(mixed $value): bool |
| 149 | { |
| 150 | foreach ($this->empty as $empty) { |
| 151 | if ($this->matchesEmpty($empty, $value)) { |
| 152 | return true; |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | return false; |
| 157 | } |
| 158 | |
| 159 | public function message(string $key, string $message): static |
| 160 | { |
| 161 | $this->messages[$this->messageKey($key)] = $message; |
| 162 | |
| 163 | return $this; |
| 164 | } |
| 165 | |
| 166 | /** @param array<string, string> $messages */ |
| 167 | public function messages(array $messages): static |
| 168 | { |
| 169 | foreach ($messages as $key => $message) { |
| 170 | $this->message($key, $message); |
| 171 | } |
| 172 | |
| 173 | return $this; |
| 174 | } |
| 175 | |
| 176 | /** @return array<string, string> */ |
| 177 | public function messageOverrides(): array |
| 178 | { |
| 179 | return $this->messages; |
| 180 | } |
| 181 | |
| 182 | public function name(): string |
| 183 | { |
| 184 | return $this->label ?? $this->field; |
| 185 | } |
| 186 | |
| 187 | public function type(): string |
| 188 | { |
| 189 | return is_string($this->type) ? $this->type : 'shape'; |
| 190 | } |
| 191 | |
| 192 | /** @param array<string, mixed> $data */ |
| 193 | public function applyPreparation(mixed $value, array $data): mixed |
| 194 | { |
| 195 | foreach ($this->preparers as $prepare) { |
| 196 | $value = $prepare($value, $data); |
| 197 | } |
| 198 | |
| 199 | return $value; |
| 200 | } |
| 201 | |
| 202 | /** @param array<string, mixed> $values */ |
| 203 | public function applyFinalization(mixed $value, array $values): mixed |
| 204 | { |
| 205 | foreach ($this->finalizers as $finalize) { |
| 206 | $values[$this->field] = $value; |
| 207 | $value = $finalize($value, $values); |
| 208 | } |
| 209 | |
| 210 | return $value; |
| 211 | } |
| 212 | |
| 213 | private function matchesEmpty(Blank $empty, mixed $value): bool |
| 214 | { |
| 215 | return match ($empty) { |
| 216 | Blank::Missing => false, |
| 217 | Blank::Null => $value === null, |
| 218 | Blank::String => $value === '', |
| 219 | Blank::Whitespace => is_string($value) && trim($value) === '', |
| 220 | Blank::List => $value === [], |
| 221 | }; |
| 222 | } |
| 223 | |
| 224 | private function messageKey(string $key): string |
| 225 | { |
| 226 | if ($key === 'type') { |
| 227 | return 'type.' . $this->type(); |
| 228 | } |
| 229 | |
| 230 | if ($key === 'missing' || $key === 'null') { |
| 231 | return $key; |
| 232 | } |
| 233 | |
| 234 | if (str_starts_with($key, 'type.') || str_starts_with($key, 'rule.')) { |
| 235 | return $key; |
| 236 | } |
| 237 | |
| 238 | return 'rule.' . $key; |
| 239 | } |
| 240 | } |
Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not
necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once.
Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement
always has an else as part of its logical flow even if you didn't write one.
| 30 | public readonly string $field, |
| 31 | public readonly string|Contract\Validator $type, |
| 32 | ) {} |
| 203 | public function applyFinalization(mixed $value, array $values): mixed |
| 204 | { |
| 205 | foreach ($this->finalizers as $finalize) { |
| 205 | foreach ($this->finalizers as $finalize) { |
| 205 | foreach ($this->finalizers as $finalize) { |
| 206 | $values[$this->field] = $value; |
| 205 | foreach ($this->finalizers as $finalize) { |
| 206 | $values[$this->field] = $value; |
| 207 | $value = $finalize($value, $values); |
| 208 | } |
| 209 | |
| 210 | return $value; |
| 211 | } |
| 193 | public function applyPreparation(mixed $value, array $data): mixed |
| 194 | { |
| 195 | foreach ($this->preparers as $prepare) { |
| 195 | foreach ($this->preparers as $prepare) { |
| 195 | foreach ($this->preparers as $prepare) { |
| 196 | $value = $prepare($value, $data); |
| 195 | foreach ($this->preparers as $prepare) { |
| 196 | $value = $prepare($value, $data); |
| 197 | } |
| 198 | |
| 199 | return $value; |
| 200 | } |
| 112 | $this->coercionMode = CoercionMode::Coerce; |
| 113 | |
| 114 | return $this; |
| 115 | } |
| 138 | public function coercionMode(CoercionMode $default): CoercionMode |
| 139 | { |
| 140 | return $this->coercionMode ?? $default; |
| 141 | } |
| 77 | public function default(mixed $value): static |
| 78 | { |
| 79 | $this->default = $value; |
| 80 | $this->flags |= self::FLAG_HAS_DEFAULT; |
| 81 | |
| 82 | if ($value === null) { |
| 83 | $this->nullable(); |
| 84 | } |
| 85 | |
| 86 | return $this; |
| 86 | return $this; |
| 87 | } |
| 124 | return $this->default; |
| 125 | } |
| 66 | public function empty(Blank|string ...$empty): static |
| 67 | { |
| 68 | $this->empty = []; |
| 69 | |
| 70 | foreach ($empty as $value) { |
| 70 | foreach ($empty as $value) { |
| 71 | $this->empty[] = $value instanceof Blank ? $value : Blank::from($value); |
| 71 | $this->empty[] = $value instanceof Blank ? $value : Blank::from($value); |
| 71 | $this->empty[] = $value instanceof Blank ? $value : Blank::from($value); |
| 70 | foreach ($empty as $value) { |
| 71 | $this->empty[] = $value instanceof Blank ? $value : Blank::from($value); |
| 70 | foreach ($empty as $value) { |
| 71 | $this->empty[] = $value instanceof Blank ? $value : Blank::from($value); |
| 72 | } |
| 73 | |
| 74 | return $this; |
| 75 | } |
| 59 | public function finalize(callable $callback): static |
| 60 | { |
| 61 | $this->finalizers[] = $callback; |
| 62 | |
| 63 | return $this; |
| 64 | } |
| 119 | return ($this->flags & self::FLAG_HAS_DEFAULT) !== 0; |
| 120 | } |
| 148 | public function isBlank(mixed $value): bool |
| 149 | { |
| 150 | foreach ($this->empty as $empty) { |
| 150 | foreach ($this->empty as $empty) { |
| 151 | if ($this->matchesEmpty($empty, $value)) { |
| 152 | return true; |
| 150 | foreach ($this->empty as $empty) { |
| 150 | foreach ($this->empty as $empty) { |
| 151 | if ($this->matchesEmpty($empty, $value)) { |
| 152 | return true; |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | return false; |
| 157 | } |
| 129 | return ($this->flags & self::FLAG_NULLABLE) !== 0; |
| 130 | } |
| 134 | return ($this->flags & self::FLAG_OPTIONAL) !== 0; |
| 135 | } |
| 43 | public function label(string $label): static |
| 44 | { |
| 45 | $this->label = $label; |
| 46 | |
| 47 | return $this; |
| 48 | } |
| 213 | private function matchesEmpty(Blank $empty, mixed $value): bool |
| 214 | { |
| 215 | return match ($empty) { |
| 216 | Blank::Missing => false, |
| 217 | Blank::Null => $value === null, |
| 218 | Blank::String => $value === '', |
| 219 | Blank::Whitespace => is_string($value) && trim($value) === '', |
| 220 | Blank::List => $value === [], |
| 220 | Blank::List => $value === [], |
| 220 | Blank::List => $value === [], |
| 216 | Blank::Missing => false, |
| 217 | Blank::Null => $value === null, |
| 218 | Blank::String => $value === '', |
| 219 | Blank::Whitespace => is_string($value) && trim($value) === '', |
| 219 | Blank::Whitespace => is_string($value) && trim($value) === '', |
| 219 | Blank::Whitespace => is_string($value) && trim($value) === '', |
| 219 | Blank::Whitespace => is_string($value) && trim($value) === '', |
| 219 | Blank::Whitespace => is_string($value) && trim($value) === '', |
| 219 | Blank::Whitespace => is_string($value) && trim($value) === '', |
| 220 | Blank::List => $value === [], |
| 220 | Blank::List => $value === [], |
| 221 | }; |
| 222 | } |
| 159 | public function message(string $key, string $message): static |
| 160 | { |
| 161 | $this->messages[$this->messageKey($key)] = $message; |
| 162 | |
| 163 | return $this; |
| 164 | } |
| 224 | private function messageKey(string $key): string |
| 225 | { |
| 226 | if ($key === 'type') { |
| 227 | return 'type.' . $this->type(); |
| 230 | if ($key === 'missing' || $key === 'null') { |
| 230 | if ($key === 'missing' || $key === 'null') { |
| 230 | if ($key === 'missing' || $key === 'null') { |
| 231 | return $key; |
| 234 | if (str_starts_with($key, 'type.') || str_starts_with($key, 'rule.')) { |
| 234 | if (str_starts_with($key, 'type.') || str_starts_with($key, 'rule.')) { |
| 234 | if (str_starts_with($key, 'type.') || str_starts_with($key, 'rule.')) { |
| 234 | if (str_starts_with($key, 'type.') || str_starts_with($key, 'rule.')) { |
| 234 | if (str_starts_with($key, 'type.') || str_starts_with($key, 'rule.')) { |
| 234 | if (str_starts_with($key, 'type.') || str_starts_with($key, 'rule.')) { |
| 234 | if (str_starts_with($key, 'type.') || str_starts_with($key, 'rule.')) { |
| 234 | if (str_starts_with($key, 'type.') || str_starts_with($key, 'rule.')) { |
| 234 | if (str_starts_with($key, 'type.') || str_starts_with($key, 'rule.')) { |
| 235 | return $key; |
| 238 | return 'rule.' . $key; |
| 239 | } |
| 179 | return $this->messages; |
| 180 | } |
| 167 | public function messages(array $messages): static |
| 168 | { |
| 169 | foreach ($messages as $key => $message) { |
| 169 | foreach ($messages as $key => $message) { |
| 169 | foreach ($messages as $key => $message) { |
| 169 | foreach ($messages as $key => $message) { |
| 170 | $this->message($key, $message); |
| 171 | } |
| 172 | |
| 173 | return $this; |
| 174 | } |
| 184 | return $this->label ?? $this->field; |
| 185 | } |
| 91 | $this->flags |= self::FLAG_NULLABLE; |
| 92 | |
| 93 | return $this; |
| 94 | } |
| 98 | $this->flags |= self::FLAG_OPTIONAL; |
| 99 | |
| 100 | return $this; |
| 101 | } |
| 51 | public function prepare(callable $callback): static |
| 52 | { |
| 53 | $this->preparers[] = $callback; |
| 54 | |
| 55 | return $this; |
| 56 | } |
| 34 | public function rules(string ...$rules): static |
| 35 | { |
| 36 | foreach ($rules as $rule) { |
| 36 | foreach ($rules as $rule) { |
| 36 | foreach ($rules as $rule) { |
| 37 | $this->rules[] = $rule; |
| 36 | foreach ($rules as $rule) { |
| 37 | $this->rules[] = $rule; |
| 38 | } |
| 39 | |
| 40 | return $this; |
| 41 | } |
| 105 | $this->coercionMode = CoercionMode::Strict; |
| 106 | |
| 107 | return $this; |
| 108 | } |
| 145 | return in_array(Blank::Missing, $this->empty, true); |
| 145 | return in_array(Blank::Missing, $this->empty, true); |
| 145 | return in_array(Blank::Missing, $this->empty, true); |
| 145 | return in_array(Blank::Missing, $this->empty, true); |
| 146 | } |
| 189 | return is_string($this->type) ? $this->type : 'shape'; |
| 189 | return is_string($this->type) ? $this->type : 'shape'; |
| 189 | return is_string($this->type) ? $this->type : 'shape'; |
| 189 | return is_string($this->type) ? $this->type : 'shape'; |
| 190 | } |