Skip to content

Commit e801a40

Browse files
committed
Issue #65.
Add onResolved callback to properties to execute further post-processing of the property only after the property has been created completely. Otherwise, the property might redirect to null causing the generation process to crash. Compositions for example use generated properties to enrich the base model the composition belongs to. This process must be delayed until the properties are completely set up (an incomplete property might occur due to recursion). Consequently now the composition uses a callback mechanism to delay the process until all recursions are resolved.
1 parent 1294d21 commit e801a40

23 files changed

+369
-107
lines changed

src/Model/Property/AbstractProperty.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ abstract class AbstractProperty implements PropertyInterface
2222
/** @var string */
2323
protected $attribute = '';
2424

25+
/** @var callable[] */
26+
protected $onResolveCallbacks = [];
27+
/** @var bool */
28+
protected $resolved = false;
29+
2530
/**
2631
* Property constructor.
2732
*
@@ -58,6 +63,20 @@ public function getAttribute(bool $variableName = false): string
5863
return ($this->isInternal() ? '_' : '') . $attribute;
5964
}
6065

66+
public function onResolve(callable $callback): PropertyInterface
67+
{
68+
$this->resolved
69+
? $callback()
70+
: $this->onResolveCallbacks[] = $callback;
71+
72+
return $this;
73+
}
74+
75+
public function isResolved(): bool
76+
{
77+
return $this->resolved;
78+
}
79+
6180
/**
6281
* Convert a name of a JSON-field into a valid PHP variable name to be used as class attribute
6382
*

src/Model/Property/CompositionPropertyDecorator.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ public function __construct(string $propertyName, JsonSchema $jsonSchema, Proper
4242
new ResolvedDefinitionsCollection([self::PROPERTY_KEY => $property]),
4343
self::PROPERTY_KEY
4444
);
45+
46+
$property->onResolve(function () {
47+
$this->resolve();
48+
});
4549
}
4650

4751
/**

src/Model/Property/Property.php

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,14 @@ class Property extends AbstractProperty
3737
/** @var Validator[] */
3838
protected $validators = [];
3939
/** @var Schema */
40-
protected $schema;
40+
protected $nestedSchema;
4141
/** @var PropertyDecoratorInterface[] */
4242
public $decorators = [];
4343
/** @var TypeHintDecoratorInterface[] */
4444
public $typeHintDecorators = [];
4545

46+
private $renderedTypeHints = [];
47+
4648
/**
4749
* Property constructor.
4850
*
@@ -59,6 +61,9 @@ public function __construct(string $name, ?PropertyType $type, JsonSchema $jsonS
5961

6062
$this->type = $type;
6163
$this->description = $description;
64+
65+
// a concrete property doesn't need to be resolved
66+
$this->resolved = true;
6267
}
6368

6469
/**
@@ -94,26 +99,47 @@ public function setType(PropertyType $type = null, PropertyType $outputType = nu
9499
/**
95100
* @inheritdoc
96101
*/
97-
public function getTypeHint(bool $outputType = false): string
102+
public function getTypeHint(bool $outputType = false, array $skipDecorators = []): string
98103
{
104+
if (isset($this->renderedTypeHints[$outputType])) {
105+
return $this->renderedTypeHints[$outputType];
106+
}
107+
108+
static $skipDec = [];
109+
110+
$additionalSkips = array_diff($skipDecorators, $skipDec);
111+
$skipDec = array_merge($skipDec, $additionalSkips);
112+
99113
$input = [$outputType && $this->outputType !== null ? $this->outputType : $this->type];
100114

101115
// If the output type differs from an input type also accept the output type
102116
if (!$outputType && $this->outputType !== null && $this->outputType !== $this->type) {
103117
$input = [$this->type, $this->outputType];
104118
}
105119

106-
$input = join('|', array_filter(array_map(function (?PropertyType $input) use ($outputType): string {
107-
$typeHint = $input ? $input->getName() : '';
120+
$input = join(
121+
'|',
122+
array_filter(array_map(function (?PropertyType $input) use ($outputType, $skipDec): string {
123+
$typeHint = $input ? $input->getName() : '';
108124

109-
foreach ($this->typeHintDecorators as $decorator) {
110-
$typeHint = $decorator->decorate($typeHint, $outputType);
111-
}
125+
$filteredDecorators = array_filter(
126+
$this->typeHintDecorators,
127+
function (TypeHintDecoratorInterface $decorator) use ($skipDec) {
128+
return !in_array(get_class($decorator), $skipDec);
129+
}
130+
);
112131

113-
return $typeHint;
114-
}, $input)));
132+
foreach ($filteredDecorators as $decorator) {
133+
$typeHint = $decorator->decorate($typeHint, $outputType);
134+
}
115135

116-
return $input ?: 'mixed';
136+
return $typeHint;
137+
}, $input))
138+
);
139+
140+
$skipDec = array_diff($skipDec, $additionalSkips);
141+
142+
return $this->renderedTypeHints[$outputType] = $input ?: 'mixed';
117143
}
118144

119145
/**
@@ -126,6 +152,14 @@ public function addTypeHintDecorator(TypeHintDecoratorInterface $typeHintDecorat
126152
return $this;
127153
}
128154

155+
/**
156+
* @inheritdoc
157+
*/
158+
public function getTypeHintDecorators(): array
159+
{
160+
return $this->typeHintDecorators;
161+
}
162+
129163
/**
130164
* @inheritdoc
131165
*/
@@ -274,7 +308,7 @@ public function isReadOnly(): bool
274308
*/
275309
public function setNestedSchema(Schema $schema): PropertyInterface
276310
{
277-
$this->schema = $schema;
311+
$this->nestedSchema = $schema;
278312
return $this;
279313
}
280314

@@ -283,7 +317,7 @@ public function setNestedSchema(Schema $schema): PropertyInterface
283317
*/
284318
public function getNestedSchema(): ?Schema
285319
{
286-
return $this->schema;
320+
return $this->nestedSchema;
287321
}
288322

289323
/**

src/Model/Property/PropertyInterface.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,11 @@ public function setType(PropertyType $type = null, PropertyType $outputType = nu
4949

5050
/**
5151
* @param bool $outputType If set to true the output type hint will be returned (may differ from the base type)
52-
*
52+
* @param string[] $skipDecorators Provide a set of decorators (FQCN) which shouldn't be applied
53+
* (might be necessary to avoid infinite loops for recursive calls)
5354
* @return string
5455
*/
55-
public function getTypeHint(bool $outputType = false): string;
56+
public function getTypeHint(bool $outputType = false, array $skipDecorators = []): string;
5657

5758
/**
5859
* @param TypeHintDecoratorInterface $typeHintDecorator
@@ -61,6 +62,7 @@ public function getTypeHint(bool $outputType = false): string;
6162
*/
6263
public function addTypeHintDecorator(TypeHintDecoratorInterface $typeHintDecorator): PropertyInterface;
6364

65+
public function getTypeHintDecorators(): array;
6466
/**
6567
* Get a description for the property. If no description is available an empty string will be returned
6668
*
@@ -203,4 +205,14 @@ public function getNestedSchema(): ?Schema;
203205
* @return JsonSchema
204206
*/
205207
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;
213+
214+
/**
215+
* Check if the property set up is finished
216+
*/
217+
public function isResolved(): bool;
206218
}

src/Model/Property/PropertyProxy.php

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,23 @@ 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+
5168
/**
5269
* Get the property out of the resolved definitions collection to proxy function calls
5370
*
@@ -77,9 +94,9 @@ public function setType(PropertyType $type = null, PropertyType $outputType = nu
7794
/**
7895
* @inheritdoc
7996
*/
80-
public function getTypeHint(bool $outputType = false): string
97+
public function getTypeHint(bool $outputType = false, array $skipDecorators = []): string
8198
{
82-
return $this->getProperty()->getTypeHint($outputType);
99+
return $this->getProperty()->getTypeHint($outputType, $skipDecorators);
83100
}
84101

85102
/**
@@ -89,6 +106,13 @@ public function addTypeHintDecorator(TypeHintDecoratorInterface $typeHintDecorat
89106
{
90107
return $this->getProperty()->addTypeHintDecorator($typeHintDecorator);
91108
}
109+
/**
110+
* @inheritdoc
111+
*/
112+
public function getTypeHintDecorators(): array
113+
{
114+
return $this->getProperty()->getTypeHintDecorators();
115+
}
92116

93117
/**
94118
* @inheritdoc

src/Model/Schema.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ class Schema
5656
/** @var SchemaDefinitionDictionary */
5757
protected $schemaDefinitionDictionary;
5858

59+
/** @var int */
60+
private $resolvedProperties = 0;
61+
/** @var callable[] */
62+
private $onAllPropertiesResolvedCallbacks = [];
63+
5964
/**
6065
* Schema constructor.
6166
*
@@ -106,6 +111,15 @@ public function getDescription(): string
106111
return $this->description;
107112
}
108113

114+
public function onAllPropertiesResolved(callable $callback): self
115+
{
116+
$this->resolvedProperties === count($this->properties)
117+
? $callback()
118+
: $this->onAllPropertiesResolvedCallbacks[] = $callback;
119+
120+
return $this;
121+
}
122+
109123
/**
110124
* @return PropertyInterface[]
111125
*/
@@ -152,6 +166,16 @@ public function addProperty(PropertyInterface $property): self
152166
{
153167
if (!isset($this->properties[$property->getName()])) {
154168
$this->properties[$property->getName()] = $property;
169+
170+
$property->onResolve(function () {
171+
if (++$this->resolvedProperties === count($this->properties)) {
172+
foreach ($this->onAllPropertiesResolvedCallbacks as $callback) {
173+
$callback();
174+
175+
$this->onAllPropertiesResolvedCallbacks = [];
176+
}
177+
}
178+
});
155179
} else {
156180
// TODO tests:
157181
// testConditionalObjectProperty
@@ -272,6 +296,11 @@ public function getMethods(): array
272296
return $this->methods;
273297
}
274298

299+
public function hasMethod(string $methodKey): bool
300+
{
301+
return isset($this->methods[$methodKey]);
302+
}
303+
275304
/**
276305
* @return string[]
277306
*/

src/Model/SchemaDefinition/SchemaDefinition.php

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class SchemaDefinition
3131
protected $schema;
3232
/** @var ResolvedDefinitionsCollection */
3333
protected $resolvedPaths;
34+
/** @var array */
35+
protected $unresolvedProxies = [];
3436

3537
/**
3638
* SchemaDefinition constructor.
@@ -92,21 +94,35 @@ public function resolveReference(
9294
$this->resolvedPaths->offsetSet($key, null);
9395

9496
try {
95-
$this->resolvedPaths->offsetSet($key, (new PropertyFactory(new PropertyProcessorFactory()))
97+
$property = (new PropertyFactory(new PropertyProcessorFactory()))
9698
->create(
9799
$propertyMetaDataCollection,
98100
$this->schemaProcessor,
99101
$this->schema,
100102
$propertyName,
101103
$this->source->withJson($jsonSchema)
102-
)
103-
);
104+
);
105+
$this->resolvedPaths->offsetSet($key, $property);
106+
107+
/** @var PropertyProxy $proxy */
108+
foreach ($this->unresolvedProxies[$key] ?? [] as $proxy) {
109+
$proxy->resolve();
110+
}
111+
112+
unset($this->unresolvedProxies[$key]);
104113
} catch (PHPModelGeneratorException $exception) {
105114
$this->resolvedPaths->offsetUnset($key);
106115
throw $exception;
107116
}
108117
}
109118

110-
return new PropertyProxy($propertyName, $this->source, $this->resolvedPaths, $key);
119+
$proxy = new PropertyProxy($propertyName, $this->source, $this->resolvedPaths, $key);
120+
$this->unresolvedProxies[$key][] = $proxy;
121+
122+
if ($this->resolvedPaths->offsetGet($key)) {
123+
$proxy->resolve();
124+
}
125+
126+
return $proxy;
111127
}
112128
}

src/Model/Validator/AbstractComposedPropertyValidator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*
1212
* @package PHPModelGenerator\Model\Validator
1313
*/
14-
abstract class AbstractComposedPropertyValidator extends PropertyTemplateValidator
14+
abstract class AbstractComposedPropertyValidator extends ExtractedMethodValidator
1515
{
1616
/** @var string */
1717
protected $compositionProcessor;

src/Model/Validator/AdditionalPropertiesValidator.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ public function __construct(
6767
new Property($propertyName ?? $schema->getClassName(), null, $propertiesStructure),
6868
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'AdditionalProperties.phptpl',
6969
[
70+
'schema' => $schema,
7071
'validationProperty' => $this->validationProperty,
7172
'additionalProperties' => RenderHelper::varExportArray(
7273
array_keys($propertiesStructure->getJson()[static::PROPERTIES_KEY] ?? [])

src/Model/Validator/ArrayItemValidator.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public function __construct(
6363
$property,
6464
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'ArrayItem.phptpl',
6565
[
66+
'schema' => $schema,
6667
'nestedProperty' => $this->nestedProperty,
6768
'viewHelper' => new RenderHelper($schemaProcessor->getGeneratorConfiguration()),
6869
'generatorConfiguration' => $schemaProcessor->getGeneratorConfiguration(),

src/Model/Validator/ArrayTupleValidator.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public function __construct(
6262
new Property($propertyName, null, $propertiesStructure),
6363
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'ArrayTuple.phptpl',
6464
[
65+
'schema' => $schema,
6566
'tupleProperties' => &$this->tupleProperties,
6667
'viewHelper' => new RenderHelper($schemaProcessor->getGeneratorConfiguration()),
6768
'generatorConfiguration' => $schemaProcessor->getGeneratorConfiguration(),

0 commit comments

Comments
 (0)