Skip to content

Commit 4582fa7

Browse files
committed
Fix array without explicit null type accepts null values
1 parent 2029797 commit 4582fa7

File tree

10 files changed

+203
-45
lines changed

10 files changed

+203
-45
lines changed

src/Model/Validator/AbstractPropertyValidator.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,8 @@ public function getValidatorSetUp(): string
5353
*/
5454
protected function removeRequiredPropertyValidator(PropertyInterface $property): void
5555
{
56-
if ($property instanceof Property) {
57-
$property->filterValidators(function (Validator $validator): bool {
58-
return !is_a($validator->getValidator(), RequiredPropertyValidator::class);
59-
});
60-
}
56+
$property->filterValidators(function (Validator $validator): bool {
57+
return !is_a($validator->getValidator(), RequiredPropertyValidator::class);
58+
});
6159
}
6260
}

src/Model/Validator/AdditionalPropertiesValidator.php

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use PHPModelGenerator\Exception\Object\InvalidAdditionalPropertiesException;
88
use PHPModelGenerator\Exception\SchemaException;
9+
use PHPModelGenerator\Model\Property\PropertyInterface;
910
use PHPModelGenerator\Model\Schema;
1011
use PHPModelGenerator\PropertyProcessor\PropertyMetaDataCollection;
1112
use PHPModelGenerator\PropertyProcessor\PropertyFactory;
@@ -27,6 +28,9 @@ class AdditionalPropertiesValidator extends PropertyTemplateValidator
2728

2829
protected const EXCEPTION_CLASS = InvalidAdditionalPropertiesException::class;
2930

31+
/** @var PropertyInterface */
32+
private $validationProperty;
33+
3034
/**
3135
* AdditionalPropertiesValidator constructor.
3236
*
@@ -45,33 +49,41 @@ public function __construct(
4549
) {
4650
$propertyFactory = new PropertyFactory(new PropertyProcessorFactory());
4751

48-
$validationProperty = $propertyFactory->create(
52+
$this->validationProperty = $propertyFactory->create(
4953
new PropertyMetaDataCollection([static::PROPERTY_NAME]),
5054
$schemaProcessor,
5155
$schema,
5256
static::PROPERTY_NAME,
5357
$propertiesStructure[static::ADDITIONAL_PROPERTIES_KEY]
5458
);
5559

56-
$this->removeRequiredPropertyValidator($validationProperty);
57-
5860
parent::__construct(
5961
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'AdditionalProperties.phptpl',
6062
[
61-
'validationProperty' => $validationProperty,
62-
'additionalProperties' => preg_replace(
63+
'validationProperty' => $this->validationProperty,
64+
'additionalProperties' => preg_replace(
6365
'(\d+\s=>)',
6466
'',
6567
var_export(array_keys($propertiesStructure[static::PROPERTIES_KEY] ?? []), true)
6668
),
6769
'generatorConfiguration' => $schemaProcessor->getGeneratorConfiguration(),
68-
'viewHelper' => new RenderHelper($schemaProcessor->getGeneratorConfiguration()),
70+
'viewHelper' => new RenderHelper($schemaProcessor->getGeneratorConfiguration()),
6971
],
7072
static::EXCEPTION_CLASS,
7173
[$propertyName ?? $schema->getClassName(), '&$invalidProperties']
7274
);
7375
}
7476

77+
/**
78+
* @inheritDoc
79+
*/
80+
public function getCheck(): string
81+
{
82+
$this->removeRequiredPropertyValidator($this->validationProperty);
83+
84+
return parent::getCheck();
85+
}
86+
7587
/**
7688
* Initialize all variables which are required to execute a property names validator
7789
*

src/Model/Validator/ArrayItemValidator.php

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@
2525
*/
2626
class ArrayItemValidator extends PropertyTemplateValidator
2727
{
28+
/** @var string */
2829
private $variableSuffix = '';
30+
/** @var PropertyInterface */
31+
private $nestedProperty;
2932

3033
/**
3134
* ArrayItemValidator constructor.
@@ -47,24 +50,24 @@ public function __construct(
4750
PropertyInterface $property
4851
) {
4952
$this->variableSuffix = '_' . uniqid();
53+
$nestedPropertyName = "item of array {$property->getName()}";
5054

5155
// an item of the array behaves like a nested property to add item-level validation
52-
$nestedProperty = (new PropertyFactory(new PropertyProcessorFactory()))
56+
$this->nestedProperty = (new PropertyFactory(new PropertyProcessorFactory()))
5357
->create(
54-
new PropertyMetaDataCollection(),
58+
new PropertyMetaDataCollection([$nestedPropertyName]),
5559
$schemaProcessor,
5660
$schema,
57-
"item of array {$property->getName()}",
61+
$nestedPropertyName,
5862
$itemStructure
5963
);
6064

61-
$this->removeRequiredPropertyValidator($nestedProperty);
62-
$property->addTypeHintDecorator(new ArrayTypeHintDecorator($nestedProperty));
65+
$property->addTypeHintDecorator(new ArrayTypeHintDecorator($this->nestedProperty));
6366

6467
parent::__construct(
6568
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'ArrayItem.phptpl',
6669
[
67-
'nestedProperty' => $nestedProperty,
70+
'nestedProperty' => $this->nestedProperty,
6871
'viewHelper' => new RenderHelper($schemaProcessor->getGeneratorConfiguration()),
6972
'generatorConfiguration' => $schemaProcessor->getGeneratorConfiguration(),
7073
'suffix' => $this->variableSuffix,
@@ -74,6 +77,17 @@ public function __construct(
7477
);
7578
}
7679

80+
/**
81+
* @inheritDoc
82+
*/
83+
public function getCheck(): string
84+
{
85+
$this->removeRequiredPropertyValidator($this->nestedProperty);
86+
87+
return parent::getCheck();
88+
}
89+
90+
7791
/**
7892
* Initialize all variables which are required to execute a property names validator
7993
*

src/Model/Validator/ArrayTupleValidator.php

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use PHPModelGenerator\Exception\Arrays\InvalidTupleException;
88
use PHPModelGenerator\Exception\SchemaException;
9+
use PHPModelGenerator\Model\Property\PropertyInterface;
910
use PHPModelGenerator\Model\Schema;
1011
use PHPModelGenerator\PropertyProcessor\PropertyMetaDataCollection;
1112
use PHPModelGenerator\PropertyProcessor\PropertyFactory;
@@ -20,6 +21,9 @@
2021
*/
2122
class ArrayTupleValidator extends PropertyTemplateValidator
2223
{
24+
/** @var PropertyInterface[] */
25+
private $tupleProperties;
26+
2327
/**
2428
* ArrayTupleValidator constructor.
2529
*
@@ -38,26 +42,24 @@ public function __construct(
3842
) {
3943
$propertyFactory = new PropertyFactory(new PropertyProcessorFactory());
4044

41-
$tupleProperties = [];
45+
$this->tupleProperties = [];
4246
foreach ($propertiesStructure as $tupleIndex => $tupleItem) {
4347
$tupleItemName = "tuple item #$tupleIndex of array $propertyName";
4448

4549
// an item of the array behaves like a nested property to add item-level validation
46-
$tupleProperties[] = $propertyFactory->create(
50+
$this->tupleProperties[] = $propertyFactory->create(
4751
new PropertyMetaDataCollection([$tupleItemName]),
4852
$schemaProcessor,
4953
$schema,
5054
$tupleItemName,
5155
$tupleItem
5256
);
53-
54-
$this->removeRequiredPropertyValidator(end($tupleProperties));
5557
}
5658

5759
parent::__construct(
5860
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'ArrayTuple.phptpl',
5961
[
60-
'tupleProperties' => $tupleProperties,
62+
'tupleProperties' => &$this->tupleProperties,
6163
'viewHelper' => new RenderHelper($schemaProcessor->getGeneratorConfiguration()),
6264
'generatorConfiguration' => $schemaProcessor->getGeneratorConfiguration(),
6365
],
@@ -66,6 +68,18 @@ public function __construct(
6668
);
6769
}
6870

71+
/**
72+
* @inheritDoc
73+
*/
74+
public function getCheck(): string
75+
{
76+
foreach ($this->tupleProperties as $tupleProperty) {
77+
$this->removeRequiredPropertyValidator($tupleProperty);
78+
}
79+
80+
return parent::getCheck();
81+
}
82+
6983
/**
7084
* Initialize all variables which are required to execute a property names validator
7185
*

src/PropertyProcessor/Property/MultiTypeProcessor.php

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,20 @@ public function process(string $propertyName, array $propertyData): PropertyInte
8080
$this->checks[] = $validator->getValidator()->getCheck();
8181
}
8282

83-
$this->processSubProperties($propertyName, $propertyData, $property);
83+
$subProperties = $this->processSubProperties($propertyName, $propertyData, $property);
8484

8585
if (empty($this->allowedPropertyTypes)) {
8686
return $property;
8787
}
8888

89-
$property->addTypeHintDecorator(new TypeHintDecorator($this->allowedPropertyTypes));
89+
$property->addTypeHintDecorator(
90+
new TypeHintDecorator(
91+
array_map(function (PropertyInterface $subProperty): string {
92+
return $subProperty->getTypeHint();
93+
},
94+
$subProperties)
95+
)
96+
);
9097

9198
return $property->addValidator(
9299
new MultiTypeCheckValidator(
@@ -131,16 +138,19 @@ protected function transferValidators(PropertyInterface $source, PropertyInterfa
131138
* @param array $propertyData
132139
* @param PropertyInterface $property
133140
*
141+
* @return PropertyInterface[]
142+
*
134143
* @throws SchemaException
135144
*/
136145
protected function processSubProperties(
137146
string $propertyName,
138147
array $propertyData,
139148
PropertyInterface $property
140-
): void {
149+
): array {
141150
$defaultValue = null;
142151
$invalidDefaultValueException = null;
143152
$invalidDefaultValues = 0;
153+
$subProperties = [];
144154

145155
if (isset($propertyData['default'])) {
146156
$defaultValue = $propertyData['default'];
@@ -163,10 +173,14 @@ protected function processSubProperties(
163173
$invalidDefaultValueException = $e;
164174
}
165175
}
176+
177+
$subProperties[] = $subProperty;
166178
}
167179

168180
if ($invalidDefaultValues === count($this->propertyProcessors)) {
169181
throw $invalidDefaultValueException;
170182
}
183+
184+
return $subProperties;
171185
}
172186
}

src/Utils/RenderHelper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public function getTypeHintAnnotation(PropertyInterface $property, bool $outputT
113113
return '';
114114
}
115115

116-
if ((!$outputType || $this->generatorConfiguration->isImplicitNullAllowed()) && !$property->isRequired()) {
116+
if (($outputType || $this->generatorConfiguration->isImplicitNullAllowed()) && !$property->isRequired()) {
117117
$typeHint = implode('|', array_unique(array_merge(explode('|', $typeHint), ['null'])));
118118
}
119119

tests/AbstractPHPModelGeneratorTest.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -445,12 +445,32 @@ protected function getMethodReturnTypeAnnotation($object, string $method): strin
445445
return $matches[1];
446446
}
447447

448-
protected function getParameterType($object, string $method): ReflectionType
448+
/**
449+
* Get the annotated parameter type for an object method
450+
*
451+
* @param string|object $object
452+
* @param string $method
453+
*
454+
* @return string
455+
*/
456+
protected function getMethodParameterTypeAnnotation($object, string $method): string
457+
{
458+
$matches = [];
459+
preg_match(
460+
'/@param\s+([^\s]*)\s?\$/',
461+
(new ReflectionClass($object))->getMethod($method)->getDocComment(),
462+
$matches
463+
);
464+
465+
return $matches[1];
466+
}
467+
468+
protected function getParameterType($object, string $method): ?ReflectionType
449469
{
450470
return (new ReflectionClass($object))->getMethod($method)->getParameters()[0]->getType();
451471
}
452472

453-
protected function getReturnType($object, string $method): ReflectionType
473+
protected function getReturnType($object, string $method): ?ReflectionType
454474
{
455475
return (new ReflectionClass($object))->getMethod($method)->getReturnType();
456476
}

0 commit comments

Comments
 (0)