Skip to content

Commit 6e9e9a4

Browse files
committed
Issue #65.
Add onResolved callback to validators to execute further post-processing of the validator and properties holding the validator only after the validator has been created completely. This is required as a validator might hold a nested property to execute the validation. This nested property might be a PropertyProxy due to recursion which might cause a crash of the generation process if the property hasn't been created completely before post-processing the property and the validator.
1 parent f80cb00 commit 6e9e9a4

File tree

56 files changed

+339
-175
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+339
-175
lines changed

src/Model/Property/AbstractProperty.php

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PHPModelGenerator\Exception\SchemaException;
88
use PHPModelGenerator\Model\SchemaDefinition\JsonSchema;
99
use PHPModelGenerator\Model\SchemaDefinition\JsonSchemaTrait;
10+
use PHPModelGenerator\Utils\ResolvableTrait;
1011

1112
/**
1213
* Class AbstractProperty
@@ -15,18 +16,13 @@
1516
*/
1617
abstract class AbstractProperty implements PropertyInterface
1718
{
18-
use JsonSchemaTrait;
19+
use JsonSchemaTrait, ResolvableTrait;
1920

2021
/** @var string */
2122
protected $name = '';
2223
/** @var string */
2324
protected $attribute = '';
2425

25-
/** @var callable[] */
26-
protected $onResolveCallbacks = [];
27-
/** @var bool */
28-
protected $resolved = false;
29-
3026
/**
3127
* Property constructor.
3228
*
@@ -63,15 +59,6 @@ public function getAttribute(bool $variableName = false): string
6359
return ($this->isInternal() ? '_' : '') . $attribute;
6460
}
6561

66-
public function onResolve(callable $callback): PropertyInterface
67-
{
68-
$this->resolved
69-
? $callback()
70-
: $this->onResolveCallbacks[] = $callback;
71-
72-
return $this;
73-
}
74-
7562
/**
7663
* Convert a name of a JSON-field into a valid PHP variable name to be used as class attribute
7764
*
@@ -85,14 +72,14 @@ protected function processAttributeName(string $name): string
8572
{
8673
$attributeName = preg_replace_callback(
8774
'/([a-z][a-z0-9]*)([A-Z])/',
88-
function ($matches) {
75+
static function (array $matches): string {
8976
return "{$matches[1]}-{$matches[2]}";
9077
},
9178
$name
9279
);
9380

9481
$elements = array_map(
95-
function ($element) {
82+
static function (string $element): string {
9683
return ucfirst(strtolower($element));
9784
},
9885
preg_split('/[^a-z0-9]/i', $attributeName)

src/Model/Property/CompositionPropertyDecorator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public function __construct(string $propertyName, JsonSchema $jsonSchema, Proper
4343
self::PROPERTY_KEY
4444
);
4545

46-
$property->onResolve(function () {
46+
$property->onResolve(function (): void {
4747
$this->resolve();
4848
});
4949
}

src/Model/Property/Property.php

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class Property extends AbstractProperty
4444
public $typeHintDecorators = [];
4545

4646
private $renderedTypeHints = [];
47+
/** @var int Track the amount of unresolved validators */
48+
private $pendingValidators = 0;
4749

4850
/**
4951
* Property constructor.
@@ -62,8 +64,7 @@ public function __construct(string $name, ?PropertyType $type, JsonSchema $jsonS
6264
$this->type = $type;
6365
$this->description = $description;
6466

65-
// a concrete property doesn't need to be resolved
66-
$this->resolved = true;
67+
$this->resolve();
6768
}
6869

6970
/**
@@ -124,7 +125,7 @@ public function getTypeHint(bool $outputType = false, array $skipDecorators = []
124125

125126
$filteredDecorators = array_filter(
126127
$this->typeHintDecorators,
127-
function (TypeHintDecoratorInterface $decorator) use ($skipDec) {
128+
static function (TypeHintDecoratorInterface $decorator) use ($skipDec): bool {
128129
return !in_array(get_class($decorator), $skipDec);
129130
}
130131
);
@@ -173,6 +174,18 @@ public function getDescription(): string
173174
*/
174175
public function addValidator(PropertyValidatorInterface $validator, int $priority = 99): PropertyInterface
175176
{
177+
if (!$validator->isResolved()) {
178+
$this->isResolved = false;
179+
180+
$this->pendingValidators++;
181+
182+
$validator->onResolve(function () {
183+
if (--$this->pendingValidators === 0) {
184+
$this->resolve();
185+
}
186+
});
187+
}
188+
176189
$this->validators[] = new Validator($validator, $priority);
177190

178191
return $this;
@@ -203,7 +216,7 @@ public function getOrderedValidators(): array
203216
{
204217
usort(
205218
$this->validators,
206-
function (Validator $validator, Validator $comparedValidator) {
219+
static function (Validator $validator, Validator $comparedValidator): int {
207220
if ($validator->getPriority() == $comparedValidator->getPriority()) {
208221
return 0;
209222
}
@@ -212,7 +225,7 @@ function (Validator $validator, Validator $comparedValidator) {
212225
);
213226

214227
return array_map(
215-
function (Validator $validator) {
228+
static function (Validator $validator): PropertyValidatorInterface {
216229
return $validator->getValidator();
217230
},
218231
$this->validators

src/Model/Property/PropertyInterface.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
use PHPModelGenerator\Model\Validator\PropertyValidatorInterface;
1111
use PHPModelGenerator\PropertyProcessor\Decorator\Property\PropertyDecoratorInterface;
1212
use PHPModelGenerator\PropertyProcessor\Decorator\TypeHint\TypeHintDecoratorInterface;
13+
use PHPModelGenerator\Utils\ResolvableInterface;
1314

1415
/**
1516
* Interface PropertyInterface
1617
*
1718
* @package PHPModelGenerator\Model
1819
*/
19-
interface PropertyInterface
20+
interface PropertyInterface extends ResolvableInterface
2021
{
2122
/**
2223
* @return string
@@ -205,9 +206,4 @@ public function getNestedSchema(): ?Schema;
205206
* @return JsonSchema
206207
*/
207208
public function getJsonSchema(): JsonSchema;
208-
209-
/**
210-
* Adds a callback which will be executed after the property is set up completely
211-
*/
212-
public function onResolve(callable $callback): PropertyInterface;
213209
}

src/Model/Property/PropertyProxy.php

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class PropertyProxy extends AbstractProperty
2828
* PropertyProxy constructor.
2929
*
3030
* @param string $name The name must be provided separately as the name is not bound to the structure of a
31-
* referenced schema. Consequently two properties with different names can refer an identical schema utilizing the
31+
* referenced schema. Consequently, two properties with different names can refer an identical schema utilizing the
3232
* PropertyProxy. By providing a name to each of the proxies the resulting properties will get the correct names.
3333
* @param JsonSchema $jsonSchema
3434
* @param ResolvedDefinitionsCollection $definitionsCollection
@@ -48,23 +48,6 @@ public function __construct(
4848
$this->definitionsCollection = $definitionsCollection;
4949
}
5050

51-
public function resolve(): PropertyInterface
52-
{
53-
if ($this->resolved) {
54-
return $this;
55-
}
56-
57-
$this->resolved = true;
58-
59-
foreach ($this->onResolveCallbacks as $callback) {
60-
$callback();
61-
}
62-
63-
$this->onResolveCallbacks = [];
64-
65-
return $this;
66-
}
67-
6851
/**
6952
* Get the property out of the resolved definitions collection to proxy function calls
7053
*
@@ -151,9 +134,12 @@ public function filterValidators(callable $filter): PropertyInterface
151134
*/
152135
public function getOrderedValidators(): array
153136
{
154-
return array_map(function (PropertyValidatorInterface $propertyValidator): PropertyValidatorInterface {
155-
return $propertyValidator->withProperty($this);
156-
}, $this->getProperty()->getOrderedValidators());
137+
return array_map(
138+
function (PropertyValidatorInterface $propertyValidator): PropertyValidatorInterface {
139+
return $propertyValidator->withProperty($this);
140+
},
141+
$this->getProperty()->getOrderedValidators()
142+
);
157143
}
158144

159145
/**

src/Model/RenderJob.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ protected function renderClass(GeneratorConfiguration $generatorConfiguration):
136136
'true' => true,
137137
'baseValidatorsWithoutCompositions' => array_filter(
138138
$this->schema->getBaseValidators(),
139-
function ($validator) {
139+
static function ($validator): bool {
140140
return !is_a($validator, AbstractComposedPropertyValidator::class);
141141
}
142142
),
@@ -167,7 +167,7 @@ protected function getUseForSchema(GeneratorConfiguration $generatorConfiguratio
167167
);
168168

169169
// filter out non-compound uses and uses which link to the current namespace
170-
$use = array_filter($use, function ($classPath) use ($namespace) {
170+
$use = array_filter($use, static function ($classPath) use ($namespace): bool {
171171
return strstr(trim(str_replace("$namespace", '', $classPath), '\\'), '\\') ||
172172
(!strstr($classPath, '\\') && !empty($namespace));
173173
});

src/Model/Schema.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ public function onAllPropertiesResolved(callable $callback): self
125125
*/
126126
public function getProperties(): array
127127
{
128-
$hasSchemaDependencyValidator = function (PropertyInterface $property): bool {
128+
$hasSchemaDependencyValidator = static function (PropertyInterface $property): bool {
129129
foreach ($property->getValidators() as $validator) {
130130
if ($validator->getValidator() instanceof SchemaDependencyValidator) {
131131
return true;
@@ -139,7 +139,7 @@ public function getProperties(): array
139139
// of the validation process for correct exception order of the messages
140140
usort(
141141
$this->properties,
142-
function (
142+
static function (
143143
PropertyInterface $property,
144144
PropertyInterface $comparedProperty
145145
) use ($hasSchemaDependencyValidator): int {
@@ -167,7 +167,7 @@ public function addProperty(PropertyInterface $property): self
167167
if (!isset($this->properties[$property->getName()])) {
168168
$this->properties[$property->getName()] = $property;
169169

170-
$property->onResolve(function () {
170+
$property->onResolve(function (): void {
171171
if (++$this->resolvedProperties === count($this->properties)) {
172172
foreach ($this->onAllPropertiesResolvedCallbacks as $callback) {
173173
$callback();

src/Model/SchemaDefinition/SchemaDefinition.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ public function resolveReference(
110110
}
111111

112112
unset($this->unresolvedProxies[$key]);
113+
114+
return $property;
113115
} catch (PHPModelGeneratorException $exception) {
114116
$this->resolvedPaths->offsetUnset($key);
115117
throw $exception;

src/Model/Validator/AbstractPropertyValidator.php

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

77
use PHPModelGenerator\Model\Property\PropertyInterface;
88
use PHPModelGenerator\Model\Validator;
9+
use PHPModelGenerator\Utils\ResolvableTrait;
910

1011
/**
1112
* Class AbstractPropertyValidator
@@ -14,6 +15,8 @@
1415
*/
1516
abstract class AbstractPropertyValidator implements PropertyValidatorInterface
1617
{
18+
use ResolvableTrait;
19+
1720
/** @var string */
1821
protected $exceptionClass;
1922
/** @var array */
@@ -79,8 +82,10 @@ public function getValidatorSetUp(): string
7982
*/
8083
protected function removeRequiredPropertyValidator(PropertyInterface $property): void
8184
{
82-
$property->filterValidators(function (Validator $validator): bool {
83-
return !is_a($validator->getValidator(), RequiredPropertyValidator::class);
85+
$property->onResolve(static function () use ($property): void {
86+
$property->filterValidators(static function (Validator $validator): bool {
87+
return !is_a($validator->getValidator(), RequiredPropertyValidator::class);
88+
});
8489
});
8590
}
8691
}

src/Model/Validator/AdditionalPropertiesValidator.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ public function __construct(
6161
$propertiesStructure->withJson($propertiesStructure->getJson()[static::ADDITIONAL_PROPERTIES_KEY])
6262
);
6363

64+
$this->validationProperty->onResolve(function (): void {
65+
$this->resolve();
66+
});
67+
6468
$patternProperties = array_keys($schema->getJsonSchema()->getJson()['patternProperties'] ?? []);
6569

6670
parent::__construct(

src/Model/Validator/ArrayItemValidator.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
*
2222
* @package PHPModelGenerator\Model\Validator
2323
*/
24-
class ArrayItemValidator extends PropertyTemplateValidator
24+
class ArrayItemValidator extends ExtractedMethodValidator
2525
{
2626
/** @var string */
2727
private $variableSuffix = '';
@@ -57,9 +57,14 @@ public function __construct(
5757
$itemStructure
5858
);
5959

60-
$property->addTypeHintDecorator(new ArrayTypeHintDecorator($this->nestedProperty));
60+
$this->nestedProperty->onResolve(function () use ($property): void {
61+
$this->resolve();
62+
63+
$property->addTypeHintDecorator(new ArrayTypeHintDecorator($this->nestedProperty));
64+
});
6165

6266
parent::__construct(
67+
$schemaProcessor->getGeneratorConfiguration(),
6368
$property,
6469
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'ArrayItem.phptpl',
6570
[

src/Model/Validator/ArrayTupleValidator.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,34 @@ public function __construct(
4545
$propertyFactory = new PropertyFactory(new PropertyProcessorFactory());
4646

4747
$this->tupleProperties = [];
48+
49+
$pendingTuples = 0;
4850
foreach ($propertiesStructure->getJson() as $tupleIndex => $tupleItem) {
4951
$tupleItemName = "tuple item #$tupleIndex of array $propertyName";
5052

5153
// an item of the array behaves like a nested property to add item-level validation
52-
$this->tupleProperties[] = $propertyFactory->create(
54+
$tupleProperty = $propertyFactory->create(
5355
new PropertyMetaDataCollection([$tupleItemName]),
5456
$schemaProcessor,
5557
$schema,
5658
$tupleItemName,
5759
$propertiesStructure->withJson($tupleItem)
5860
);
61+
62+
if (!$tupleProperty->isResolved()) {
63+
$pendingTuples++;
64+
$tupleProperty->onResolve(function () use (&$pendingTuples): void {
65+
if (--$pendingTuples === 0) {
66+
$this->resolve();
67+
}
68+
});
69+
}
70+
71+
$this->tupleProperties[] = $tupleProperty;
72+
}
73+
74+
if ($pendingTuples === 0) {
75+
$this->resolve();
5976
}
6077

6178
parent::__construct(

src/Model/Validator/ComposedPropertyValidator.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ public function __construct(
2424
string $compositionProcessor,
2525
array $validatorVariables
2626
) {
27+
$this->isResolved = true;
28+
2729
parent::__construct(
2830
$generatorConfiguration,
2931
$property,
@@ -62,8 +64,8 @@ public function withoutNestedCompositionValidation(): self
6264

6365
/** @var CompositionPropertyDecorator $composedProperty */
6466
foreach ($validator->composedProperties as $composedProperty) {
65-
$composedProperty->onResolve(function () use ($composedProperty) {
66-
$composedProperty->filterValidators(function (Validator $validator): bool {
67+
$composedProperty->onResolve(static function () use ($composedProperty): void {
68+
$composedProperty->filterValidators(static function (Validator $validator): bool {
6769
return !is_a($validator->getValidator(), AbstractComposedPropertyValidator::class);
6870
});
6971
});

src/Model/Validator/ConditionalPropertyValidator.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ public function __construct(
2222
array $composedProperties,
2323
array $validatorVariables
2424
) {
25+
$this->isResolved = true;
26+
2527
parent::__construct(
2628
$generatorConfiguration,
2729
$property,

0 commit comments

Comments
 (0)