Skip to content

Commit b3deda8

Browse files
authored
Merge pull request #68 from wol-soft/65-recursive-reference-resolving
Fix recursive reference resolving
2 parents 1294d21 + 3a8dbf1 commit b3deda8

File tree

65 files changed

+813
-222
lines changed

Some content is hidden

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

65 files changed

+813
-222
lines changed

src/Model/Property/AbstractProperty.php

Lines changed: 4 additions & 3 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,7 +16,7 @@
1516
*/
1617
abstract class AbstractProperty implements PropertyInterface
1718
{
18-
use JsonSchemaTrait;
19+
use JsonSchemaTrait, ResolvableTrait;
1920

2021
/** @var string */
2122
protected $name = '';
@@ -71,14 +72,14 @@ protected function processAttributeName(string $name): string
7172
{
7273
$attributeName = preg_replace_callback(
7374
'/([a-z][a-z0-9]*)([A-Z])/',
74-
function ($matches) {
75+
static function (array $matches): string {
7576
return "{$matches[1]}-{$matches[2]}";
7677
},
7778
$name
7879
);
7980

8081
$elements = array_map(
81-
function ($element) {
82+
static function (string $element): string {
8283
return ucfirst(strtolower($element));
8384
},
8485
preg_split('/[^a-z0-9]/i', $attributeName)

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 (): void {
47+
$this->resolve();
48+
});
4549
}
4650

4751
/**

src/Model/Property/Property.php

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,16 @@ 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+
/** @var int Track the amount of unresolved validators */
48+
private $pendingValidators = 0;
49+
4650
/**
4751
* Property constructor.
4852
*
@@ -59,6 +63,8 @@ public function __construct(string $name, ?PropertyType $type, JsonSchema $jsonS
5963

6064
$this->type = $type;
6165
$this->description = $description;
66+
67+
$this->resolve();
6268
}
6369

6470
/**
@@ -94,26 +100,47 @@ public function setType(PropertyType $type = null, PropertyType $outputType = nu
94100
/**
95101
* @inheritdoc
96102
*/
97-
public function getTypeHint(bool $outputType = false): string
103+
public function getTypeHint(bool $outputType = false, array $skipDecorators = []): string
98104
{
105+
if (isset($this->renderedTypeHints[$outputType])) {
106+
return $this->renderedTypeHints[$outputType];
107+
}
108+
109+
static $skipDec = [];
110+
111+
$additionalSkips = array_diff($skipDecorators, $skipDec);
112+
$skipDec = array_merge($skipDec, $additionalSkips);
113+
99114
$input = [$outputType && $this->outputType !== null ? $this->outputType : $this->type];
100115

101116
// If the output type differs from an input type also accept the output type
102117
if (!$outputType && $this->outputType !== null && $this->outputType !== $this->type) {
103118
$input = [$this->type, $this->outputType];
104119
}
105120

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

109-
foreach ($this->typeHintDecorators as $decorator) {
110-
$typeHint = $decorator->decorate($typeHint, $outputType);
111-
}
126+
$filteredDecorators = array_filter(
127+
$this->typeHintDecorators,
128+
static function (TypeHintDecoratorInterface $decorator) use ($skipDec): bool {
129+
return !in_array(get_class($decorator), $skipDec);
130+
}
131+
);
132+
133+
foreach ($filteredDecorators as $decorator) {
134+
$typeHint = $decorator->decorate($typeHint, $outputType);
135+
}
136+
137+
return $typeHint;
138+
}, $input))
139+
);
112140

113-
return $typeHint;
114-
}, $input)));
141+
$skipDec = array_diff($skipDec, $additionalSkips);
115142

116-
return $input ?: 'mixed';
143+
return $this->renderedTypeHints[$outputType] = $input ?: 'mixed';
117144
}
118145

119146
/**
@@ -139,6 +166,18 @@ public function getDescription(): string
139166
*/
140167
public function addValidator(PropertyValidatorInterface $validator, int $priority = 99): PropertyInterface
141168
{
169+
if (!$validator->isResolved()) {
170+
$this->isResolved = false;
171+
172+
$this->pendingValidators++;
173+
174+
$validator->onResolve(function () {
175+
if (--$this->pendingValidators === 0) {
176+
$this->resolve();
177+
}
178+
});
179+
}
180+
142181
$this->validators[] = new Validator($validator, $priority);
143182

144183
return $this;
@@ -169,7 +208,7 @@ public function getOrderedValidators(): array
169208
{
170209
usort(
171210
$this->validators,
172-
function (Validator $validator, Validator $comparedValidator) {
211+
static function (Validator $validator, Validator $comparedValidator): int {
173212
if ($validator->getPriority() == $comparedValidator->getPriority()) {
174213
return 0;
175214
}
@@ -178,7 +217,7 @@ function (Validator $validator, Validator $comparedValidator) {
178217
);
179218

180219
return array_map(
181-
function (Validator $validator) {
220+
static function (Validator $validator): PropertyValidatorInterface {
182221
return $validator->getValidator();
183222
},
184223
$this->validators
@@ -274,7 +313,7 @@ public function isReadOnly(): bool
274313
*/
275314
public function setNestedSchema(Schema $schema): PropertyInterface
276315
{
277-
$this->schema = $schema;
316+
$this->nestedSchema = $schema;
278317
return $this;
279318
}
280319

@@ -283,7 +322,7 @@ public function setNestedSchema(Schema $schema): PropertyInterface
283322
*/
284323
public function getNestedSchema(): ?Schema
285324
{
286-
return $this->schema;
325+
return $this->nestedSchema;
287326
}
288327

289328
/**

src/Model/Property/PropertyInterface.php

Lines changed: 5 additions & 3 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
@@ -49,10 +50,11 @@ public function setType(PropertyType $type = null, PropertyType $outputType = nu
4950

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

5759
/**
5860
* @param TypeHintDecoratorInterface $typeHintDecorator

src/Model/Property/PropertyProxy.php

Lines changed: 9 additions & 13 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
@@ -77,9 +77,9 @@ public function setType(PropertyType $type = null, PropertyType $outputType = nu
7777
/**
7878
* @inheritdoc
7979
*/
80-
public function getTypeHint(bool $outputType = false): string
80+
public function getTypeHint(bool $outputType = false, array $skipDecorators = []): string
8181
{
82-
return $this->getProperty()->getTypeHint($outputType);
82+
return $this->getProperty()->getTypeHint($outputType, $skipDecorators);
8383
}
8484

8585
/**
@@ -127,9 +127,12 @@ public function filterValidators(callable $filter): PropertyInterface
127127
*/
128128
public function getOrderedValidators(): array
129129
{
130-
return array_map(function (PropertyValidatorInterface $propertyValidator): PropertyValidatorInterface {
131-
return $propertyValidator->withProperty($this);
132-
}, $this->getProperty()->getOrderedValidators());
130+
return array_map(
131+
function (PropertyValidatorInterface $propertyValidator): PropertyValidatorInterface {
132+
return $propertyValidator->withProperty($this);
133+
},
134+
$this->getProperty()->getOrderedValidators()
135+
);
133136
}
134137

135138
/**
@@ -247,11 +250,4 @@ public function isInternal(): bool
247250
{
248251
return $this->getProperty()->isInternal();
249252
}
250-
251-
public function __clone()
252-
{
253-
$cloneKey = $this->key . uniqid();
254-
$this->definitionsCollection->offsetSet($cloneKey, clone $this->definitionsCollection->offsetGet($this->key));
255-
$this->key = $cloneKey;
256-
}
257253
}

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: 31 additions & 2 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,12 +111,21 @@ 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
*/
112126
public function getProperties(): array
113127
{
114-
$hasSchemaDependencyValidator = function (PropertyInterface $property): bool {
128+
$hasSchemaDependencyValidator = static function (PropertyInterface $property): bool {
115129
foreach ($property->getValidators() as $validator) {
116130
if ($validator->getValidator() instanceof SchemaDependencyValidator) {
117131
return true;
@@ -125,7 +139,7 @@ public function getProperties(): array
125139
// of the validation process for correct exception order of the messages
126140
usort(
127141
$this->properties,
128-
function (
142+
static function (
129143
PropertyInterface $property,
130144
PropertyInterface $comparedProperty
131145
) use ($hasSchemaDependencyValidator): int {
@@ -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 (): void {
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
*/

0 commit comments

Comments
 (0)