Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
100.00% |
272 / 272 |
|
99.34% |
151 / 152 |
|
65.22% |
75 / 115 |
|
96.77% |
30 / 31 |
CRAP | |
0.00% |
0 / 1 |
| ValidationRun | |
100.00% |
272 / 272 |
|
99.34% |
151 / 152 |
|
65.22% |
75 / 115 |
|
100.00% |
31 / 31 |
372.90 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| validate | |
100.00% |
16 / 16 |
|
100.00% |
11 / 11 |
|
25.00% |
4 / 16 |
|
100.00% |
1 / 1 |
15.55 | |||
| prepareData | |
100.00% |
3 / 3 |
|
100.00% |
4 / 4 |
|
66.67% |
2 / 3 |
|
100.00% |
1 / 1 |
2.15 | |||
| validateField | |
100.00% |
29 / 29 |
|
100.00% |
14 / 14 |
|
80.00% |
8 / 10 |
|
100.00% |
1 / 1 |
7.39 | |||
| finalizeValues | |
100.00% |
6 / 6 |
|
100.00% |
6 / 6 |
|
50.00% |
2 / 4 |
|
100.00% |
1 / 1 |
4.12 | |||
| finalizeItem | |
100.00% |
11 / 11 |
|
100.00% |
6 / 6 |
|
75.00% |
3 / 4 |
|
100.00% |
1 / 1 |
3.14 | |||
| extractValues | |
100.00% |
6 / 6 |
|
100.00% |
6 / 6 |
|
50.00% |
2 / 4 |
|
100.00% |
1 / 1 |
4.12 | |||
| readFromData | |
100.00% |
10 / 10 |
|
100.00% |
9 / 9 |
|
50.00% |
3 / 6 |
|
100.00% |
1 / 1 |
6.00 | |||
| readFieldValue | |
100.00% |
6 / 6 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| readEmptyValue | |
100.00% |
9 / 9 |
|
100.00% |
5 / 5 |
|
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
| readDefaultValue | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| addReadErrors | |
100.00% |
6 / 6 |
|
100.00% |
5 / 5 |
|
75.00% |
3 / 4 |
|
100.00% |
1 / 1 |
3.14 | |||
| readExtraValue | |
100.00% |
8 / 8 |
|
100.00% |
5 / 5 |
|
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
| readKnownValue | |
100.00% |
18 / 18 |
|
100.00% |
10 / 10 |
|
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
5 | |||
| formatExtraFailure | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| formatNullFailure | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| formatMissingFailure | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| formatCoercionFailure | |
100.00% |
11 / 11 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| formatShapeFailure | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| formatFailure | |
100.00% |
16 / 16 |
|
85.71% |
6 / 7 |
|
50.00% |
2 / 4 |
|
100.00% |
1 / 1 |
4.12 | |||
| toSubValues | |
100.00% |
13 / 13 |
|
100.00% |
5 / 5 |
|
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
| review | |
100.00% |
7 / 7 |
|
100.00% |
4 / 4 |
|
66.67% |
2 / 3 |
|
100.00% |
1 / 1 |
2.15 | |||
| fillMissingFromFields | |
100.00% |
15 / 15 |
|
100.00% |
14 / 14 |
|
58.33% |
7 / 12 |
|
100.00% |
1 / 1 |
10.54 | |||
| readValues | |
100.00% |
15 / 15 |
|
100.00% |
8 / 8 |
|
40.00% |
2 / 5 |
|
100.00% |
1 / 1 |
7.46 | |||
| validateItem | |
100.00% |
11 / 11 |
|
100.00% |
9 / 9 |
|
66.67% |
4 / 6 |
|
100.00% |
1 / 1 |
4.59 | |||
| getValues | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| blockRules | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| rulesBlocked | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| path | |
100.00% |
3 / 3 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| pathKey | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| isRawEmptyValue | |
100.00% |
1 / 1 |
|
100.00% |
5 / 5 |
|
25.00% |
1 / 4 |
|
100.00% |
1 / 1 |
6.80 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Celema\Sire; |
| 6 | |
| 7 | use Celema\Sire\Contract\Value; |
| 8 | use ValueError; |
| 9 | |
| 10 | /** @internal */ |
| 11 | final class ValidationRun |
| 12 | { |
| 13 | private ErrorBag $errors; |
| 14 | /** @var array<string, true> */ |
| 15 | private array $ruleBlockedPaths = []; |
| 16 | |
| 17 | /** @param array<array-key, mixed> $data */ |
| 18 | public function __construct( |
| 19 | private readonly ShapeDefinition $shape, |
| 20 | private readonly array $data, |
| 21 | ) { |
| 22 | $this->errors = new ErrorBag(); |
| 23 | } |
| 24 | |
| 25 | public function validate(): Result |
| 26 | { |
| 27 | $data = $this->prepareData($this->data); |
| 28 | $values = $this->readValues($data); |
| 29 | $validatedValues = []; |
| 30 | |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 33 | /** @var array<string, Value> $subValues */ |
| 34 | $validatedValues[] = $this->validateItem($subValues, $listIndex); |
| 35 | } |
| 36 | } else { |
| 37 | /** @var array<string, Value> $values */ |
| 38 | $validatedValues = $this->validateItem($values); |
| 39 | } |
| 40 | |
| 41 | if (!$this->errors->hasErrors()) { |
| 42 | $validatedValues = $this->finalizeValues($validatedValues); |
| 43 | } |
| 44 | |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 46 | |
| 47 | if (!$this->errors->hasErrors()) { |
| 48 | $this->review($extractedValues); |
| 49 | } |
| 50 | |
| 51 | return new Result( |
| 52 | $this->errors->issues(), |
| 53 | $extractedValues, |
| 54 | ); |
| 55 | } |
| 56 | |
| 57 | /** |
| 58 | * @param array<array-key, mixed> $data |
| 59 | * @return array<array-key, mixed> |
| 60 | */ |
| 61 | private function prepareData(array $data): array |
| 62 | { |
| 63 | foreach ($this->shape->prepareCallbacks as $prepare) { |
| 64 | $data = $prepare($data); |
| 65 | } |
| 66 | |
| 67 | return $data; |
| 68 | } |
| 69 | |
| 70 | private function validateField( |
| 71 | Field $definition, |
| 72 | Value $value, |
| 73 | string $ruleDefinition, |
| 74 | string|int|null $listIndex, |
| 75 | ): void { |
| 76 | $parsedRule = $this->shape->ruleParser->parse($ruleDefinition); |
| 77 | $ruleName = $parsedRule['name']; |
| 78 | $ruleArgs = $parsedRule['args']; |
| 79 | |
| 80 | $rule = $this->shape->rules->get($ruleName); |
| 81 | |
| 82 | if ($rule === null) { |
| 83 | throw new ValueError( |
| 84 | sprintf('Unknown rule "%s" in field "%s"', $ruleName, $definition->field), |
| 85 | ); |
| 86 | } |
| 87 | |
| 88 | if ($this->rulesBlocked($definition->field, $listIndex)) { |
| 89 | return; |
| 90 | } |
| 91 | |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 93 | return; |
| 94 | } |
| 95 | |
| 96 | $validation = $rule->validate($value, ...$ruleArgs); |
| 97 | |
| 98 | if ($validation->failure !== null) { |
| 99 | $this->errors->add( |
| 100 | self::path($definition->field, $listIndex), |
| 101 | $this->formatFailure( |
| 102 | $validation->failure, |
| 103 | $definition->name(), |
| 104 | $definition->field, |
| 105 | $value->pristine, |
| 106 | 'rule.' . $ruleName, |
| 107 | $rule->message, |
| 108 | $rule instanceof Contract\NamedArguments |
| 109 | ? Arguments::named($ruleArgs, $rule) |
| 110 | : $ruleArgs, |
| 111 | $definition->messageOverrides(), |
| 112 | ), |
| 113 | ); |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | /** |
| 118 | * @param array<string, Value>|list<array<string, Value>> $validatedValues |
| 119 | * @return array<string, Value>|list<array<string, Value>> |
| 120 | */ |
| 121 | private function finalizeValues(array $validatedValues): array |
| 122 | { |
| 123 | if ($this->shape->list) { |
| 124 | $values = []; |
| 125 | |
| 126 | foreach ($validatedValues as $item) { |
| 127 | /** @var array<string, Value> $item */ |
| 128 | $values[] = $this->finalizeItem($item); |
| 129 | } |
| 130 | |
| 131 | return $values; |
| 132 | } |
| 133 | |
| 134 | /** @var array<string, Value> $validatedValues */ |
| 135 | return $this->finalizeItem($validatedValues); |
| 136 | } |
| 137 | |
| 138 | /** |
| 139 | * @param array<string, Value> $values |
| 140 | * @return array<string, Value> |
| 141 | */ |
| 142 | private function finalizeItem(array $values): array |
| 143 | { |
| 144 | $itemValues = $this->getValues($values); |
| 145 | |
| 146 | foreach ($this->shape->fields as $field => $definition) { |
| 147 | if (!array_key_exists($field, $values)) { |
| 148 | continue; |
| 149 | } |
| 150 | |
| 151 | $value = $values[$field]; |
| 152 | $values[$field] = new \Celema\Sire\Value( |
| 153 | $definition->applyFinalization($value->value, $itemValues), |
| 154 | $value->pristine, |
| 155 | $value->empty, |
| 156 | ); |
| 157 | } |
| 158 | |
| 159 | return $values; |
| 160 | } |
| 161 | |
| 162 | /** |
| 163 | * @param array<string, Value>|list<array<string, Value>> $validatedValues |
| 164 | * @return array<string, mixed>|list<array<string, mixed>> |
| 165 | */ |
| 166 | private function extractValues(array $validatedValues): array |
| 167 | { |
| 168 | if ($this->shape->list) { |
| 169 | $values = []; |
| 170 | |
| 171 | foreach ($validatedValues as $item) { |
| 172 | /** @var array<string, Value> $item */ |
| 173 | $values[] = $this->getValues($item); |
| 174 | } |
| 175 | |
| 176 | return $values; |
| 177 | } |
| 178 | |
| 179 | /** @var array<string, Value> $validatedValues */ |
| 180 | return $this->getValues($validatedValues); |
| 181 | } |
| 182 | |
| 183 | /** |
| 184 | * @param array<array-key, mixed> $data |
| 185 | * @return array<string, Value> |
| 186 | */ |
| 187 | private function readFromData(array $data, string|int|null $listIndex = null): array |
| 188 | { |
| 189 | $values = []; |
| 190 | |
| 191 | /** @var mixed $value */ |
| 192 | foreach ($data as $field => $value) { |
| 193 | $field = (string) $field; |
| 194 | $definition = $this->shape->fields[$field] ?? null; |
| 195 | $value = $definition instanceof Field |
| 196 | ? $this->readFieldValue($field, $definition, $value, $data, $listIndex) |
| 197 | : $this->readExtraValue($field, $value, $listIndex); |
| 198 | |
| 199 | if ($value !== null) { |
| 200 | $values[$field] = $value; |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | return $values; |
| 205 | } |
| 206 | |
| 207 | /** @param array<array-key, mixed> $data */ |
| 208 | private function readFieldValue( |
| 209 | string $field, |
| 210 | Field $definition, |
| 211 | mixed $value, |
| 212 | array $data, |
| 213 | string|int|null $listIndex, |
| 214 | ): ?Value { |
| 215 | /** @var mixed $value */ |
| 216 | $value = $definition->applyPreparation($value, $data); |
| 217 | |
| 218 | if ($definition->isBlank($value)) { |
| 219 | return $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 220 | } |
| 221 | |
| 222 | $read = $this->readKnownValue($definition, $value); |
| 223 | $this->addReadErrors($field, $read, $listIndex); |
| 224 | |
| 225 | return $read->value; |
| 226 | } |
| 227 | |
| 228 | /** @param array<array-key, mixed> $data */ |
| 229 | private function readEmptyValue( |
| 230 | string $field, |
| 231 | Field $definition, |
| 232 | array $data, |
| 233 | string|int|null $listIndex, |
| 234 | ): ?Value { |
| 235 | if ($definition->hasDefault()) { |
| 236 | return $this->readDefaultValue($field, $definition, $data, $listIndex); |
| 237 | } |
| 238 | |
| 239 | if ($definition->isOptional()) { |
| 240 | return null; |
| 241 | } |
| 242 | |
| 243 | $this->errors->add( |
| 244 | self::path($field, $listIndex), |
| 245 | $this->formatMissingFailure($definition), |
| 246 | ); |
| 247 | |
| 248 | return null; |
| 249 | } |
| 250 | |
| 251 | /** @param array<array-key, mixed> $data */ |
| 252 | private function readDefaultValue( |
| 253 | string $field, |
| 254 | Field $definition, |
| 255 | array $data, |
| 256 | string|int|null $listIndex, |
| 257 | ): Value { |
| 258 | $read = $this->readKnownValue( |
| 259 | $definition, |
| 260 | $definition->applyPreparation($definition->defaultValue(), $data), |
| 261 | ); |
| 262 | $this->addReadErrors($field, $read, $listIndex); |
| 263 | |
| 264 | return $read->value; |
| 265 | } |
| 266 | |
| 267 | private function addReadErrors(string $field, ReadValue $read, string|int|null $listIndex): void |
| 268 | { |
| 269 | if ($read->nestedResult !== null) { |
| 270 | $this->errors->addNested(self::path($field, $listIndex), $read->nestedResult); |
| 271 | $this->blockRules($field, $listIndex); |
| 272 | } |
| 273 | |
| 274 | if ($read->issue !== null) { |
| 275 | $this->errors->add(self::path($field, $listIndex), $read->issue); |
| 276 | $this->blockRules($field, $listIndex); |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | private function readExtraValue(string $field, mixed $value, string|int|null $listIndex): ?Value |
| 281 | { |
| 282 | if ($this->shape->extra === Extra::Allow) { |
| 283 | return new \Celema\Sire\Value($value, $value, self::isRawEmptyValue($value)); |
| 284 | } |
| 285 | |
| 286 | if ($this->shape->extra === Extra::Forbid) { |
| 287 | $this->errors->add( |
| 288 | self::path($field, $listIndex), |
| 289 | $this->formatExtraFailure($field, $value), |
| 290 | ); |
| 291 | } |
| 292 | |
| 293 | return null; |
| 294 | } |
| 295 | |
| 296 | private function readKnownValue(Field $definition, mixed $value): ReadValue |
| 297 | { |
| 298 | if ($value === null) { |
| 299 | return new ReadValue( |
| 300 | new \Celema\Sire\Value(null, null, true), |
| 301 | $definition->isNullable() ? null : $this->formatNullFailure($definition), |
| 302 | ); |
| 303 | } |
| 304 | |
| 305 | if ($definition->type instanceof Contract\Validator) { |
| 306 | return $this->toSubValues($value, $definition, $definition->type); |
| 307 | } |
| 308 | |
| 309 | $coercer = $this->shape->coercers->get($definition->type); |
| 310 | |
| 311 | if ($coercer === null) { |
| 312 | throw new ValueError('Wrong shape type'); |
| 313 | } |
| 314 | |
| 315 | $coercion = $coercer->coerce( |
| 316 | $value, |
| 317 | $definition->coercionMode($this->shape->coercionMode), |
| 318 | ); |
| 319 | |
| 320 | return new ReadValue( |
| 321 | new \Celema\Sire\Value($coercion->value, $coercion->pristine, $coercion->empty), |
| 322 | $this->formatCoercionFailure($coercion, $definition, $coercer), |
| 323 | ); |
| 324 | } |
| 325 | |
| 326 | private function formatExtraFailure(string $field, mixed $value): Issue |
| 327 | { |
| 328 | return $this->formatFailure( |
| 329 | Failure::invalid(), |
| 330 | $field, |
| 331 | $field, |
| 332 | $value, |
| 333 | 'extra', |
| 334 | Messages::EXTRA, |
| 335 | ); |
| 336 | } |
| 337 | |
| 338 | private function formatNullFailure(Field $definition): Issue |
| 339 | { |
| 340 | return $this->formatFailure( |
| 341 | Failure::key('null'), |
| 342 | $definition->name(), |
| 343 | $definition->field, |
| 344 | null, |
| 345 | 'null', |
| 346 | Messages::NULL, |
| 347 | messages: $definition->messageOverrides(), |
| 348 | ); |
| 349 | } |
| 350 | |
| 351 | private function formatMissingFailure(Field $definition): Issue |
| 352 | { |
| 353 | return $this->formatFailure( |
| 354 | Failure::key('missing'), |
| 355 | $definition->name(), |
| 356 | $definition->field, |
| 357 | null, |
| 358 | 'missing', |
| 359 | Messages::MISSING, |
| 360 | messages: $definition->messageOverrides(), |
| 361 | ); |
| 362 | } |
| 363 | |
| 364 | private function formatCoercionFailure( |
| 365 | Contract\Coercion $coercion, |
| 366 | Field $definition, |
| 367 | Contract\Coercer $coercer, |
| 368 | ): ?Issue { |
| 369 | if ($coercion->failure === null) { |
| 370 | return null; |
| 371 | } |
| 372 | |
| 373 | return $this->formatFailure( |
| 374 | $coercion->failure, |
| 375 | $definition->name(), |
| 376 | $definition->field, |
| 377 | $coercion->pristine, |
| 378 | 'type.' . $definition->type(), |
| 379 | $coercer->message, |
| 380 | messages: $definition->messageOverrides(), |
| 381 | ); |
| 382 | } |
| 383 | |
| 384 | private function formatShapeFailure(Field $definition, mixed $value): Issue |
| 385 | { |
| 386 | return $this->formatFailure( |
| 387 | Failure::invalid(), |
| 388 | $definition->name(), |
| 389 | $definition->field, |
| 390 | $value, |
| 391 | 'type.shape', |
| 392 | Messages::SHAPE, |
| 393 | messages: $definition->messageOverrides(), |
| 394 | ); |
| 395 | } |
| 396 | |
| 397 | /** |
| 398 | * @param array<array-key, mixed> $args |
| 399 | * @param array<string, string> $messages |
| 400 | */ |
| 401 | private function formatFailure( |
| 402 | Failure $failure, |
| 403 | string $label, |
| 404 | string $field, |
| 405 | mixed $pristine, |
| 406 | ?string $defaultKey, |
| 407 | string $fallback, |
| 408 | array $args = [], |
| 409 | array $messages = [], |
| 410 | ): Issue { |
| 411 | $args = $failure->args === [] ? $args : $failure->args; |
| 412 | |
| 413 | return new Issue( |
| 414 | [], |
| 415 | $failure->key !== '' ? $failure->key : $defaultKey ?? 'invalid', |
| 416 | $this->shape->messageFormatter->format( |
| 417 | $failure, |
| 418 | $label, |
| 419 | $field, |
| 420 | $pristine, |
| 421 | $defaultKey, |
| 422 | $fallback, |
| 423 | $args, |
| 424 | $messages, |
| 425 | ), |
| 426 | Arguments::keyed($args), |
| 427 | ); |
| 428 | } |
| 429 | |
| 430 | private function toSubValues( |
| 431 | mixed $pristine, |
| 432 | Field $definition, |
| 433 | Contract\Validator $shape, |
| 434 | ): ReadValue { |
| 435 | if (!is_array($pristine)) { |
| 436 | return new ReadValue( |
| 437 | new \Celema\Sire\Value($pristine, $pristine, self::isRawEmptyValue($pristine)), |
| 438 | $this->formatShapeFailure($definition, $pristine), |
| 439 | ); |
| 440 | } |
| 441 | |
| 442 | $result = $shape->validate($pristine); |
| 443 | |
| 444 | if ($result->valid()) { |
| 445 | $values = $result->values(); |
| 446 | |
| 447 | return new ReadValue(new \Celema\Sire\Value($values, $pristine, $values === [])); |
| 448 | } |
| 449 | |
| 450 | return new ReadValue( |
| 451 | new \Celema\Sire\Value($pristine, $pristine, self::isRawEmptyValue($pristine)), |
| 452 | nestedResult: $result, |
| 453 | ); |
| 454 | } |
| 455 | |
| 456 | /** @param array<string, mixed>|list<array<string, mixed>> $values */ |
| 457 | private function review(array $values): void |
| 458 | { |
| 459 | $context = new Review( |
| 460 | $this->errors, |
| 461 | $values, |
| 462 | $this->shape->list, |
| 463 | ); |
| 464 | |
| 465 | foreach ($this->shape->reviewCallbacks as $review) { |
| 466 | $review($context); |
| 467 | } |
| 468 | } |
| 469 | |
| 470 | /** |
| 471 | * @param array<string, Value> $values |
| 472 | * @param array<array-key, mixed> $data |
| 473 | * @return array<string, Value> |
| 474 | */ |
| 475 | private function fillMissingFromFields( |
| 476 | array $values, |
| 477 | array $data, |
| 478 | string|int|null $listIndex = null, |
| 479 | ): array { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 482 | continue; |
| 483 | } |
| 484 | |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 486 | $value = $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 487 | |
| 488 | if ($value !== null) { |
| 489 | $values[$field] = $value; |
| 490 | } |
| 491 | |
| 492 | continue; |
| 493 | } |
| 494 | |
| 495 | if ($definition->isOptional()) { |
| 496 | continue; |
| 497 | } |
| 498 | |
| 499 | $this->errors->add( |
| 500 | self::path($field, $listIndex), |
| 501 | $this->formatMissingFailure($definition), |
| 502 | ); |
| 503 | } |
| 504 | |
| 505 | return $values; |
| 506 | } |
| 507 | |
| 508 | /** |
| 509 | * @param array<array-key, mixed> $data |
| 510 | * @return array<string, Value>|list<array<string, Value>> |
| 511 | */ |
| 512 | private function readValues(array $data): array |
| 513 | { |
| 514 | if ($this->shape->list) { |
| 515 | $values = []; |
| 516 | |
| 517 | foreach ($data as $listIndex => $item) { |
| 518 | if (!is_array($item)) { |
| 519 | $this->errors->add( |
| 520 | [$listIndex], |
| 521 | new Issue([], 'type.shape', 'Item must be an array'), |
| 522 | ); |
| 523 | $values[] = []; |
| 524 | |
| 525 | continue; |
| 526 | } |
| 527 | |
| 528 | $subValues = $this->readFromData($item, $listIndex); |
| 529 | $values[] = $this->fillMissingFromFields($subValues, $item, $listIndex); |
| 530 | } |
| 531 | |
| 532 | return $values; |
| 533 | } |
| 534 | |
| 535 | $values = $this->readFromData($data); |
| 536 | |
| 537 | return $this->fillMissingFromFields($values, $data); |
| 538 | } |
| 539 | |
| 540 | /** |
| 541 | * @param array<string, Value> $values |
| 542 | * @return array<string, Value> |
| 543 | */ |
| 544 | private function validateItem(array $values, string|int|null $listIndex = null): array |
| 545 | { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 547 | if (!array_key_exists($field, $values)) { |
| 548 | continue; |
| 549 | } |
| 550 | |
| 551 | foreach ($definition->rules as $rule) { |
| 552 | $this->validateField( |
| 553 | $definition, |
| 554 | $values[$field], |
| 555 | $rule, |
| 556 | $listIndex, |
| 557 | ); |
| 558 | } |
| 559 | } |
| 560 | |
| 561 | return $values; |
| 562 | } |
| 563 | |
| 564 | /** |
| 565 | * @param array<string, Value> $values |
| 566 | * @return array<string, mixed> |
| 567 | */ |
| 568 | private function getValues(array $values): array |
| 569 | { |
| 570 | return array_map( |
| 571 | static fn(Value $item): mixed => $item->value, |
| 572 | $values, |
| 573 | ); |
| 574 | } |
| 575 | |
| 576 | private function blockRules(string $field, string|int|null $listIndex): void |
| 577 | { |
| 578 | $this->ruleBlockedPaths[self::pathKey(self::path($field, $listIndex))] = true; |
| 579 | } |
| 580 | |
| 581 | private function rulesBlocked(string $field, string|int|null $listIndex): bool |
| 582 | { |
| 583 | return array_key_exists( |
| 584 | self::pathKey(self::path($field, $listIndex)), |
| 585 | $this->ruleBlockedPaths, |
| 586 | ); |
| 587 | } |
| 588 | |
| 589 | /** @return list<string|int> */ |
| 590 | private static function path(string $field, string|int|null $listIndex): array |
| 591 | { |
| 592 | if ($listIndex === null) { |
| 593 | return [$field]; |
| 594 | } |
| 595 | |
| 596 | return [$listIndex, $field]; |
| 597 | } |
| 598 | |
| 599 | /** @param list<string|int> $path */ |
| 600 | private static function pathKey(array $path): string |
| 601 | { |
| 602 | return serialize($path); |
| 603 | } |
| 604 | |
| 605 | private static function isRawEmptyValue(mixed $value): bool |
| 606 | { |
| 607 | return $value === null || $value === [] || $value === ''; |
| 608 | } |
| 609 | } |
Below are the source code lines that represent each code path as identified by Xdebug. Please note a path is not
necessarily coterminous with a line, a line may contain multiple paths and therefore show up more than once.
Please also be aware that some paths may include implicit rather than explicit branches, e.g. an if statement
always has an else as part of its logical flow even if you didn't write one.
| 19 | private readonly ShapeDefinition $shape, |
| 20 | private readonly array $data, |
| 21 | ) { |
| 22 | $this->errors = new ErrorBag(); |
| 23 | } |
| 267 | private function addReadErrors(string $field, ReadValue $read, string|int|null $listIndex): void |
| 268 | { |
| 269 | if ($read->nestedResult !== null) { |
| 270 | $this->errors->addNested(self::path($field, $listIndex), $read->nestedResult); |
| 271 | $this->blockRules($field, $listIndex); |
| 272 | } |
| 273 | |
| 274 | if ($read->issue !== null) { |
| 274 | if ($read->issue !== null) { |
| 275 | $this->errors->add(self::path($field, $listIndex), $read->issue); |
| 276 | $this->blockRules($field, $listIndex); |
| 277 | } |
| 278 | } |
| 278 | } |
| 267 | private function addReadErrors(string $field, ReadValue $read, string|int|null $listIndex): void |
| 268 | { |
| 269 | if ($read->nestedResult !== null) { |
| 270 | $this->errors->addNested(self::path($field, $listIndex), $read->nestedResult); |
| 271 | $this->blockRules($field, $listIndex); |
| 272 | } |
| 273 | |
| 274 | if ($read->issue !== null) { |
| 274 | if ($read->issue !== null) { |
| 278 | } |
| 267 | private function addReadErrors(string $field, ReadValue $read, string|int|null $listIndex): void |
| 268 | { |
| 269 | if ($read->nestedResult !== null) { |
| 274 | if ($read->issue !== null) { |
| 275 | $this->errors->add(self::path($field, $listIndex), $read->issue); |
| 276 | $this->blockRules($field, $listIndex); |
| 277 | } |
| 278 | } |
| 278 | } |
| 267 | private function addReadErrors(string $field, ReadValue $read, string|int|null $listIndex): void |
| 268 | { |
| 269 | if ($read->nestedResult !== null) { |
| 274 | if ($read->issue !== null) { |
| 278 | } |
| 576 | private function blockRules(string $field, string|int|null $listIndex): void |
| 577 | { |
| 578 | $this->ruleBlockedPaths[self::pathKey(self::path($field, $listIndex))] = true; |
| 579 | } |
| 166 | private function extractValues(array $validatedValues): array |
| 167 | { |
| 168 | if ($this->shape->list) { |
| 169 | $values = []; |
| 170 | |
| 171 | foreach ($validatedValues as $item) { |
| 171 | foreach ($validatedValues as $item) { |
| 171 | foreach ($validatedValues as $item) { |
| 172 | /** @var array<string, Value> $item */ |
| 173 | $values[] = $this->getValues($item); |
| 171 | foreach ($validatedValues as $item) { |
| 171 | foreach ($validatedValues as $item) { |
| 172 | /** @var array<string, Value> $item */ |
| 173 | $values[] = $this->getValues($item); |
| 174 | } |
| 175 | |
| 176 | return $values; |
| 166 | private function extractValues(array $validatedValues): array |
| 167 | { |
| 168 | if ($this->shape->list) { |
| 169 | $values = []; |
| 170 | |
| 171 | foreach ($validatedValues as $item) { |
| 171 | foreach ($validatedValues as $item) { |
| 171 | foreach ($validatedValues as $item) { |
| 172 | /** @var array<string, Value> $item */ |
| 173 | $values[] = $this->getValues($item); |
| 174 | } |
| 175 | |
| 176 | return $values; |
| 166 | private function extractValues(array $validatedValues): array |
| 167 | { |
| 168 | if ($this->shape->list) { |
| 169 | $values = []; |
| 170 | |
| 171 | foreach ($validatedValues as $item) { |
| 171 | foreach ($validatedValues as $item) { |
| 172 | /** @var array<string, Value> $item */ |
| 173 | $values[] = $this->getValues($item); |
| 174 | } |
| 175 | |
| 176 | return $values; |
| 166 | private function extractValues(array $validatedValues): array |
| 167 | { |
| 168 | if ($this->shape->list) { |
| 180 | return $this->getValues($validatedValues); |
| 181 | } |
| 476 | array $values, |
| 477 | array $data, |
| 478 | string|int|null $listIndex = null, |
| 479 | ): array { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 482 | continue; |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 482 | continue; |
| 483 | } |
| 484 | |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 486 | $value = $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 487 | |
| 488 | if ($value !== null) { |
| 489 | $values[$field] = $value; |
| 490 | } |
| 491 | |
| 492 | continue; |
| 493 | } |
| 494 | |
| 495 | if ($definition->isOptional()) { |
| 496 | continue; |
| 497 | } |
| 498 | |
| 499 | $this->errors->add( |
| 500 | self::path($field, $listIndex), |
| 501 | $this->formatMissingFailure($definition), |
| 502 | ); |
| 503 | } |
| 504 | |
| 505 | return $values; |
| 506 | } |
| 476 | array $values, |
| 477 | array $data, |
| 478 | string|int|null $listIndex = null, |
| 479 | ): array { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 486 | $value = $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 487 | |
| 488 | if ($value !== null) { |
| 489 | $values[$field] = $value; |
| 490 | } |
| 491 | |
| 492 | continue; |
| 492 | continue; |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 482 | continue; |
| 483 | } |
| 484 | |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 486 | $value = $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 487 | |
| 488 | if ($value !== null) { |
| 489 | $values[$field] = $value; |
| 490 | } |
| 491 | |
| 492 | continue; |
| 493 | } |
| 494 | |
| 495 | if ($definition->isOptional()) { |
| 496 | continue; |
| 497 | } |
| 498 | |
| 499 | $this->errors->add( |
| 500 | self::path($field, $listIndex), |
| 501 | $this->formatMissingFailure($definition), |
| 502 | ); |
| 503 | } |
| 504 | |
| 505 | return $values; |
| 506 | } |
| 476 | array $values, |
| 477 | array $data, |
| 478 | string|int|null $listIndex = null, |
| 479 | ): array { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 486 | $value = $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 487 | |
| 488 | if ($value !== null) { |
| 492 | continue; |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 482 | continue; |
| 483 | } |
| 484 | |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 486 | $value = $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 487 | |
| 488 | if ($value !== null) { |
| 489 | $values[$field] = $value; |
| 490 | } |
| 491 | |
| 492 | continue; |
| 493 | } |
| 494 | |
| 495 | if ($definition->isOptional()) { |
| 496 | continue; |
| 497 | } |
| 498 | |
| 499 | $this->errors->add( |
| 500 | self::path($field, $listIndex), |
| 501 | $this->formatMissingFailure($definition), |
| 502 | ); |
| 503 | } |
| 504 | |
| 505 | return $values; |
| 506 | } |
| 476 | array $values, |
| 477 | array $data, |
| 478 | string|int|null $listIndex = null, |
| 479 | ): array { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 495 | if ($definition->isOptional()) { |
| 496 | continue; |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 482 | continue; |
| 483 | } |
| 484 | |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 486 | $value = $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 487 | |
| 488 | if ($value !== null) { |
| 489 | $values[$field] = $value; |
| 490 | } |
| 491 | |
| 492 | continue; |
| 493 | } |
| 494 | |
| 495 | if ($definition->isOptional()) { |
| 496 | continue; |
| 497 | } |
| 498 | |
| 499 | $this->errors->add( |
| 500 | self::path($field, $listIndex), |
| 501 | $this->formatMissingFailure($definition), |
| 502 | ); |
| 503 | } |
| 504 | |
| 505 | return $values; |
| 506 | } |
| 476 | array $values, |
| 477 | array $data, |
| 478 | string|int|null $listIndex = null, |
| 479 | ): array { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 495 | if ($definition->isOptional()) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 482 | continue; |
| 483 | } |
| 484 | |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 486 | $value = $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 487 | |
| 488 | if ($value !== null) { |
| 489 | $values[$field] = $value; |
| 490 | } |
| 491 | |
| 492 | continue; |
| 493 | } |
| 494 | |
| 495 | if ($definition->isOptional()) { |
| 496 | continue; |
| 497 | } |
| 498 | |
| 499 | $this->errors->add( |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 482 | continue; |
| 483 | } |
| 484 | |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 486 | $value = $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 487 | |
| 488 | if ($value !== null) { |
| 489 | $values[$field] = $value; |
| 490 | } |
| 491 | |
| 492 | continue; |
| 493 | } |
| 494 | |
| 495 | if ($definition->isOptional()) { |
| 496 | continue; |
| 497 | } |
| 498 | |
| 499 | $this->errors->add( |
| 500 | self::path($field, $listIndex), |
| 501 | $this->formatMissingFailure($definition), |
| 502 | ); |
| 503 | } |
| 504 | |
| 505 | return $values; |
| 506 | } |
| 476 | array $values, |
| 477 | array $data, |
| 478 | string|int|null $listIndex = null, |
| 479 | ): array { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 482 | continue; |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 482 | continue; |
| 483 | } |
| 484 | |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 486 | $value = $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 487 | |
| 488 | if ($value !== null) { |
| 489 | $values[$field] = $value; |
| 490 | } |
| 491 | |
| 492 | continue; |
| 493 | } |
| 494 | |
| 495 | if ($definition->isOptional()) { |
| 496 | continue; |
| 497 | } |
| 498 | |
| 499 | $this->errors->add( |
| 500 | self::path($field, $listIndex), |
| 501 | $this->formatMissingFailure($definition), |
| 502 | ); |
| 503 | } |
| 504 | |
| 505 | return $values; |
| 506 | } |
| 476 | array $values, |
| 477 | array $data, |
| 478 | string|int|null $listIndex = null, |
| 479 | ): array { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 486 | $value = $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 487 | |
| 488 | if ($value !== null) { |
| 489 | $values[$field] = $value; |
| 490 | } |
| 491 | |
| 492 | continue; |
| 492 | continue; |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 482 | continue; |
| 483 | } |
| 484 | |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 486 | $value = $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 487 | |
| 488 | if ($value !== null) { |
| 489 | $values[$field] = $value; |
| 490 | } |
| 491 | |
| 492 | continue; |
| 493 | } |
| 494 | |
| 495 | if ($definition->isOptional()) { |
| 496 | continue; |
| 497 | } |
| 498 | |
| 499 | $this->errors->add( |
| 500 | self::path($field, $listIndex), |
| 501 | $this->formatMissingFailure($definition), |
| 502 | ); |
| 503 | } |
| 504 | |
| 505 | return $values; |
| 506 | } |
| 476 | array $values, |
| 477 | array $data, |
| 478 | string|int|null $listIndex = null, |
| 479 | ): array { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 486 | $value = $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 487 | |
| 488 | if ($value !== null) { |
| 492 | continue; |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 482 | continue; |
| 483 | } |
| 484 | |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 486 | $value = $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 487 | |
| 488 | if ($value !== null) { |
| 489 | $values[$field] = $value; |
| 490 | } |
| 491 | |
| 492 | continue; |
| 493 | } |
| 494 | |
| 495 | if ($definition->isOptional()) { |
| 496 | continue; |
| 497 | } |
| 498 | |
| 499 | $this->errors->add( |
| 500 | self::path($field, $listIndex), |
| 501 | $this->formatMissingFailure($definition), |
| 502 | ); |
| 503 | } |
| 504 | |
| 505 | return $values; |
| 506 | } |
| 476 | array $values, |
| 477 | array $data, |
| 478 | string|int|null $listIndex = null, |
| 479 | ): array { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 495 | if ($definition->isOptional()) { |
| 496 | continue; |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 482 | continue; |
| 483 | } |
| 484 | |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 486 | $value = $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 487 | |
| 488 | if ($value !== null) { |
| 489 | $values[$field] = $value; |
| 490 | } |
| 491 | |
| 492 | continue; |
| 493 | } |
| 494 | |
| 495 | if ($definition->isOptional()) { |
| 496 | continue; |
| 497 | } |
| 498 | |
| 499 | $this->errors->add( |
| 500 | self::path($field, $listIndex), |
| 501 | $this->formatMissingFailure($definition), |
| 502 | ); |
| 503 | } |
| 504 | |
| 505 | return $values; |
| 506 | } |
| 476 | array $values, |
| 477 | array $data, |
| 478 | string|int|null $listIndex = null, |
| 479 | ): array { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 495 | if ($definition->isOptional()) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 482 | continue; |
| 483 | } |
| 484 | |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 486 | $value = $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 487 | |
| 488 | if ($value !== null) { |
| 489 | $values[$field] = $value; |
| 490 | } |
| 491 | |
| 492 | continue; |
| 493 | } |
| 494 | |
| 495 | if ($definition->isOptional()) { |
| 496 | continue; |
| 497 | } |
| 498 | |
| 499 | $this->errors->add( |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 482 | continue; |
| 483 | } |
| 484 | |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 486 | $value = $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 487 | |
| 488 | if ($value !== null) { |
| 489 | $values[$field] = $value; |
| 490 | } |
| 491 | |
| 492 | continue; |
| 493 | } |
| 494 | |
| 495 | if ($definition->isOptional()) { |
| 496 | continue; |
| 497 | } |
| 498 | |
| 499 | $this->errors->add( |
| 500 | self::path($field, $listIndex), |
| 501 | $this->formatMissingFailure($definition), |
| 502 | ); |
| 503 | } |
| 504 | |
| 505 | return $values; |
| 506 | } |
| 476 | array $values, |
| 477 | array $data, |
| 478 | string|int|null $listIndex = null, |
| 479 | ): array { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 482 | continue; |
| 483 | } |
| 484 | |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 486 | $value = $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 487 | |
| 488 | if ($value !== null) { |
| 489 | $values[$field] = $value; |
| 490 | } |
| 491 | |
| 492 | continue; |
| 493 | } |
| 494 | |
| 495 | if ($definition->isOptional()) { |
| 496 | continue; |
| 497 | } |
| 498 | |
| 499 | $this->errors->add( |
| 500 | self::path($field, $listIndex), |
| 501 | $this->formatMissingFailure($definition), |
| 502 | ); |
| 503 | } |
| 504 | |
| 505 | return $values; |
| 506 | } |
| 476 | array $values, |
| 477 | array $data, |
| 478 | string|int|null $listIndex = null, |
| 479 | ): array { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 480 | foreach ($this->shape->fields as $field => $definition) { |
| 481 | if (array_key_exists($field, $values) || array_key_exists($field, $data)) { |
| 482 | continue; |
| 483 | } |
| 484 | |
| 485 | if ($definition->treatsMissingAsEmpty()) { |
| 486 | $value = $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 487 | |
| 488 | if ($value !== null) { |
| 489 | $values[$field] = $value; |
| 490 | } |
| 491 | |
| 492 | continue; |
| 493 | } |
| 494 | |
| 495 | if ($definition->isOptional()) { |
| 496 | continue; |
| 497 | } |
| 498 | |
| 499 | $this->errors->add( |
| 500 | self::path($field, $listIndex), |
| 501 | $this->formatMissingFailure($definition), |
| 502 | ); |
| 503 | } |
| 504 | |
| 505 | return $values; |
| 506 | } |
| 142 | private function finalizeItem(array $values): array |
| 143 | { |
| 144 | $itemValues = $this->getValues($values); |
| 145 | |
| 146 | foreach ($this->shape->fields as $field => $definition) { |
| 146 | foreach ($this->shape->fields as $field => $definition) { |
| 146 | foreach ($this->shape->fields as $field => $definition) { |
| 147 | if (!array_key_exists($field, $values)) { |
| 148 | continue; |
| 146 | foreach ($this->shape->fields as $field => $definition) { |
| 146 | foreach ($this->shape->fields as $field => $definition) { |
| 147 | if (!array_key_exists($field, $values)) { |
| 148 | continue; |
| 149 | } |
| 150 | |
| 151 | $value = $values[$field]; |
| 152 | $values[$field] = new \Celema\Sire\Value( |
| 153 | $definition->applyFinalization($value->value, $itemValues), |
| 154 | $value->pristine, |
| 155 | $value->empty, |
| 156 | ); |
| 157 | } |
| 158 | |
| 159 | return $values; |
| 160 | } |
| 142 | private function finalizeItem(array $values): array |
| 143 | { |
| 144 | $itemValues = $this->getValues($values); |
| 145 | |
| 146 | foreach ($this->shape->fields as $field => $definition) { |
| 146 | foreach ($this->shape->fields as $field => $definition) { |
| 146 | foreach ($this->shape->fields as $field => $definition) { |
| 147 | if (!array_key_exists($field, $values)) { |
| 146 | foreach ($this->shape->fields as $field => $definition) { |
| 147 | if (!array_key_exists($field, $values)) { |
| 148 | continue; |
| 149 | } |
| 150 | |
| 151 | $value = $values[$field]; |
| 146 | foreach ($this->shape->fields as $field => $definition) { |
| 146 | foreach ($this->shape->fields as $field => $definition) { |
| 147 | if (!array_key_exists($field, $values)) { |
| 148 | continue; |
| 149 | } |
| 150 | |
| 151 | $value = $values[$field]; |
| 152 | $values[$field] = new \Celema\Sire\Value( |
| 153 | $definition->applyFinalization($value->value, $itemValues), |
| 154 | $value->pristine, |
| 155 | $value->empty, |
| 156 | ); |
| 157 | } |
| 158 | |
| 159 | return $values; |
| 160 | } |
| 142 | private function finalizeItem(array $values): array |
| 143 | { |
| 144 | $itemValues = $this->getValues($values); |
| 145 | |
| 146 | foreach ($this->shape->fields as $field => $definition) { |
| 146 | foreach ($this->shape->fields as $field => $definition) { |
| 146 | foreach ($this->shape->fields as $field => $definition) { |
| 147 | if (!array_key_exists($field, $values)) { |
| 148 | continue; |
| 149 | } |
| 150 | |
| 151 | $value = $values[$field]; |
| 152 | $values[$field] = new \Celema\Sire\Value( |
| 153 | $definition->applyFinalization($value->value, $itemValues), |
| 154 | $value->pristine, |
| 155 | $value->empty, |
| 156 | ); |
| 157 | } |
| 158 | |
| 159 | return $values; |
| 160 | } |
| 142 | private function finalizeItem(array $values): array |
| 143 | { |
| 144 | $itemValues = $this->getValues($values); |
| 145 | |
| 146 | foreach ($this->shape->fields as $field => $definition) { |
| 146 | foreach ($this->shape->fields as $field => $definition) { |
| 147 | if (!array_key_exists($field, $values)) { |
| 148 | continue; |
| 149 | } |
| 150 | |
| 151 | $value = $values[$field]; |
| 152 | $values[$field] = new \Celema\Sire\Value( |
| 153 | $definition->applyFinalization($value->value, $itemValues), |
| 154 | $value->pristine, |
| 155 | $value->empty, |
| 156 | ); |
| 157 | } |
| 158 | |
| 159 | return $values; |
| 160 | } |
| 121 | private function finalizeValues(array $validatedValues): array |
| 122 | { |
| 123 | if ($this->shape->list) { |
| 124 | $values = []; |
| 125 | |
| 126 | foreach ($validatedValues as $item) { |
| 126 | foreach ($validatedValues as $item) { |
| 126 | foreach ($validatedValues as $item) { |
| 127 | /** @var array<string, Value> $item */ |
| 128 | $values[] = $this->finalizeItem($item); |
| 126 | foreach ($validatedValues as $item) { |
| 126 | foreach ($validatedValues as $item) { |
| 127 | /** @var array<string, Value> $item */ |
| 128 | $values[] = $this->finalizeItem($item); |
| 129 | } |
| 130 | |
| 131 | return $values; |
| 121 | private function finalizeValues(array $validatedValues): array |
| 122 | { |
| 123 | if ($this->shape->list) { |
| 124 | $values = []; |
| 125 | |
| 126 | foreach ($validatedValues as $item) { |
| 126 | foreach ($validatedValues as $item) { |
| 126 | foreach ($validatedValues as $item) { |
| 127 | /** @var array<string, Value> $item */ |
| 128 | $values[] = $this->finalizeItem($item); |
| 129 | } |
| 130 | |
| 131 | return $values; |
| 121 | private function finalizeValues(array $validatedValues): array |
| 122 | { |
| 123 | if ($this->shape->list) { |
| 124 | $values = []; |
| 125 | |
| 126 | foreach ($validatedValues as $item) { |
| 126 | foreach ($validatedValues as $item) { |
| 127 | /** @var array<string, Value> $item */ |
| 128 | $values[] = $this->finalizeItem($item); |
| 129 | } |
| 130 | |
| 131 | return $values; |
| 121 | private function finalizeValues(array $validatedValues): array |
| 122 | { |
| 123 | if ($this->shape->list) { |
| 135 | return $this->finalizeItem($validatedValues); |
| 136 | } |
| 365 | Contract\Coercion $coercion, |
| 366 | Field $definition, |
| 367 | Contract\Coercer $coercer, |
| 368 | ): ?Issue { |
| 369 | if ($coercion->failure === null) { |
| 370 | return null; |
| 365 | Contract\Coercion $coercion, |
| 366 | Field $definition, |
| 367 | Contract\Coercer $coercer, |
| 368 | ): ?Issue { |
| 369 | if ($coercion->failure === null) { |
| 373 | return $this->formatFailure( |
| 374 | $coercion->failure, |
| 375 | $definition->name(), |
| 376 | $definition->field, |
| 377 | $coercion->pristine, |
| 378 | 'type.' . $definition->type(), |
| 379 | $coercer->message, |
| 380 | messages: $definition->messageOverrides(), |
| 381 | ); |
| 382 | } |
| 326 | private function formatExtraFailure(string $field, mixed $value): Issue |
| 327 | { |
| 328 | return $this->formatFailure( |
| 329 | Failure::invalid(), |
| 330 | $field, |
| 331 | $field, |
| 332 | $value, |
| 333 | 'extra', |
| 334 | Messages::EXTRA, |
| 335 | ); |
| 336 | } |
| 402 | Failure $failure, |
| 403 | string $label, |
| 404 | string $field, |
| 405 | mixed $pristine, |
| 406 | ?string $defaultKey, |
| 407 | string $fallback, |
| 408 | array $args = [], |
| 409 | array $messages = [], |
| 410 | ): Issue { |
| 411 | $args = $failure->args === [] ? $args : $failure->args; |
| 411 | $args = $failure->args === [] ? $args : $failure->args; |
| 411 | $args = $failure->args === [] ? $args : $failure->args; |
| 412 | |
| 413 | return new Issue( |
| 414 | [], |
| 415 | $failure->key !== '' ? $failure->key : $defaultKey ?? 'invalid', |
| 415 | $failure->key !== '' ? $failure->key : $defaultKey ?? 'invalid', |
| 415 | $failure->key !== '' ? $failure->key : $defaultKey ?? 'invalid', |
| 416 | $this->shape->messageFormatter->format( |
| 417 | $failure, |
| 418 | $label, |
| 419 | $field, |
| 420 | $pristine, |
| 421 | $defaultKey, |
| 422 | $fallback, |
| 423 | $args, |
| 424 | $messages, |
| 425 | ), |
| 426 | Arguments::keyed($args), |
| 427 | ); |
| 428 | } |
| 402 | Failure $failure, |
| 403 | string $label, |
| 404 | string $field, |
| 405 | mixed $pristine, |
| 406 | ?string $defaultKey, |
| 407 | string $fallback, |
| 408 | array $args = [], |
| 409 | array $messages = [], |
| 410 | ): Issue { |
| 411 | $args = $failure->args === [] ? $args : $failure->args; |
| 411 | $args = $failure->args === [] ? $args : $failure->args; |
| 411 | $args = $failure->args === [] ? $args : $failure->args; |
| 412 | |
| 413 | return new Issue( |
| 414 | [], |
| 415 | $failure->key !== '' ? $failure->key : $defaultKey ?? 'invalid', |
| 415 | $failure->key !== '' ? $failure->key : $defaultKey ?? 'invalid', |
| 415 | $failure->key !== '' ? $failure->key : $defaultKey ?? 'invalid', |
| 416 | $this->shape->messageFormatter->format( |
| 417 | $failure, |
| 418 | $label, |
| 419 | $field, |
| 420 | $pristine, |
| 421 | $defaultKey, |
| 422 | $fallback, |
| 423 | $args, |
| 424 | $messages, |
| 425 | ), |
| 426 | Arguments::keyed($args), |
| 427 | ); |
| 428 | } |
| 402 | Failure $failure, |
| 403 | string $label, |
| 404 | string $field, |
| 405 | mixed $pristine, |
| 406 | ?string $defaultKey, |
| 407 | string $fallback, |
| 408 | array $args = [], |
| 409 | array $messages = [], |
| 410 | ): Issue { |
| 411 | $args = $failure->args === [] ? $args : $failure->args; |
| 411 | $args = $failure->args === [] ? $args : $failure->args; |
| 411 | $args = $failure->args === [] ? $args : $failure->args; |
| 412 | |
| 413 | return new Issue( |
| 414 | [], |
| 415 | $failure->key !== '' ? $failure->key : $defaultKey ?? 'invalid', |
| 415 | $failure->key !== '' ? $failure->key : $defaultKey ?? 'invalid', |
| 415 | $failure->key !== '' ? $failure->key : $defaultKey ?? 'invalid', |
| 416 | $this->shape->messageFormatter->format( |
| 417 | $failure, |
| 418 | $label, |
| 419 | $field, |
| 420 | $pristine, |
| 421 | $defaultKey, |
| 422 | $fallback, |
| 423 | $args, |
| 424 | $messages, |
| 425 | ), |
| 426 | Arguments::keyed($args), |
| 427 | ); |
| 428 | } |
| 402 | Failure $failure, |
| 403 | string $label, |
| 404 | string $field, |
| 405 | mixed $pristine, |
| 406 | ?string $defaultKey, |
| 407 | string $fallback, |
| 408 | array $args = [], |
| 409 | array $messages = [], |
| 410 | ): Issue { |
| 411 | $args = $failure->args === [] ? $args : $failure->args; |
| 411 | $args = $failure->args === [] ? $args : $failure->args; |
| 411 | $args = $failure->args === [] ? $args : $failure->args; |
| 412 | |
| 413 | return new Issue( |
| 414 | [], |
| 415 | $failure->key !== '' ? $failure->key : $defaultKey ?? 'invalid', |
| 415 | $failure->key !== '' ? $failure->key : $defaultKey ?? 'invalid', |
| 415 | $failure->key !== '' ? $failure->key : $defaultKey ?? 'invalid', |
| 416 | $this->shape->messageFormatter->format( |
| 417 | $failure, |
| 418 | $label, |
| 419 | $field, |
| 420 | $pristine, |
| 421 | $defaultKey, |
| 422 | $fallback, |
| 423 | $args, |
| 424 | $messages, |
| 425 | ), |
| 426 | Arguments::keyed($args), |
| 427 | ); |
| 428 | } |
| 351 | private function formatMissingFailure(Field $definition): Issue |
| 352 | { |
| 353 | return $this->formatFailure( |
| 354 | Failure::key('missing'), |
| 355 | $definition->name(), |
| 356 | $definition->field, |
| 357 | null, |
| 358 | 'missing', |
| 359 | Messages::MISSING, |
| 360 | messages: $definition->messageOverrides(), |
| 361 | ); |
| 362 | } |
| 338 | private function formatNullFailure(Field $definition): Issue |
| 339 | { |
| 340 | return $this->formatFailure( |
| 341 | Failure::key('null'), |
| 342 | $definition->name(), |
| 343 | $definition->field, |
| 344 | null, |
| 345 | 'null', |
| 346 | Messages::NULL, |
| 347 | messages: $definition->messageOverrides(), |
| 348 | ); |
| 349 | } |
| 384 | private function formatShapeFailure(Field $definition, mixed $value): Issue |
| 385 | { |
| 386 | return $this->formatFailure( |
| 387 | Failure::invalid(), |
| 388 | $definition->name(), |
| 389 | $definition->field, |
| 390 | $value, |
| 391 | 'type.shape', |
| 392 | Messages::SHAPE, |
| 393 | messages: $definition->messageOverrides(), |
| 394 | ); |
| 395 | } |
| 568 | private function getValues(array $values): array |
| 569 | { |
| 570 | return array_map( |
| 571 | static fn(Value $item): mixed => $item->value, |
| 572 | $values, |
| 573 | ); |
| 574 | } |
| 605 | private static function isRawEmptyValue(mixed $value): bool |
| 606 | { |
| 607 | return $value === null || $value === [] || $value === ''; |
| 607 | return $value === null || $value === [] || $value === ''; |
| 607 | return $value === null || $value === [] || $value === ''; |
| 607 | return $value === null || $value === [] || $value === ''; |
| 607 | return $value === null || $value === [] || $value === ''; |
| 608 | } |
| 605 | private static function isRawEmptyValue(mixed $value): bool |
| 606 | { |
| 607 | return $value === null || $value === [] || $value === ''; |
| 607 | return $value === null || $value === [] || $value === ''; |
| 607 | return $value === null || $value === [] || $value === ''; |
| 607 | return $value === null || $value === [] || $value === ''; |
| 608 | } |
| 605 | private static function isRawEmptyValue(mixed $value): bool |
| 606 | { |
| 607 | return $value === null || $value === [] || $value === ''; |
| 607 | return $value === null || $value === [] || $value === ''; |
| 607 | return $value === null || $value === [] || $value === ''; |
| 607 | return $value === null || $value === [] || $value === ''; |
| 608 | } |
| 605 | private static function isRawEmptyValue(mixed $value): bool |
| 606 | { |
| 607 | return $value === null || $value === [] || $value === ''; |
| 607 | return $value === null || $value === [] || $value === ''; |
| 607 | return $value === null || $value === [] || $value === ''; |
| 608 | } |
| 590 | private static function path(string $field, string|int|null $listIndex): array |
| 591 | { |
| 592 | if ($listIndex === null) { |
| 593 | return [$field]; |
| 590 | private static function path(string $field, string|int|null $listIndex): array |
| 591 | { |
| 592 | if ($listIndex === null) { |
| 596 | return [$listIndex, $field]; |
| 597 | } |
| 600 | private static function pathKey(array $path): string |
| 601 | { |
| 602 | return serialize($path); |
| 603 | } |
| 61 | private function prepareData(array $data): array |
| 62 | { |
| 63 | foreach ($this->shape->prepareCallbacks as $prepare) { |
| 63 | foreach ($this->shape->prepareCallbacks as $prepare) { |
| 63 | foreach ($this->shape->prepareCallbacks as $prepare) { |
| 64 | $data = $prepare($data); |
| 63 | foreach ($this->shape->prepareCallbacks as $prepare) { |
| 63 | foreach ($this->shape->prepareCallbacks as $prepare) { |
| 64 | $data = $prepare($data); |
| 65 | } |
| 66 | |
| 67 | return $data; |
| 68 | } |
| 61 | private function prepareData(array $data): array |
| 62 | { |
| 63 | foreach ($this->shape->prepareCallbacks as $prepare) { |
| 63 | foreach ($this->shape->prepareCallbacks as $prepare) { |
| 63 | foreach ($this->shape->prepareCallbacks as $prepare) { |
| 64 | $data = $prepare($data); |
| 65 | } |
| 66 | |
| 67 | return $data; |
| 68 | } |
| 61 | private function prepareData(array $data): array |
| 62 | { |
| 63 | foreach ($this->shape->prepareCallbacks as $prepare) { |
| 63 | foreach ($this->shape->prepareCallbacks as $prepare) { |
| 64 | $data = $prepare($data); |
| 65 | } |
| 66 | |
| 67 | return $data; |
| 68 | } |
| 253 | string $field, |
| 254 | Field $definition, |
| 255 | array $data, |
| 256 | string|int|null $listIndex, |
| 257 | ): Value { |
| 258 | $read = $this->readKnownValue( |
| 259 | $definition, |
| 260 | $definition->applyPreparation($definition->defaultValue(), $data), |
| 261 | ); |
| 262 | $this->addReadErrors($field, $read, $listIndex); |
| 263 | |
| 264 | return $read->value; |
| 265 | } |
| 230 | string $field, |
| 231 | Field $definition, |
| 232 | array $data, |
| 233 | string|int|null $listIndex, |
| 234 | ): ?Value { |
| 235 | if ($definition->hasDefault()) { |
| 236 | return $this->readDefaultValue($field, $definition, $data, $listIndex); |
| 230 | string $field, |
| 231 | Field $definition, |
| 232 | array $data, |
| 233 | string|int|null $listIndex, |
| 234 | ): ?Value { |
| 235 | if ($definition->hasDefault()) { |
| 239 | if ($definition->isOptional()) { |
| 240 | return null; |
| 230 | string $field, |
| 231 | Field $definition, |
| 232 | array $data, |
| 233 | string|int|null $listIndex, |
| 234 | ): ?Value { |
| 235 | if ($definition->hasDefault()) { |
| 239 | if ($definition->isOptional()) { |
| 243 | $this->errors->add( |
| 244 | self::path($field, $listIndex), |
| 245 | $this->formatMissingFailure($definition), |
| 246 | ); |
| 247 | |
| 248 | return null; |
| 249 | } |
| 280 | private function readExtraValue(string $field, mixed $value, string|int|null $listIndex): ?Value |
| 281 | { |
| 282 | if ($this->shape->extra === Extra::Allow) { |
| 283 | return new \Celema\Sire\Value($value, $value, self::isRawEmptyValue($value)); |
| 280 | private function readExtraValue(string $field, mixed $value, string|int|null $listIndex): ?Value |
| 281 | { |
| 282 | if ($this->shape->extra === Extra::Allow) { |
| 286 | if ($this->shape->extra === Extra::Forbid) { |
| 287 | $this->errors->add( |
| 288 | self::path($field, $listIndex), |
| 289 | $this->formatExtraFailure($field, $value), |
| 290 | ); |
| 291 | } |
| 292 | |
| 293 | return null; |
| 293 | return null; |
| 294 | } |
| 280 | private function readExtraValue(string $field, mixed $value, string|int|null $listIndex): ?Value |
| 281 | { |
| 282 | if ($this->shape->extra === Extra::Allow) { |
| 286 | if ($this->shape->extra === Extra::Forbid) { |
| 293 | return null; |
| 294 | } |
| 209 | string $field, |
| 210 | Field $definition, |
| 211 | mixed $value, |
| 212 | array $data, |
| 213 | string|int|null $listIndex, |
| 214 | ): ?Value { |
| 215 | /** @var mixed $value */ |
| 216 | $value = $definition->applyPreparation($value, $data); |
| 217 | |
| 218 | if ($definition->isBlank($value)) { |
| 219 | return $this->readEmptyValue($field, $definition, $data, $listIndex); |
| 209 | string $field, |
| 210 | Field $definition, |
| 211 | mixed $value, |
| 212 | array $data, |
| 213 | string|int|null $listIndex, |
| 214 | ): ?Value { |
| 215 | /** @var mixed $value */ |
| 216 | $value = $definition->applyPreparation($value, $data); |
| 217 | |
| 218 | if ($definition->isBlank($value)) { |
| 222 | $read = $this->readKnownValue($definition, $value); |
| 223 | $this->addReadErrors($field, $read, $listIndex); |
| 224 | |
| 225 | return $read->value; |
| 226 | } |
| 187 | private function readFromData(array $data, string|int|null $listIndex = null): array |
| 188 | { |
| 189 | $values = []; |
| 190 | |
| 191 | /** @var mixed $value */ |
| 192 | foreach ($data as $field => $value) { |
| 192 | foreach ($data as $field => $value) { |
| 192 | foreach ($data as $field => $value) { |
| 193 | $field = (string) $field; |
| 194 | $definition = $this->shape->fields[$field] ?? null; |
| 195 | $value = $definition instanceof Field |
| 196 | ? $this->readFieldValue($field, $definition, $value, $data, $listIndex) |
| 195 | $value = $definition instanceof Field |
| 196 | ? $this->readFieldValue($field, $definition, $value, $data, $listIndex) |
| 197 | : $this->readExtraValue($field, $value, $listIndex); |
| 198 | |
| 199 | if ($value !== null) { |
| 192 | foreach ($data as $field => $value) { |
| 193 | $field = (string) $field; |
| 194 | $definition = $this->shape->fields[$field] ?? null; |
| 195 | $value = $definition instanceof Field |
| 196 | ? $this->readFieldValue($field, $definition, $value, $data, $listIndex) |
| 197 | : $this->readExtraValue($field, $value, $listIndex); |
| 198 | |
| 199 | if ($value !== null) { |
| 200 | $values[$field] = $value; |
| 192 | foreach ($data as $field => $value) { |
| 192 | foreach ($data as $field => $value) { |
| 192 | foreach ($data as $field => $value) { |
| 193 | $field = (string) $field; |
| 194 | $definition = $this->shape->fields[$field] ?? null; |
| 195 | $value = $definition instanceof Field |
| 196 | ? $this->readFieldValue($field, $definition, $value, $data, $listIndex) |
| 197 | : $this->readExtraValue($field, $value, $listIndex); |
| 198 | |
| 199 | if ($value !== null) { |
| 200 | $values[$field] = $value; |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | return $values; |
| 205 | } |
| 187 | private function readFromData(array $data, string|int|null $listIndex = null): array |
| 188 | { |
| 189 | $values = []; |
| 190 | |
| 191 | /** @var mixed $value */ |
| 192 | foreach ($data as $field => $value) { |
| 192 | foreach ($data as $field => $value) { |
| 192 | foreach ($data as $field => $value) { |
| 193 | $field = (string) $field; |
| 194 | $definition = $this->shape->fields[$field] ?? null; |
| 195 | $value = $definition instanceof Field |
| 196 | ? $this->readFieldValue($field, $definition, $value, $data, $listIndex) |
| 195 | $value = $definition instanceof Field |
| 196 | ? $this->readFieldValue($field, $definition, $value, $data, $listIndex) |
| 197 | : $this->readExtraValue($field, $value, $listIndex); |
| 198 | |
| 199 | if ($value !== null) { |
| 192 | foreach ($data as $field => $value) { |
| 192 | foreach ($data as $field => $value) { |
| 192 | foreach ($data as $field => $value) { |
| 193 | $field = (string) $field; |
| 194 | $definition = $this->shape->fields[$field] ?? null; |
| 195 | $value = $definition instanceof Field |
| 196 | ? $this->readFieldValue($field, $definition, $value, $data, $listIndex) |
| 197 | : $this->readExtraValue($field, $value, $listIndex); |
| 198 | |
| 199 | if ($value !== null) { |
| 200 | $values[$field] = $value; |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | return $values; |
| 205 | } |
| 187 | private function readFromData(array $data, string|int|null $listIndex = null): array |
| 188 | { |
| 189 | $values = []; |
| 190 | |
| 191 | /** @var mixed $value */ |
| 192 | foreach ($data as $field => $value) { |
| 192 | foreach ($data as $field => $value) { |
| 192 | foreach ($data as $field => $value) { |
| 193 | $field = (string) $field; |
| 194 | $definition = $this->shape->fields[$field] ?? null; |
| 195 | $value = $definition instanceof Field |
| 195 | $value = $definition instanceof Field |
| 196 | ? $this->readFieldValue($field, $definition, $value, $data, $listIndex) |
| 197 | : $this->readExtraValue($field, $value, $listIndex); |
| 195 | $value = $definition instanceof Field |
| 196 | ? $this->readFieldValue($field, $definition, $value, $data, $listIndex) |
| 197 | : $this->readExtraValue($field, $value, $listIndex); |
| 198 | |
| 199 | if ($value !== null) { |
| 192 | foreach ($data as $field => $value) { |
| 193 | $field = (string) $field; |
| 194 | $definition = $this->shape->fields[$field] ?? null; |
| 195 | $value = $definition instanceof Field |
| 196 | ? $this->readFieldValue($field, $definition, $value, $data, $listIndex) |
| 197 | : $this->readExtraValue($field, $value, $listIndex); |
| 198 | |
| 199 | if ($value !== null) { |
| 200 | $values[$field] = $value; |
| 192 | foreach ($data as $field => $value) { |
| 192 | foreach ($data as $field => $value) { |
| 192 | foreach ($data as $field => $value) { |
| 193 | $field = (string) $field; |
| 194 | $definition = $this->shape->fields[$field] ?? null; |
| 195 | $value = $definition instanceof Field |
| 196 | ? $this->readFieldValue($field, $definition, $value, $data, $listIndex) |
| 197 | : $this->readExtraValue($field, $value, $listIndex); |
| 198 | |
| 199 | if ($value !== null) { |
| 200 | $values[$field] = $value; |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | return $values; |
| 205 | } |
| 187 | private function readFromData(array $data, string|int|null $listIndex = null): array |
| 188 | { |
| 189 | $values = []; |
| 190 | |
| 191 | /** @var mixed $value */ |
| 192 | foreach ($data as $field => $value) { |
| 192 | foreach ($data as $field => $value) { |
| 192 | foreach ($data as $field => $value) { |
| 193 | $field = (string) $field; |
| 194 | $definition = $this->shape->fields[$field] ?? null; |
| 195 | $value = $definition instanceof Field |
| 195 | $value = $definition instanceof Field |
| 196 | ? $this->readFieldValue($field, $definition, $value, $data, $listIndex) |
| 197 | : $this->readExtraValue($field, $value, $listIndex); |
| 195 | $value = $definition instanceof Field |
| 196 | ? $this->readFieldValue($field, $definition, $value, $data, $listIndex) |
| 197 | : $this->readExtraValue($field, $value, $listIndex); |
| 198 | |
| 199 | if ($value !== null) { |
| 192 | foreach ($data as $field => $value) { |
| 192 | foreach ($data as $field => $value) { |
| 192 | foreach ($data as $field => $value) { |
| 193 | $field = (string) $field; |
| 194 | $definition = $this->shape->fields[$field] ?? null; |
| 195 | $value = $definition instanceof Field |
| 196 | ? $this->readFieldValue($field, $definition, $value, $data, $listIndex) |
| 197 | : $this->readExtraValue($field, $value, $listIndex); |
| 198 | |
| 199 | if ($value !== null) { |
| 200 | $values[$field] = $value; |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | return $values; |
| 205 | } |
| 187 | private function readFromData(array $data, string|int|null $listIndex = null): array |
| 188 | { |
| 189 | $values = []; |
| 190 | |
| 191 | /** @var mixed $value */ |
| 192 | foreach ($data as $field => $value) { |
| 192 | foreach ($data as $field => $value) { |
| 192 | foreach ($data as $field => $value) { |
| 193 | $field = (string) $field; |
| 194 | $definition = $this->shape->fields[$field] ?? null; |
| 195 | $value = $definition instanceof Field |
| 196 | ? $this->readFieldValue($field, $definition, $value, $data, $listIndex) |
| 197 | : $this->readExtraValue($field, $value, $listIndex); |
| 198 | |
| 199 | if ($value !== null) { |
| 200 | $values[$field] = $value; |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | return $values; |
| 205 | } |
| 187 | private function readFromData(array $data, string|int|null $listIndex = null): array |
| 188 | { |
| 189 | $values = []; |
| 190 | |
| 191 | /** @var mixed $value */ |
| 192 | foreach ($data as $field => $value) { |
| 192 | foreach ($data as $field => $value) { |
| 193 | $field = (string) $field; |
| 194 | $definition = $this->shape->fields[$field] ?? null; |
| 195 | $value = $definition instanceof Field |
| 196 | ? $this->readFieldValue($field, $definition, $value, $data, $listIndex) |
| 197 | : $this->readExtraValue($field, $value, $listIndex); |
| 198 | |
| 199 | if ($value !== null) { |
| 200 | $values[$field] = $value; |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | return $values; |
| 205 | } |
| 296 | private function readKnownValue(Field $definition, mixed $value): ReadValue |
| 297 | { |
| 298 | if ($value === null) { |
| 299 | return new ReadValue( |
| 300 | new \Celema\Sire\Value(null, null, true), |
| 301 | $definition->isNullable() ? null : $this->formatNullFailure($definition), |
| 301 | $definition->isNullable() ? null : $this->formatNullFailure($definition), |
| 301 | $definition->isNullable() ? null : $this->formatNullFailure($definition), |
| 296 | private function readKnownValue(Field $definition, mixed $value): ReadValue |
| 297 | { |
| 298 | if ($value === null) { |
| 299 | return new ReadValue( |
| 300 | new \Celema\Sire\Value(null, null, true), |
| 301 | $definition->isNullable() ? null : $this->formatNullFailure($definition), |
| 301 | $definition->isNullable() ? null : $this->formatNullFailure($definition), |
| 301 | $definition->isNullable() ? null : $this->formatNullFailure($definition), |
| 296 | private function readKnownValue(Field $definition, mixed $value): ReadValue |
| 297 | { |
| 298 | if ($value === null) { |
| 305 | if ($definition->type instanceof Contract\Validator) { |
| 306 | return $this->toSubValues($value, $definition, $definition->type); |
| 296 | private function readKnownValue(Field $definition, mixed $value): ReadValue |
| 297 | { |
| 298 | if ($value === null) { |
| 305 | if ($definition->type instanceof Contract\Validator) { |
| 309 | $coercer = $this->shape->coercers->get($definition->type); |
| 310 | |
| 311 | if ($coercer === null) { |
| 312 | throw new ValueError('Wrong shape type'); |
| 296 | private function readKnownValue(Field $definition, mixed $value): ReadValue |
| 297 | { |
| 298 | if ($value === null) { |
| 305 | if ($definition->type instanceof Contract\Validator) { |
| 309 | $coercer = $this->shape->coercers->get($definition->type); |
| 310 | |
| 311 | if ($coercer === null) { |
| 315 | $coercion = $coercer->coerce( |
| 316 | $value, |
| 317 | $definition->coercionMode($this->shape->coercionMode), |
| 318 | ); |
| 319 | |
| 320 | return new ReadValue( |
| 321 | new \Celema\Sire\Value($coercion->value, $coercion->pristine, $coercion->empty), |
| 322 | $this->formatCoercionFailure($coercion, $definition, $coercer), |
| 323 | ); |
| 324 | } |
| 512 | private function readValues(array $data): array |
| 513 | { |
| 514 | if ($this->shape->list) { |
| 515 | $values = []; |
| 516 | |
| 517 | foreach ($data as $listIndex => $item) { |
| 517 | foreach ($data as $listIndex => $item) { |
| 517 | foreach ($data as $listIndex => $item) { |
| 518 | if (!is_array($item)) { |
| 519 | $this->errors->add( |
| 520 | [$listIndex], |
| 521 | new Issue([], 'type.shape', 'Item must be an array'), |
| 522 | ); |
| 523 | $values[] = []; |
| 524 | |
| 525 | continue; |
| 517 | foreach ($data as $listIndex => $item) { |
| 517 | foreach ($data as $listIndex => $item) { |
| 518 | if (!is_array($item)) { |
| 519 | $this->errors->add( |
| 520 | [$listIndex], |
| 521 | new Issue([], 'type.shape', 'Item must be an array'), |
| 522 | ); |
| 523 | $values[] = []; |
| 524 | |
| 525 | continue; |
| 526 | } |
| 527 | |
| 528 | $subValues = $this->readFromData($item, $listIndex); |
| 529 | $values[] = $this->fillMissingFromFields($subValues, $item, $listIndex); |
| 530 | } |
| 531 | |
| 532 | return $values; |
| 512 | private function readValues(array $data): array |
| 513 | { |
| 514 | if ($this->shape->list) { |
| 515 | $values = []; |
| 516 | |
| 517 | foreach ($data as $listIndex => $item) { |
| 517 | foreach ($data as $listIndex => $item) { |
| 517 | foreach ($data as $listIndex => $item) { |
| 518 | if (!is_array($item)) { |
| 517 | foreach ($data as $listIndex => $item) { |
| 518 | if (!is_array($item)) { |
| 519 | $this->errors->add( |
| 520 | [$listIndex], |
| 521 | new Issue([], 'type.shape', 'Item must be an array'), |
| 522 | ); |
| 523 | $values[] = []; |
| 524 | |
| 525 | continue; |
| 526 | } |
| 527 | |
| 528 | $subValues = $this->readFromData($item, $listIndex); |
| 517 | foreach ($data as $listIndex => $item) { |
| 517 | foreach ($data as $listIndex => $item) { |
| 518 | if (!is_array($item)) { |
| 519 | $this->errors->add( |
| 520 | [$listIndex], |
| 521 | new Issue([], 'type.shape', 'Item must be an array'), |
| 522 | ); |
| 523 | $values[] = []; |
| 524 | |
| 525 | continue; |
| 526 | } |
| 527 | |
| 528 | $subValues = $this->readFromData($item, $listIndex); |
| 529 | $values[] = $this->fillMissingFromFields($subValues, $item, $listIndex); |
| 530 | } |
| 531 | |
| 532 | return $values; |
| 512 | private function readValues(array $data): array |
| 513 | { |
| 514 | if ($this->shape->list) { |
| 515 | $values = []; |
| 516 | |
| 517 | foreach ($data as $listIndex => $item) { |
| 517 | foreach ($data as $listIndex => $item) { |
| 517 | foreach ($data as $listIndex => $item) { |
| 518 | if (!is_array($item)) { |
| 519 | $this->errors->add( |
| 520 | [$listIndex], |
| 521 | new Issue([], 'type.shape', 'Item must be an array'), |
| 522 | ); |
| 523 | $values[] = []; |
| 524 | |
| 525 | continue; |
| 526 | } |
| 527 | |
| 528 | $subValues = $this->readFromData($item, $listIndex); |
| 529 | $values[] = $this->fillMissingFromFields($subValues, $item, $listIndex); |
| 530 | } |
| 531 | |
| 532 | return $values; |
| 512 | private function readValues(array $data): array |
| 513 | { |
| 514 | if ($this->shape->list) { |
| 515 | $values = []; |
| 516 | |
| 517 | foreach ($data as $listIndex => $item) { |
| 517 | foreach ($data as $listIndex => $item) { |
| 518 | if (!is_array($item)) { |
| 519 | $this->errors->add( |
| 520 | [$listIndex], |
| 521 | new Issue([], 'type.shape', 'Item must be an array'), |
| 522 | ); |
| 523 | $values[] = []; |
| 524 | |
| 525 | continue; |
| 526 | } |
| 527 | |
| 528 | $subValues = $this->readFromData($item, $listIndex); |
| 529 | $values[] = $this->fillMissingFromFields($subValues, $item, $listIndex); |
| 530 | } |
| 531 | |
| 532 | return $values; |
| 512 | private function readValues(array $data): array |
| 513 | { |
| 514 | if ($this->shape->list) { |
| 535 | $values = $this->readFromData($data); |
| 536 | |
| 537 | return $this->fillMissingFromFields($values, $data); |
| 538 | } |
| 457 | private function review(array $values): void |
| 458 | { |
| 459 | $context = new Review( |
| 460 | $this->errors, |
| 461 | $values, |
| 462 | $this->shape->list, |
| 463 | ); |
| 464 | |
| 465 | foreach ($this->shape->reviewCallbacks as $review) { |
| 465 | foreach ($this->shape->reviewCallbacks as $review) { |
| 465 | foreach ($this->shape->reviewCallbacks as $review) { |
| 466 | $review($context); |
| 465 | foreach ($this->shape->reviewCallbacks as $review) { |
| 465 | foreach ($this->shape->reviewCallbacks as $review) { |
| 466 | $review($context); |
| 467 | } |
| 468 | } |
| 457 | private function review(array $values): void |
| 458 | { |
| 459 | $context = new Review( |
| 460 | $this->errors, |
| 461 | $values, |
| 462 | $this->shape->list, |
| 463 | ); |
| 464 | |
| 465 | foreach ($this->shape->reviewCallbacks as $review) { |
| 465 | foreach ($this->shape->reviewCallbacks as $review) { |
| 465 | foreach ($this->shape->reviewCallbacks as $review) { |
| 466 | $review($context); |
| 467 | } |
| 468 | } |
| 457 | private function review(array $values): void |
| 458 | { |
| 459 | $context = new Review( |
| 460 | $this->errors, |
| 461 | $values, |
| 462 | $this->shape->list, |
| 463 | ); |
| 464 | |
| 465 | foreach ($this->shape->reviewCallbacks as $review) { |
| 465 | foreach ($this->shape->reviewCallbacks as $review) { |
| 466 | $review($context); |
| 467 | } |
| 468 | } |
| 581 | private function rulesBlocked(string $field, string|int|null $listIndex): bool |
| 582 | { |
| 583 | return array_key_exists( |
| 584 | self::pathKey(self::path($field, $listIndex)), |
| 585 | $this->ruleBlockedPaths, |
| 586 | ); |
| 587 | } |
| 431 | mixed $pristine, |
| 432 | Field $definition, |
| 433 | Contract\Validator $shape, |
| 434 | ): ReadValue { |
| 435 | if (!is_array($pristine)) { |
| 436 | return new ReadValue( |
| 437 | new \Celema\Sire\Value($pristine, $pristine, self::isRawEmptyValue($pristine)), |
| 438 | $this->formatShapeFailure($definition, $pristine), |
| 431 | mixed $pristine, |
| 432 | Field $definition, |
| 433 | Contract\Validator $shape, |
| 434 | ): ReadValue { |
| 435 | if (!is_array($pristine)) { |
| 442 | $result = $shape->validate($pristine); |
| 443 | |
| 444 | if ($result->valid()) { |
| 445 | $values = $result->values(); |
| 446 | |
| 447 | return new ReadValue(new \Celema\Sire\Value($values, $pristine, $values === [])); |
| 431 | mixed $pristine, |
| 432 | Field $definition, |
| 433 | Contract\Validator $shape, |
| 434 | ): ReadValue { |
| 435 | if (!is_array($pristine)) { |
| 442 | $result = $shape->validate($pristine); |
| 443 | |
| 444 | if ($result->valid()) { |
| 450 | return new ReadValue( |
| 451 | new \Celema\Sire\Value($pristine, $pristine, self::isRawEmptyValue($pristine)), |
| 452 | nestedResult: $result, |
| 453 | ); |
| 454 | } |
| 27 | $data = $this->prepareData($this->data); |
| 28 | $values = $this->readValues($data); |
| 29 | $validatedValues = []; |
| 30 | |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 41 | if (!$this->errors->hasErrors()) { |
| 42 | $validatedValues = $this->finalizeValues($validatedValues); |
| 43 | } |
| 44 | |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 46 | |
| 47 | if (!$this->errors->hasErrors()) { |
| 48 | $this->review($extractedValues); |
| 49 | } |
| 50 | |
| 51 | return new Result( |
| 51 | return new Result( |
| 52 | $this->errors->issues(), |
| 53 | $extractedValues, |
| 54 | ); |
| 55 | } |
| 27 | $data = $this->prepareData($this->data); |
| 28 | $values = $this->readValues($data); |
| 29 | $validatedValues = []; |
| 30 | |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 41 | if (!$this->errors->hasErrors()) { |
| 42 | $validatedValues = $this->finalizeValues($validatedValues); |
| 43 | } |
| 44 | |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 46 | |
| 47 | if (!$this->errors->hasErrors()) { |
| 51 | return new Result( |
| 52 | $this->errors->issues(), |
| 53 | $extractedValues, |
| 54 | ); |
| 55 | } |
| 27 | $data = $this->prepareData($this->data); |
| 28 | $values = $this->readValues($data); |
| 29 | $validatedValues = []; |
| 30 | |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 41 | if (!$this->errors->hasErrors()) { |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 46 | |
| 47 | if (!$this->errors->hasErrors()) { |
| 48 | $this->review($extractedValues); |
| 49 | } |
| 50 | |
| 51 | return new Result( |
| 51 | return new Result( |
| 52 | $this->errors->issues(), |
| 53 | $extractedValues, |
| 54 | ); |
| 55 | } |
| 27 | $data = $this->prepareData($this->data); |
| 28 | $values = $this->readValues($data); |
| 29 | $validatedValues = []; |
| 30 | |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 41 | if (!$this->errors->hasErrors()) { |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 46 | |
| 47 | if (!$this->errors->hasErrors()) { |
| 51 | return new Result( |
| 52 | $this->errors->issues(), |
| 53 | $extractedValues, |
| 54 | ); |
| 55 | } |
| 27 | $data = $this->prepareData($this->data); |
| 28 | $values = $this->readValues($data); |
| 29 | $validatedValues = []; |
| 30 | |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 41 | if (!$this->errors->hasErrors()) { |
| 42 | $validatedValues = $this->finalizeValues($validatedValues); |
| 43 | } |
| 44 | |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 46 | |
| 47 | if (!$this->errors->hasErrors()) { |
| 48 | $this->review($extractedValues); |
| 49 | } |
| 50 | |
| 51 | return new Result( |
| 51 | return new Result( |
| 52 | $this->errors->issues(), |
| 53 | $extractedValues, |
| 54 | ); |
| 55 | } |
| 27 | $data = $this->prepareData($this->data); |
| 28 | $values = $this->readValues($data); |
| 29 | $validatedValues = []; |
| 30 | |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 41 | if (!$this->errors->hasErrors()) { |
| 42 | $validatedValues = $this->finalizeValues($validatedValues); |
| 43 | } |
| 44 | |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 46 | |
| 47 | if (!$this->errors->hasErrors()) { |
| 51 | return new Result( |
| 52 | $this->errors->issues(), |
| 53 | $extractedValues, |
| 54 | ); |
| 55 | } |
| 27 | $data = $this->prepareData($this->data); |
| 28 | $values = $this->readValues($data); |
| 29 | $validatedValues = []; |
| 30 | |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 41 | if (!$this->errors->hasErrors()) { |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 46 | |
| 47 | if (!$this->errors->hasErrors()) { |
| 48 | $this->review($extractedValues); |
| 49 | } |
| 50 | |
| 51 | return new Result( |
| 51 | return new Result( |
| 52 | $this->errors->issues(), |
| 53 | $extractedValues, |
| 54 | ); |
| 55 | } |
| 27 | $data = $this->prepareData($this->data); |
| 28 | $values = $this->readValues($data); |
| 29 | $validatedValues = []; |
| 30 | |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 41 | if (!$this->errors->hasErrors()) { |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 46 | |
| 47 | if (!$this->errors->hasErrors()) { |
| 51 | return new Result( |
| 52 | $this->errors->issues(), |
| 53 | $extractedValues, |
| 54 | ); |
| 55 | } |
| 27 | $data = $this->prepareData($this->data); |
| 28 | $values = $this->readValues($data); |
| 29 | $validatedValues = []; |
| 30 | |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 41 | if (!$this->errors->hasErrors()) { |
| 42 | $validatedValues = $this->finalizeValues($validatedValues); |
| 43 | } |
| 44 | |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 46 | |
| 47 | if (!$this->errors->hasErrors()) { |
| 48 | $this->review($extractedValues); |
| 49 | } |
| 50 | |
| 51 | return new Result( |
| 51 | return new Result( |
| 52 | $this->errors->issues(), |
| 53 | $extractedValues, |
| 54 | ); |
| 55 | } |
| 27 | $data = $this->prepareData($this->data); |
| 28 | $values = $this->readValues($data); |
| 29 | $validatedValues = []; |
| 30 | |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 41 | if (!$this->errors->hasErrors()) { |
| 42 | $validatedValues = $this->finalizeValues($validatedValues); |
| 43 | } |
| 44 | |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 46 | |
| 47 | if (!$this->errors->hasErrors()) { |
| 51 | return new Result( |
| 52 | $this->errors->issues(), |
| 53 | $extractedValues, |
| 54 | ); |
| 55 | } |
| 27 | $data = $this->prepareData($this->data); |
| 28 | $values = $this->readValues($data); |
| 29 | $validatedValues = []; |
| 30 | |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 41 | if (!$this->errors->hasErrors()) { |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 46 | |
| 47 | if (!$this->errors->hasErrors()) { |
| 48 | $this->review($extractedValues); |
| 49 | } |
| 50 | |
| 51 | return new Result( |
| 51 | return new Result( |
| 52 | $this->errors->issues(), |
| 53 | $extractedValues, |
| 54 | ); |
| 55 | } |
| 27 | $data = $this->prepareData($this->data); |
| 28 | $values = $this->readValues($data); |
| 29 | $validatedValues = []; |
| 30 | |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 31 | if ($this->shape->list) { |
| 32 | foreach ($values as $listIndex => $subValues) { |
| 41 | if (!$this->errors->hasErrors()) { |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 46 | |
| 47 | if (!$this->errors->hasErrors()) { |
| 51 | return new Result( |
| 52 | $this->errors->issues(), |
| 53 | $extractedValues, |
| 54 | ); |
| 55 | } |
| 27 | $data = $this->prepareData($this->data); |
| 28 | $values = $this->readValues($data); |
| 29 | $validatedValues = []; |
| 30 | |
| 31 | if ($this->shape->list) { |
| 38 | $validatedValues = $this->validateItem($values); |
| 39 | } |
| 40 | |
| 41 | if (!$this->errors->hasErrors()) { |
| 41 | if (!$this->errors->hasErrors()) { |
| 42 | $validatedValues = $this->finalizeValues($validatedValues); |
| 43 | } |
| 44 | |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 46 | |
| 47 | if (!$this->errors->hasErrors()) { |
| 48 | $this->review($extractedValues); |
| 49 | } |
| 50 | |
| 51 | return new Result( |
| 51 | return new Result( |
| 52 | $this->errors->issues(), |
| 53 | $extractedValues, |
| 54 | ); |
| 55 | } |
| 27 | $data = $this->prepareData($this->data); |
| 28 | $values = $this->readValues($data); |
| 29 | $validatedValues = []; |
| 30 | |
| 31 | if ($this->shape->list) { |
| 38 | $validatedValues = $this->validateItem($values); |
| 39 | } |
| 40 | |
| 41 | if (!$this->errors->hasErrors()) { |
| 41 | if (!$this->errors->hasErrors()) { |
| 42 | $validatedValues = $this->finalizeValues($validatedValues); |
| 43 | } |
| 44 | |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 46 | |
| 47 | if (!$this->errors->hasErrors()) { |
| 51 | return new Result( |
| 52 | $this->errors->issues(), |
| 53 | $extractedValues, |
| 54 | ); |
| 55 | } |
| 27 | $data = $this->prepareData($this->data); |
| 28 | $values = $this->readValues($data); |
| 29 | $validatedValues = []; |
| 30 | |
| 31 | if ($this->shape->list) { |
| 38 | $validatedValues = $this->validateItem($values); |
| 39 | } |
| 40 | |
| 41 | if (!$this->errors->hasErrors()) { |
| 41 | if (!$this->errors->hasErrors()) { |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 46 | |
| 47 | if (!$this->errors->hasErrors()) { |
| 48 | $this->review($extractedValues); |
| 49 | } |
| 50 | |
| 51 | return new Result( |
| 51 | return new Result( |
| 52 | $this->errors->issues(), |
| 53 | $extractedValues, |
| 54 | ); |
| 55 | } |
| 27 | $data = $this->prepareData($this->data); |
| 28 | $values = $this->readValues($data); |
| 29 | $validatedValues = []; |
| 30 | |
| 31 | if ($this->shape->list) { |
| 38 | $validatedValues = $this->validateItem($values); |
| 39 | } |
| 40 | |
| 41 | if (!$this->errors->hasErrors()) { |
| 41 | if (!$this->errors->hasErrors()) { |
| 45 | $extractedValues = $this->extractValues($validatedValues); |
| 46 | |
| 47 | if (!$this->errors->hasErrors()) { |
| 51 | return new Result( |
| 52 | $this->errors->issues(), |
| 53 | $extractedValues, |
| 54 | ); |
| 55 | } |
| 71 | Field $definition, |
| 72 | Value $value, |
| 73 | string $ruleDefinition, |
| 74 | string|int|null $listIndex, |
| 75 | ): void { |
| 76 | $parsedRule = $this->shape->ruleParser->parse($ruleDefinition); |
| 77 | $ruleName = $parsedRule['name']; |
| 78 | $ruleArgs = $parsedRule['args']; |
| 79 | |
| 80 | $rule = $this->shape->rules->get($ruleName); |
| 81 | |
| 82 | if ($rule === null) { |
| 83 | throw new ValueError( |
| 84 | sprintf('Unknown rule "%s" in field "%s"', $ruleName, $definition->field), |
| 71 | Field $definition, |
| 72 | Value $value, |
| 73 | string $ruleDefinition, |
| 74 | string|int|null $listIndex, |
| 75 | ): void { |
| 76 | $parsedRule = $this->shape->ruleParser->parse($ruleDefinition); |
| 77 | $ruleName = $parsedRule['name']; |
| 78 | $ruleArgs = $parsedRule['args']; |
| 79 | |
| 80 | $rule = $this->shape->rules->get($ruleName); |
| 81 | |
| 82 | if ($rule === null) { |
| 88 | if ($this->rulesBlocked($definition->field, $listIndex)) { |
| 89 | return; |
| 71 | Field $definition, |
| 72 | Value $value, |
| 73 | string $ruleDefinition, |
| 74 | string|int|null $listIndex, |
| 75 | ): void { |
| 76 | $parsedRule = $this->shape->ruleParser->parse($ruleDefinition); |
| 77 | $ruleName = $parsedRule['name']; |
| 78 | $ruleArgs = $parsedRule['args']; |
| 79 | |
| 80 | $rule = $this->shape->rules->get($ruleName); |
| 81 | |
| 82 | if ($rule === null) { |
| 88 | if ($this->rulesBlocked($definition->field, $listIndex)) { |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 93 | return; |
| 71 | Field $definition, |
| 72 | Value $value, |
| 73 | string $ruleDefinition, |
| 74 | string|int|null $listIndex, |
| 75 | ): void { |
| 76 | $parsedRule = $this->shape->ruleParser->parse($ruleDefinition); |
| 77 | $ruleName = $parsedRule['name']; |
| 78 | $ruleArgs = $parsedRule['args']; |
| 79 | |
| 80 | $rule = $this->shape->rules->get($ruleName); |
| 81 | |
| 82 | if ($rule === null) { |
| 88 | if ($this->rulesBlocked($definition->field, $listIndex)) { |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 96 | $validation = $rule->validate($value, ...$ruleArgs); |
| 97 | |
| 98 | if ($validation->failure !== null) { |
| 99 | $this->errors->add( |
| 100 | self::path($definition->field, $listIndex), |
| 101 | $this->formatFailure( |
| 102 | $validation->failure, |
| 103 | $definition->name(), |
| 104 | $definition->field, |
| 105 | $value->pristine, |
| 106 | 'rule.' . $ruleName, |
| 107 | $rule->message, |
| 108 | $rule instanceof Contract\NamedArguments |
| 109 | ? Arguments::named($ruleArgs, $rule) |
| 110 | : $ruleArgs, |
| 111 | $definition->messageOverrides(), |
| 112 | ), |
| 113 | ); |
| 114 | } |
| 115 | } |
| 115 | } |
| 71 | Field $definition, |
| 72 | Value $value, |
| 73 | string $ruleDefinition, |
| 74 | string|int|null $listIndex, |
| 75 | ): void { |
| 76 | $parsedRule = $this->shape->ruleParser->parse($ruleDefinition); |
| 77 | $ruleName = $parsedRule['name']; |
| 78 | $ruleArgs = $parsedRule['args']; |
| 79 | |
| 80 | $rule = $this->shape->rules->get($ruleName); |
| 81 | |
| 82 | if ($rule === null) { |
| 88 | if ($this->rulesBlocked($definition->field, $listIndex)) { |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 96 | $validation = $rule->validate($value, ...$ruleArgs); |
| 97 | |
| 98 | if ($validation->failure !== null) { |
| 99 | $this->errors->add( |
| 100 | self::path($definition->field, $listIndex), |
| 101 | $this->formatFailure( |
| 102 | $validation->failure, |
| 103 | $definition->name(), |
| 104 | $definition->field, |
| 105 | $value->pristine, |
| 106 | 'rule.' . $ruleName, |
| 107 | $rule->message, |
| 108 | $rule instanceof Contract\NamedArguments |
| 110 | : $ruleArgs, |
| 110 | : $ruleArgs, |
| 111 | $definition->messageOverrides(), |
| 112 | ), |
| 113 | ); |
| 114 | } |
| 115 | } |
| 115 | } |
| 71 | Field $definition, |
| 72 | Value $value, |
| 73 | string $ruleDefinition, |
| 74 | string|int|null $listIndex, |
| 75 | ): void { |
| 76 | $parsedRule = $this->shape->ruleParser->parse($ruleDefinition); |
| 77 | $ruleName = $parsedRule['name']; |
| 78 | $ruleArgs = $parsedRule['args']; |
| 79 | |
| 80 | $rule = $this->shape->rules->get($ruleName); |
| 81 | |
| 82 | if ($rule === null) { |
| 88 | if ($this->rulesBlocked($definition->field, $listIndex)) { |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 96 | $validation = $rule->validate($value, ...$ruleArgs); |
| 97 | |
| 98 | if ($validation->failure !== null) { |
| 115 | } |
| 71 | Field $definition, |
| 72 | Value $value, |
| 73 | string $ruleDefinition, |
| 74 | string|int|null $listIndex, |
| 75 | ): void { |
| 76 | $parsedRule = $this->shape->ruleParser->parse($ruleDefinition); |
| 77 | $ruleName = $parsedRule['name']; |
| 78 | $ruleArgs = $parsedRule['args']; |
| 79 | |
| 80 | $rule = $this->shape->rules->get($ruleName); |
| 81 | |
| 82 | if ($rule === null) { |
| 88 | if ($this->rulesBlocked($definition->field, $listIndex)) { |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 93 | return; |
| 71 | Field $definition, |
| 72 | Value $value, |
| 73 | string $ruleDefinition, |
| 74 | string|int|null $listIndex, |
| 75 | ): void { |
| 76 | $parsedRule = $this->shape->ruleParser->parse($ruleDefinition); |
| 77 | $ruleName = $parsedRule['name']; |
| 78 | $ruleArgs = $parsedRule['args']; |
| 79 | |
| 80 | $rule = $this->shape->rules->get($ruleName); |
| 81 | |
| 82 | if ($rule === null) { |
| 88 | if ($this->rulesBlocked($definition->field, $listIndex)) { |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 96 | $validation = $rule->validate($value, ...$ruleArgs); |
| 97 | |
| 98 | if ($validation->failure !== null) { |
| 99 | $this->errors->add( |
| 100 | self::path($definition->field, $listIndex), |
| 101 | $this->formatFailure( |
| 102 | $validation->failure, |
| 103 | $definition->name(), |
| 104 | $definition->field, |
| 105 | $value->pristine, |
| 106 | 'rule.' . $ruleName, |
| 107 | $rule->message, |
| 108 | $rule instanceof Contract\NamedArguments |
| 109 | ? Arguments::named($ruleArgs, $rule) |
| 110 | : $ruleArgs, |
| 111 | $definition->messageOverrides(), |
| 112 | ), |
| 113 | ); |
| 114 | } |
| 115 | } |
| 115 | } |
| 71 | Field $definition, |
| 72 | Value $value, |
| 73 | string $ruleDefinition, |
| 74 | string|int|null $listIndex, |
| 75 | ): void { |
| 76 | $parsedRule = $this->shape->ruleParser->parse($ruleDefinition); |
| 77 | $ruleName = $parsedRule['name']; |
| 78 | $ruleArgs = $parsedRule['args']; |
| 79 | |
| 80 | $rule = $this->shape->rules->get($ruleName); |
| 81 | |
| 82 | if ($rule === null) { |
| 88 | if ($this->rulesBlocked($definition->field, $listIndex)) { |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 96 | $validation = $rule->validate($value, ...$ruleArgs); |
| 97 | |
| 98 | if ($validation->failure !== null) { |
| 99 | $this->errors->add( |
| 100 | self::path($definition->field, $listIndex), |
| 101 | $this->formatFailure( |
| 102 | $validation->failure, |
| 103 | $definition->name(), |
| 104 | $definition->field, |
| 105 | $value->pristine, |
| 106 | 'rule.' . $ruleName, |
| 107 | $rule->message, |
| 108 | $rule instanceof Contract\NamedArguments |
| 110 | : $ruleArgs, |
| 110 | : $ruleArgs, |
| 111 | $definition->messageOverrides(), |
| 112 | ), |
| 113 | ); |
| 114 | } |
| 115 | } |
| 115 | } |
| 71 | Field $definition, |
| 72 | Value $value, |
| 73 | string $ruleDefinition, |
| 74 | string|int|null $listIndex, |
| 75 | ): void { |
| 76 | $parsedRule = $this->shape->ruleParser->parse($ruleDefinition); |
| 77 | $ruleName = $parsedRule['name']; |
| 78 | $ruleArgs = $parsedRule['args']; |
| 79 | |
| 80 | $rule = $this->shape->rules->get($ruleName); |
| 81 | |
| 82 | if ($rule === null) { |
| 88 | if ($this->rulesBlocked($definition->field, $listIndex)) { |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 92 | if (!$rule instanceof Contract\ValidatesEmpty && $value->empty) { |
| 96 | $validation = $rule->validate($value, ...$ruleArgs); |
| 97 | |
| 98 | if ($validation->failure !== null) { |
| 115 | } |
| 544 | private function validateItem(array $values, string|int|null $listIndex = null): array |
| 545 | { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 547 | if (!array_key_exists($field, $values)) { |
| 548 | continue; |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 547 | if (!array_key_exists($field, $values)) { |
| 548 | continue; |
| 549 | } |
| 550 | |
| 551 | foreach ($definition->rules as $rule) { |
| 552 | $this->validateField( |
| 553 | $definition, |
| 554 | $values[$field], |
| 555 | $rule, |
| 556 | $listIndex, |
| 557 | ); |
| 558 | } |
| 559 | } |
| 560 | |
| 561 | return $values; |
| 562 | } |
| 544 | private function validateItem(array $values, string|int|null $listIndex = null): array |
| 545 | { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 547 | if (!array_key_exists($field, $values)) { |
| 551 | foreach ($definition->rules as $rule) { |
| 551 | foreach ($definition->rules as $rule) { |
| 551 | foreach ($definition->rules as $rule) { |
| 552 | $this->validateField( |
| 551 | foreach ($definition->rules as $rule) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 547 | if (!array_key_exists($field, $values)) { |
| 548 | continue; |
| 549 | } |
| 550 | |
| 551 | foreach ($definition->rules as $rule) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 547 | if (!array_key_exists($field, $values)) { |
| 548 | continue; |
| 549 | } |
| 550 | |
| 551 | foreach ($definition->rules as $rule) { |
| 552 | $this->validateField( |
| 553 | $definition, |
| 554 | $values[$field], |
| 555 | $rule, |
| 556 | $listIndex, |
| 557 | ); |
| 558 | } |
| 559 | } |
| 560 | |
| 561 | return $values; |
| 562 | } |
| 544 | private function validateItem(array $values, string|int|null $listIndex = null): array |
| 545 | { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 547 | if (!array_key_exists($field, $values)) { |
| 551 | foreach ($definition->rules as $rule) { |
| 551 | foreach ($definition->rules as $rule) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 547 | if (!array_key_exists($field, $values)) { |
| 548 | continue; |
| 549 | } |
| 550 | |
| 551 | foreach ($definition->rules as $rule) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 547 | if (!array_key_exists($field, $values)) { |
| 548 | continue; |
| 549 | } |
| 550 | |
| 551 | foreach ($definition->rules as $rule) { |
| 552 | $this->validateField( |
| 553 | $definition, |
| 554 | $values[$field], |
| 555 | $rule, |
| 556 | $listIndex, |
| 557 | ); |
| 558 | } |
| 559 | } |
| 560 | |
| 561 | return $values; |
| 562 | } |
| 544 | private function validateItem(array $values, string|int|null $listIndex = null): array |
| 545 | { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 547 | if (!array_key_exists($field, $values)) { |
| 551 | foreach ($definition->rules as $rule) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 547 | if (!array_key_exists($field, $values)) { |
| 548 | continue; |
| 549 | } |
| 550 | |
| 551 | foreach ($definition->rules as $rule) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 547 | if (!array_key_exists($field, $values)) { |
| 548 | continue; |
| 549 | } |
| 550 | |
| 551 | foreach ($definition->rules as $rule) { |
| 552 | $this->validateField( |
| 553 | $definition, |
| 554 | $values[$field], |
| 555 | $rule, |
| 556 | $listIndex, |
| 557 | ); |
| 558 | } |
| 559 | } |
| 560 | |
| 561 | return $values; |
| 562 | } |
| 544 | private function validateItem(array $values, string|int|null $listIndex = null): array |
| 545 | { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 547 | if (!array_key_exists($field, $values)) { |
| 548 | continue; |
| 549 | } |
| 550 | |
| 551 | foreach ($definition->rules as $rule) { |
| 552 | $this->validateField( |
| 553 | $definition, |
| 554 | $values[$field], |
| 555 | $rule, |
| 556 | $listIndex, |
| 557 | ); |
| 558 | } |
| 559 | } |
| 560 | |
| 561 | return $values; |
| 562 | } |
| 544 | private function validateItem(array $values, string|int|null $listIndex = null): array |
| 545 | { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 546 | foreach ($this->shape->fields as $field => $definition) { |
| 547 | if (!array_key_exists($field, $values)) { |
| 548 | continue; |
| 549 | } |
| 550 | |
| 551 | foreach ($definition->rules as $rule) { |
| 552 | $this->validateField( |
| 553 | $definition, |
| 554 | $values[$field], |
| 555 | $rule, |
| 556 | $listIndex, |
| 557 | ); |
| 558 | } |
| 559 | } |
| 560 | |
| 561 | return $values; |
| 562 | } |
| 571 | static fn(Value $item): mixed => $item->value, |