Skip to content

Commit b21ac59

Browse files
committed
Don't execute enum validations for properties with a transforming filter if the input value is an already transformed value
1 parent 2d10675 commit b21ac59

File tree

12 files changed

+160
-37
lines changed

12 files changed

+160
-37
lines changed

docs/source/nonStandardExtensions/filter.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ Transforming filter
8080

8181
You may keep it simple and skip this for your first tries and only experiment with non-transforming filters like the trim filter
8282

83-
Filters may change the type of the property. For example the builtin filter **dateTime** creates a DateTime object. Consequently further validations like pattern checks for the string property won't be performed.
83+
Filters may change the type of the property. For example the builtin filter **dateTime** creates a DateTime object. Consequently further type-related validations like pattern checks for the string property won't be performed. Additionally enum validations will not be executed if an already transformed value is provided.
8484

8585
As the required check is executed before the filter a filter may transform a required value into a null value. Be aware when writing custom filters which transform values to not break your validation rules by adding filters to a property.
8686

src/Model/Validator/EnumValidator.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace PHPModelGenerator\Model\Validator;
6+
7+
use PHPModelGenerator\Model\Property\PropertyInterface;
8+
9+
/**
10+
* Class EnumValidator
11+
*
12+
* @package PHPModelGenerator\Model\Validator
13+
*/
14+
class EnumValidator extends PropertyValidator
15+
{
16+
/**
17+
* EnumValidator constructor.
18+
*
19+
* @param PropertyInterface $property
20+
* @param array $allowedValues
21+
*/
22+
public function __construct(PropertyInterface $property, array $allowedValues)
23+
{
24+
25+
parent::__construct(
26+
'!in_array($value, ' .
27+
preg_replace('(\d+\s=>)', '', var_export($allowedValues, true)) .
28+
', true)',
29+
"Invalid value for {$property->getName()} declined by enum constraint"
30+
);
31+
}
32+
}

src/PropertyProcessor/Filter/FilterProcessor.php

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010
use PHPModelGenerator\Model\Property\Serializer\TransformingFilterSerializer;
1111
use PHPModelGenerator\Model\Schema;
1212
use PHPModelGenerator\Model\Validator;
13+
use PHPModelGenerator\Model\Validator\EnumValidator;
1314
use PHPModelGenerator\Model\Validator\FilterValidator;
1415
use PHPModelGenerator\Model\Validator\PassThroughTypeCheckValidator;
16+
use PHPModelGenerator\Model\Validator\PropertyValidator;
17+
use PHPModelGenerator\Model\Validator\ReflectionTypeCheckValidator;
1518
use PHPModelGenerator\Model\Validator\TypeCheckValidator;
1619
use PHPModelGenerator\Utils\RenderHelper;
1720
use ReflectionException;
@@ -86,8 +89,8 @@ public function process(
8689
$typeAfterFilter->getName() &&
8790
$property->getType() !== $typeAfterFilter->getName()
8891
) {
89-
$this->addTransformedValuePassThrough($property, $filter);
90-
$this->extendTypeCheckValidatorToAllowTransformedValue($property, $schema, $typeAfterFilter);
92+
$this->addTransformedValuePassThrough($property, $filter, $typeAfterFilter);
93+
$this->extendTypeCheckValidatorToAllowTransformedValue($property, $typeAfterFilter);
9194

9295
$property->setType(
9396
$property->getType(),
@@ -112,19 +115,39 @@ public function process(
112115
*
113116
* @param PropertyInterface $property
114117
* @param TransformingFilterInterface $filter
118+
* @param ReflectionType $filteredType
115119
*
116120
* @throws ReflectionException
117121
*/
118122
private function addTransformedValuePassThrough(
119123
PropertyInterface $property,
120-
TransformingFilterInterface $filter
124+
TransformingFilterInterface $filter,
125+
ReflectionType $filteredType
121126
): void {
122127
foreach ($property->getValidators() as $validator) {
123128
$validator = $validator->getValidator();
124129

125130
if ($validator instanceof FilterValidator) {
126131
$validator->addTransformedCheck($filter, $property);
127132
}
133+
134+
if ($validator instanceof EnumValidator) {
135+
$property->filterValidators(function (Validator $validator): bool {
136+
return !is_a($validator->getValidator(), EnumValidator::class);
137+
});
138+
139+
$property->addValidator(
140+
new PropertyValidator(
141+
sprintf(
142+
"%s && %s",
143+
ReflectionTypeCheckValidator::fromReflectionType($filteredType, $property)->getCheck(),
144+
$validator->getCheck()
145+
),
146+
$validator->getExceptionMessage()
147+
),
148+
3
149+
);
150+
}
128151
}
129152
}
130153

@@ -133,12 +156,10 @@ private function addTransformedValuePassThrough(
133156
* used to allow also already transformed values as valid input values
134157
*
135158
* @param PropertyInterface $property
136-
* @param Schema $schema
137159
* @param ReflectionType $typeAfterFilter
138160
*/
139161
private function extendTypeCheckValidatorToAllowTransformedValue(
140162
PropertyInterface $property,
141-
Schema $schema,
142163
ReflectionType $typeAfterFilter
143164
): void {
144165
$typeCheckValidator = null;

src/PropertyProcessor/Property/AbstractPropertyProcessor.php

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@
55
namespace PHPModelGenerator\PropertyProcessor\Property;
66

77
use PHPModelGenerator\Exception\SchemaException;
8-
use PHPModelGenerator\Model\GeneratorConfiguration;
98
use PHPModelGenerator\Model\Property\PropertyInterface;
109
use PHPModelGenerator\Model\Schema;
10+
use PHPModelGenerator\Model\Validator\EnumValidator;
1111
use PHPModelGenerator\Model\Validator\PropertyDependencyValidator;
12-
use PHPModelGenerator\Model\Validator\PropertyValidator;
1312
use PHPModelGenerator\Model\Validator\RequiredPropertyValidator;
1413
use PHPModelGenerator\Model\Validator\SchemaDependencyValidator;
1514
use PHPModelGenerator\PropertyProcessor\ComposedValueProcessorFactory;
@@ -84,19 +83,11 @@ protected function generateValidators(PropertyInterface $property, array $proper
8483
*/
8584
protected function addEnumValidator(PropertyInterface $property, array $allowedValues): void
8685
{
87-
if (!$property->isRequired()) {
86+
if ($this->isImplicitNullAllowed($property)) {
8887
$allowedValues[] = null;
8988
}
9089

91-
$property->addValidator(
92-
new PropertyValidator(
93-
'!in_array($value, ' .
94-
preg_replace('(\d+\s=>)', '', var_export(array_unique($allowedValues), true)) .
95-
', true)',
96-
"Invalid value for {$property->getName()} declined by enum constraint"
97-
),
98-
3
99-
);
90+
$property->addValidator(new EnumValidator($property, array_unique($allowedValues)), 3);
10091
}
10192

10293
/**
@@ -261,13 +252,12 @@ protected function inheritIfPropertyType(array $propertyData): array
261252
* Check if implicit null values are allowed for the given property (a not required property which has no
262253
* explicit null type and is passed with a null value will be accepted)
263254
*
264-
* @param GeneratorConfiguration $configuration
265255
* @param PropertyInterface $property
266256
*
267257
* @return bool
268258
*/
269-
protected function isImplicitNullAllowed(GeneratorConfiguration $configuration, PropertyInterface $property): bool
259+
protected function isImplicitNullAllowed(PropertyInterface $property): bool
270260
{
271-
return $configuration->isImplicitNullAllowed() && !$property->isRequired();
261+
return $this->schemaProcessor->getGeneratorConfiguration()->isImplicitNullAllowed() && !$property->isRequired();
272262
}
273263
}

src/PropertyProcessor/Property/AbstractTypedValueProcessor.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,7 @@ protected function generateValidators(PropertyInterface $property, array $proper
8282
parent::generateValidators($property, $propertyData);
8383

8484
$property->addValidator(
85-
new TypeCheckValidator(
86-
static::TYPE,
87-
$property,
88-
$this->isImplicitNullAllowed($this->schemaProcessor->getGeneratorConfiguration(), $property)
89-
),
85+
new TypeCheckValidator(static::TYPE, $property, $this->isImplicitNullAllowed($property)),
9086
2
9187
);
9288
}

src/PropertyProcessor/Property/MultiTypeProcessor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public function process(string $propertyName, array $propertyData): PropertyInte
8989
new MultiTypeCheckValidator(
9090
array_unique($this->allowedPropertyTypeChecks),
9191
$property,
92-
$this->isImplicitNullAllowed($this->schemaProcessor->getGeneratorConfiguration(), $property)
92+
$this->isImplicitNullAllowed($property)
9393
),
9494
2
9595
);

tests/Basic/FilterTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,4 +729,33 @@ public function arrayFilterDataProvider(): array
729729
'nested array' => [[['Hello'], [], [12], ['']], [['Hello'], [12], ['']]],
730730
];
731731
}
732+
733+
public function testEnumCheckWithTransformingFilterIsExecutedForNonTransformedValues(): void
734+
{
735+
$className = $this->generateClassFromFile('EnumBeforeFilter.json');
736+
737+
$object = new $className(['filteredProperty' => '2020-12-12']);
738+
$this->assertInstanceOf(DateTime::class, $object->getFilteredProperty());
739+
$this->assertSame(
740+
(new DateTime('2020-12-12'))->format(DATE_ATOM),
741+
$object->getFilteredProperty()->format(DATE_ATOM)
742+
);
743+
744+
$this->expectException(ValidationException::class);
745+
$this->expectExceptionMessage('Invalid value for filteredProperty declined by enum constraint');
746+
747+
new $className(['filteredProperty' => '1999-12-12']);
748+
}
749+
750+
public function testEnumCheckWithTransformingFilterIsNotExecutedForTransformedValues(): void
751+
{
752+
$className = $this->generateClassFromFile('EnumBeforeFilter.json');
753+
$object = new $className(['filteredProperty' => new DateTime('1999-12-12')]);
754+
755+
$this->assertInstanceOf(DateTime::class, $object->getFilteredProperty());
756+
$this->assertSame(
757+
(new DateTime('1999-12-12'))->format(DATE_ATOM),
758+
$object->getFilteredProperty()->format(DATE_ATOM)
759+
);
760+
}
732761
}

tests/Objects/EnumPropertyTest.php

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,21 @@ public function validEnumEntriesDataProvider(): array
6161
];
6262
}
6363

64+
/**
65+
* @throws FileSystemException
66+
* @throws RenderException
67+
* @throws SchemaException
68+
*/
69+
public function testNullWithoutImplicitNullThrowsAnException(): void
70+
{
71+
$this->expectException(ValidationException::class);
72+
$this->expectExceptionMessage('Invalid value for property declined by enum constraint');
73+
74+
$className = $this->generateClassFromFile('TypedEnumProperty.json', null, false, false);
75+
76+
new $className(['property' => null]);
77+
}
78+
6479
/**
6580
* @dataProvider invalidEnumEntriesDataProvider
6681
*
@@ -150,6 +165,8 @@ public function testNullProvidedForRequiredEnumThrowsAnException(): void
150165
}
151166

152167
/**
168+
* @dataProvider implicitNullDataProvider
169+
*
153170
* @throws FileSystemException
154171
* @throws RenderException
155172
* @throws SchemaException
@@ -188,6 +205,21 @@ public function validEnumEntriesUntypedEnumDataProvider(): array
188205
];
189206
}
190207

208+
/**
209+
* @throws FileSystemException
210+
* @throws RenderException
211+
* @throws SchemaException
212+
*/
213+
public function testNullInUntypedEnumWithoutImplicitNullThrowsAnException(): void
214+
{
215+
$this->expectException(ValidationException::class);
216+
$this->expectExceptionMessage('Invalid value for property declined by enum constraint');
217+
218+
$className = $this->generateClassFromFile('UntypedEnumProperty.json', null, false, false);
219+
220+
new $className(['property' => null]);
221+
}
222+
191223
/**
192224
* @dataProvider invalidEnumEntriesUntypedEnumDataProvider
193225
*
@@ -235,16 +267,20 @@ public function testNotProvidedEnumItemInRequiredUntypedEnumThrowsAnException():
235267
}
236268

237269
/**
270+
* @dataProvider implicitNullDataProvider
271+
*
272+
* @param bool $implicitNull
273+
*
238274
* @throws FileSystemException
239275
* @throws RenderException
240276
* @throws SchemaException
241277
*/
242-
public function testNullProvidedEnumItemInRequiredUntypedEnumThrowsAnException(): void
278+
public function testNullProvidedEnumItemInRequiredUntypedEnumThrowsAnException(bool $implicitNull): void
243279
{
244280
$this->expectException(ValidationException::class);
245281
$this->expectExceptionMessage('Invalid value for property declined by enum constraint');
246282

247-
$className = $this->generateClassFromFile('RequiredUntypedEnumProperty.json');
283+
$className = $this->generateClassFromFile('RequiredUntypedEnumProperty.json', null, false, $implicitNull);
248284

249285
new $className(['property' => null]);
250286
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"property": {
5+
"type": ["string", "null"],
6+
"enum": ["red", "green"]
7+
}
8+
}
9+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"filteredProperty": {
5+
"type": "string",
6+
"enum": ["2020-12-12", "2019-12-12"],
7+
"filter": "dateTime"
8+
}
9+
}
10+
}

tests/manual/schema/person.json

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33
"type": "object",
44
"properties": {
55
"name": {
6-
"type": "string",
6+
"type": ["null", "string"],
77
"description": "The name of the person",
8-
"example": "Lawrence"
9-
},
10-
"age": {
11-
"type": "integer",
12-
"description": "The age of the person",
13-
"example": 42
8+
"example": "Lawrence",
9+
"filter": [
10+
"trim",
11+
"dateTime"
12+
]
1413
}
1514
},
1615
"required": [

tests/manual/test.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
$generator = new ModelGenerator((new GeneratorConfiguration())
1010
->setNamespacePrefix('\\ManualSchema')
1111
->setImmutable(false)
12-
->setPrettyPrint(true)
12+
->setPrettyPrint(false)
13+
->setImplicitNull(false)
1314
);
1415

1516
$generator

0 commit comments

Comments
 (0)