Skip to content

Commit cc5016f

Browse files
author
Enno Woortmann
committed
Improve exception message for invalid array items
1 parent 154be05 commit cc5016f

File tree

8 files changed

+240
-73
lines changed

8 files changed

+240
-73
lines changed

docs/source/complexTypes/array.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ The items of a list can be restricted with a nested schema. All items of the sch
5454
5555
With a schema like this all items must contain a string with at least two characters. Possible exceptions:
5656

57-
* Invalid item in array example
57+
.. code-block:: none
58+
59+
Invalid item in array example:
60+
- invalid item #3
61+
* Invalid type for item of array example. Requires string, got double
5862
5963
A more complex array may contain a nested object.
6064

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace PHPModelGenerator\Model\Validator;
6+
7+
use PHPMicroTemplate\Exception\FileSystemException;
8+
use PHPMicroTemplate\Exception\SyntaxErrorException;
9+
use PHPMicroTemplate\Exception\UndefinedSymbolException;
10+
use PHPModelGenerator\Exception\SchemaException;
11+
use PHPModelGenerator\Model\Property\PropertyInterface;
12+
use PHPModelGenerator\Model\Schema;
13+
use PHPModelGenerator\PropertyProcessor\Decorator\TypeHint\ArrayTypeHintDecorator;
14+
use PHPModelGenerator\PropertyProcessor\PropertyCollectionProcessor;
15+
use PHPModelGenerator\PropertyProcessor\PropertyFactory;
16+
use PHPModelGenerator\PropertyProcessor\PropertyProcessorFactory;
17+
use PHPModelGenerator\SchemaProcessor\SchemaProcessor;
18+
use PHPModelGenerator\Utils\RenderHelper;
19+
20+
/**
21+
* Class ArrayItemValidator
22+
*
23+
* @package PHPModelGenerator\Model\Validator
24+
*/
25+
class ArrayItemValidator extends PropertyTemplateValidator
26+
{
27+
private $variableSuffix = '';
28+
29+
/**
30+
* ArrayItemValidator constructor.
31+
*
32+
* @param SchemaProcessor $schemaProcessor
33+
* @param Schema $schema
34+
* @param array $itemStructure
35+
* @param PropertyInterface $property
36+
*
37+
* @throws SchemaException
38+
* @throws FileSystemException
39+
* @throws SyntaxErrorException
40+
* @throws UndefinedSymbolException
41+
*/
42+
public function __construct(
43+
SchemaProcessor $schemaProcessor,
44+
Schema $schema,
45+
array $itemStructure,
46+
PropertyInterface $property
47+
) {
48+
$this->variableSuffix = '_' . uniqid();
49+
50+
// an item of the array behaves like a nested property to add item-level validation
51+
$nestedProperty = (new PropertyFactory(new PropertyProcessorFactory()))
52+
->create(
53+
new PropertyCollectionProcessor(),
54+
$schemaProcessor,
55+
$schema,
56+
"item of array {$property->getName()}",
57+
$itemStructure
58+
);
59+
60+
$property->addTypeHintDecorator(new ArrayTypeHintDecorator($nestedProperty));
61+
62+
parent::__construct(
63+
$this->getRenderer()->renderTemplate(
64+
DIRECTORY_SEPARATOR . 'Exception' . DIRECTORY_SEPARATOR . 'InvalidArrayItemsException.phptpl',
65+
['propertyName' => $property->getName(), 'suffix' => $this->variableSuffix]
66+
),
67+
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'ArrayItem.phptpl',
68+
[
69+
'nestedProperty' => $nestedProperty,
70+
'viewHelper' => new RenderHelper($schemaProcessor->getGeneratorConfiguration()),
71+
'generatorConfiguration' => $schemaProcessor->getGeneratorConfiguration(),
72+
'suffix' => $this->variableSuffix,
73+
]
74+
);
75+
}
76+
77+
/**
78+
* Initialize all variables which are required to execute a property names validator
79+
*
80+
* @return string
81+
*/
82+
public function getValidatorSetUp(): string
83+
{
84+
return "\$invalidItems{$this->variableSuffix} = [];";
85+
}
86+
}

src/PropertyProcessor/Property/ArrayProcessor.php

Lines changed: 20 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
use PHPModelGenerator\Exception\SchemaException;
1111
use PHPModelGenerator\Model\Property\PropertyInterface;
1212
use PHPModelGenerator\Model\Validator\AdditionalItemsValidator;
13+
use PHPModelGenerator\Model\Validator\ArrayItemValidator;
1314
use PHPModelGenerator\Model\Validator\ArrayTupleValidator;
1415
use PHPModelGenerator\Model\Validator\PropertyTemplateValidator;
1516
use PHPModelGenerator\Model\Validator\PropertyValidator;
16-
use PHPModelGenerator\PropertyProcessor\Decorator\TypeHint\ArrayTypeHintDecorator;
1717
use PHPModelGenerator\PropertyProcessor\PropertyCollectionProcessor;
1818
use PHPModelGenerator\PropertyProcessor\PropertyFactory;
1919
use PHPModelGenerator\PropertyProcessor\PropertyProcessorFactory;
@@ -128,12 +128,13 @@ private function addItemsValidation(PropertyInterface $property, array $property
128128
return;
129129
}
130130

131-
// TODO: more detailed exception including the violations
132-
$this->addItemValidator(
133-
$property,
134-
$propertyData[self::JSON_FIELD_ITEMS],
135-
'ArrayItem.phptpl',
136-
"Invalid item in array {$property->getName()}"
131+
$property->addValidator(
132+
new ArrayItemValidator(
133+
$this->schemaProcessor,
134+
$this->schema,
135+
$propertyData[self::JSON_FIELD_ITEMS],
136+
$property
137+
)
137138
);
138139
}
139140

@@ -216,52 +217,26 @@ private function addContainsValidation(PropertyInterface $property, array $prope
216217
return;
217218
}
218219

219-
$this->addItemValidator(
220-
$property,
221-
$propertyData[self::JSON_FIELD_CONTAINS],
222-
'ArrayContains.phptpl',
223-
"No item in array {$property->getName()} matches contains constraint"
224-
);
225-
}
226-
227-
/**
228-
* Add an item based validator
229-
*
230-
* @param PropertyInterface $property
231-
* @param array $propertyData
232-
* @param string $template
233-
* @param string $message
234-
*
235-
* @throws SchemaException
236-
*/
237-
private function addItemValidator(
238-
PropertyInterface $property,
239-
array $propertyData,
240-
string $template,
241-
string $message
242-
) {
243220
// an item of the array behaves like a nested property to add item-level validation
244221
$nestedProperty = (new PropertyFactory(new PropertyProcessorFactory()))
245222
->create(
246223
new PropertyCollectionProcessor(),
247224
$this->schemaProcessor,
248225
$this->schema,
249226
"item of array {$property->getName()}",
250-
$propertyData
227+
$propertyData[self::JSON_FIELD_CONTAINS]
251228
);
252229

253-
$property
254-
->addTypeHintDecorator(new ArrayTypeHintDecorator($nestedProperty))
255-
->addValidator(
256-
new PropertyTemplateValidator(
257-
$message,
258-
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . $template,
259-
[
260-
'property' => $nestedProperty,
261-
'viewHelper' => new RenderHelper($this->schemaProcessor->getGeneratorConfiguration()),
262-
'generatorConfiguration' => $this->schemaProcessor->getGeneratorConfiguration(),
263-
]
264-
)
265-
);
230+
$property->addValidator(
231+
new PropertyTemplateValidator(
232+
"No item in array {$property->getName()} matches contains constraint",
233+
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'ArrayContains.phptpl',
234+
[
235+
'property' => $nestedProperty,
236+
'viewHelper' => new RenderHelper($this->schemaProcessor->getGeneratorConfiguration()),
237+
'generatorConfiguration' => $this->schemaProcessor->getGeneratorConfiguration(),
238+
]
239+
)
240+
);
266241
}
267242
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Invalid item in array {{ propertyName }}:"
2+
. (function (array $invalidItems) {
3+
$output = '';
4+
foreach ($invalidItems as $index => $errors) {
5+
$output .= "\n - invalid item #$index\n * " . implode("\n * ", $errors);
6+
}
7+
return $output;
8+
})($invalidItems{{ suffix }}) . "
Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,39 @@
1-
is_array($value) && (function (&$items) {
2-
if (empty($items)) {
3-
return false;
4-
}
1+
is_array($value) && (function (&$items) use (&$invalidItems{{ suffix }}) {
2+
{% if generatorConfiguration.collectErrors() %}
3+
$originalErrorRegistry = $this->errorRegistry;
4+
{% endif%}
5+
6+
foreach ($items as $index => &$value) {
7+
{% if generatorConfiguration.collectErrors() %}
8+
$this->errorRegistry = new {{ viewHelper.getSimpleClassName(generatorConfiguration.getErrorRegistryClass()) }}();
9+
{% endif%}
510

6-
foreach ($items as &$value) {
7-
{{ viewHelper.resolvePropertyDecorator(property) }}
11+
try {
12+
{{ viewHelper.resolvePropertyDecorator(nestedProperty) }}
813

9-
{% foreach property.getOrderedValidators() as validator %}
10-
{{ validator.getValidatorSetUp() }}
11-
if ({{ validator.getCheck() }}) {
12-
{{ viewHelper.validationError(validator.getExceptionMessage()) }}
13-
}
14-
{% endforeach %}
14+
{% foreach nestedProperty.getOrderedValidators() as validator %}
15+
{{ validator.getValidatorSetUp() }}
16+
if ({{ validator.getCheck() }}) {
17+
{{ viewHelper.validationError(validator.getExceptionMessage()) }}
18+
}
19+
{% endforeach %}
20+
21+
{% if generatorConfiguration.collectErrors() %}
22+
if ($this->errorRegistry->getErrors()) {
23+
$invalidItems{{ suffix }}[$index] = $this->errorRegistry->getErrors();
24+
}
25+
{% endif %}
26+
} catch (\Exception $e) {
27+
// collect all errors concerning invalid items
28+
isset($invalidItems{{ suffix }}[$index])
29+
? $invalidItems{{ suffix }}[$index][] = $e->getMessage()
30+
: $invalidItems{{ suffix }}[$index] = [$e->getMessage()];
31+
}
1532
}
1633

17-
return false;
34+
{% if generatorConfiguration.collectErrors() %}
35+
$this->errorRegistry = $originalErrorRegistry;
36+
{% endif %}
37+
38+
return !empty($invalidItems{{ suffix }});
1839
})($value)

src/Templates/Validator/ArrayTuple.phptpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ is_array($value) && (function (&$items) use (&$invalidTuples) {
3131
}
3232
{% endif %}
3333
} catch (\Exception $e) {
34-
// collect all errors concerning invalid property names
34+
// collect all errors concerning invalid tuples
3535
isset($invalidTuples[$index])
3636
? $invalidTuples[$index][] = $e->getMessage()
3737
: $invalidTuples[$index] = [$e->getMessage()];

tests/Objects/ArrayPropertyTest.php

Lines changed: 85 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,9 @@ public function validTypedArrayDataProvider(): array
322322
* @dataProvider invalidTypedArrayDataProvider
323323
*
324324
* @param GeneratorConfiguration $configuration
325-
* @param string $type
326-
* @param $propertyValue
325+
* @param string $type
326+
* @param $propertyValue
327+
* @param string $message
327328
*
328329
* @throws FileSystemException
329330
* @throws RenderException
@@ -332,9 +333,10 @@ public function validTypedArrayDataProvider(): array
332333
public function testInvalidTypedArrayThrowsAnException(
333334
GeneratorConfiguration $configuration,
334335
string $type,
335-
$propertyValue
336+
$propertyValue,
337+
string $message = ''
336338
): void {
337-
$this->expectValidationError($configuration, 'Invalid type for item of array');
339+
$this->expectValidationError($configuration, $message);
338340

339341
$className = $this->generateClassFromFileTemplate('ArrayPropertyTyped.json', [$type], $configuration, false);
340342

@@ -346,19 +348,90 @@ public function invalidTypedArrayDataProvider(): array
346348
return $this->combineDataProvider(
347349
$this->validationMethodDataProvider(),
348350
[
349-
'String array containing int' => ['string', ['a', 'b', 1]],
350-
'Int array containing string' => ['integer', [1, 2, 3, '4']],
351-
'Int array containing float' => ['integer', [1, 2, 3, 2.5]],
352-
'Number array containing array' => ['number', [1, 1.1, 4.5, 6, []]],
353-
'Boolean array containing int' => ['boolean', [true, false, true, 3]],
354-
'Null array containing string' => ['null', [null, null, 'null']],
351+
'String array containing int' => [
352+
'string',
353+
['a', 'b', 1],
354+
<<<ERROR
355+
Invalid item in array property:
356+
- invalid item #2
357+
* Invalid type for item of array property. Requires string, got integer
358+
ERROR
359+
],
360+
'Int array containing string' => [
361+
'integer',
362+
[1, 2, 3, '4'],
363+
<<<ERROR
364+
Invalid item in array property:
365+
- invalid item #3
366+
* Invalid type for item of array property. Requires int, got string
367+
ERROR
368+
],
369+
'Int array containing float' => [
370+
'integer',
371+
[1, 2, 3, 2.5],
372+
<<<ERROR
373+
Invalid item in array property:
374+
- invalid item #3
375+
* Invalid type for item of array property. Requires int, got double
376+
ERROR
377+
],
378+
'Number array containing array' => [
379+
'number',
380+
[1, 1.1, 4.5, 6, []],
381+
<<<ERROR
382+
Invalid item in array property:
383+
- invalid item #4
384+
* Invalid type for item of array property. Requires float, got array
385+
ERROR
386+
],
387+
'Boolean array containing int' => [
388+
'boolean',
389+
[true, false, true, 3],
390+
<<<ERROR
391+
Invalid item in array property:
392+
- invalid item #3
393+
* Invalid type for item of array property. Requires bool, got integer
394+
ERROR
395+
],
396+
'Null array containing string' => [
397+
'null',
398+
[null, null, 'null'],
399+
<<<ERROR
400+
Invalid item in array property:
401+
- invalid item #2
402+
* Invalid type for item of array property. Requires null, got string
403+
ERROR
404+
],
405+
'Multiple violations' => [
406+
'boolean',
407+
[true, false, true, 3, true, 'true'],
408+
<<<ERROR
409+
Invalid item in array property:
410+
- invalid item #3
411+
* Invalid type for item of array property. Requires bool, got integer
412+
- invalid item #5
413+
* Invalid type for item of array property. Requires bool, got string
414+
ERROR
415+
],
355416
'Nested array containing int' => [
356417
'array","items":{"type":"integer"},"injection":"yes we can',
357-
[[1, 2], [], 3]
418+
[[1, 2], [], 3],
419+
<<<ERROR
420+
Invalid item in array property:
421+
- invalid item #2
422+
* Invalid type for item of array property. Requires array, got integer
423+
ERROR
358424
],
359425
'Nested array inner array containing string' => [
360426
'array","items":{"type":"integer"},"injection":"yes we can',
361-
[[1, '2'], [], [3]]
427+
[[1, '2'], [], [3]],
428+
<<<ERROR
429+
Invalid item in array property:
430+
- invalid item #0
431+
* Invalid item in array item of array property:
432+
- invalid item #1
433+
* Invalid type for item of array item of array property. Requires int, got string
434+
ERROR
362435
]
363436
]
364437
);

0 commit comments

Comments
 (0)