Skip to content

Commit

Permalink
Merge pull request #68 from wol-soft/65-recursive-reference-resolving
Browse files Browse the repository at this point in the history
Fix recursive reference resolving
  • Loading branch information
wol-soft authored Mar 30, 2023
2 parents 1294d21 + 3a8dbf1 commit b3deda8
Show file tree
Hide file tree
Showing 65 changed files with 813 additions and 222 deletions.
7 changes: 4 additions & 3 deletions src/Model/Property/AbstractProperty.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPModelGenerator\Exception\SchemaException;
use PHPModelGenerator\Model\SchemaDefinition\JsonSchema;
use PHPModelGenerator\Model\SchemaDefinition\JsonSchemaTrait;
use PHPModelGenerator\Utils\ResolvableTrait;

/**
* Class AbstractProperty
Expand All @@ -15,7 +16,7 @@
*/
abstract class AbstractProperty implements PropertyInterface
{
use JsonSchemaTrait;
use JsonSchemaTrait, ResolvableTrait;

/** @var string */
protected $name = '';
Expand Down Expand Up @@ -71,14 +72,14 @@ protected function processAttributeName(string $name): string
{
$attributeName = preg_replace_callback(
'/([a-z][a-z0-9]*)([A-Z])/',
function ($matches) {
static function (array $matches): string {
return "{$matches[1]}-{$matches[2]}";
},
$name
);

$elements = array_map(
function ($element) {
static function (string $element): string {
return ucfirst(strtolower($element));
},
preg_split('/[^a-z0-9]/i', $attributeName)
Expand Down
4 changes: 4 additions & 0 deletions src/Model/Property/CompositionPropertyDecorator.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ public function __construct(string $propertyName, JsonSchema $jsonSchema, Proper
new ResolvedDefinitionsCollection([self::PROPERTY_KEY => $property]),
self::PROPERTY_KEY
);

$property->onResolve(function (): void {
$this->resolve();
});
}

/**
Expand Down
67 changes: 53 additions & 14 deletions src/Model/Property/Property.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,16 @@ class Property extends AbstractProperty
/** @var Validator[] */
protected $validators = [];
/** @var Schema */
protected $schema;
protected $nestedSchema;
/** @var PropertyDecoratorInterface[] */
public $decorators = [];
/** @var TypeHintDecoratorInterface[] */
public $typeHintDecorators = [];

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

/**
* Property constructor.
*
Expand All @@ -59,6 +63,8 @@ public function __construct(string $name, ?PropertyType $type, JsonSchema $jsonS

$this->type = $type;
$this->description = $description;

$this->resolve();
}

/**
Expand Down Expand Up @@ -94,26 +100,47 @@ public function setType(PropertyType $type = null, PropertyType $outputType = nu
/**
* @inheritdoc
*/
public function getTypeHint(bool $outputType = false): string
public function getTypeHint(bool $outputType = false, array $skipDecorators = []): string
{
if (isset($this->renderedTypeHints[$outputType])) {
return $this->renderedTypeHints[$outputType];
}

static $skipDec = [];

$additionalSkips = array_diff($skipDecorators, $skipDec);
$skipDec = array_merge($skipDec, $additionalSkips);

$input = [$outputType && $this->outputType !== null ? $this->outputType : $this->type];

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

$input = join('|', array_filter(array_map(function (?PropertyType $input) use ($outputType): string {
$typeHint = $input ? $input->getName() : '';
$input = join(
'|',
array_filter(array_map(function (?PropertyType $input) use ($outputType, $skipDec): string {
$typeHint = $input ? $input->getName() : '';

foreach ($this->typeHintDecorators as $decorator) {
$typeHint = $decorator->decorate($typeHint, $outputType);
}
$filteredDecorators = array_filter(
$this->typeHintDecorators,
static function (TypeHintDecoratorInterface $decorator) use ($skipDec): bool {
return !in_array(get_class($decorator), $skipDec);
}
);

foreach ($filteredDecorators as $decorator) {
$typeHint = $decorator->decorate($typeHint, $outputType);
}

return $typeHint;
}, $input))
);

return $typeHint;
}, $input)));
$skipDec = array_diff($skipDec, $additionalSkips);

return $input ?: 'mixed';
return $this->renderedTypeHints[$outputType] = $input ?: 'mixed';
}

/**
Expand All @@ -139,6 +166,18 @@ public function getDescription(): string
*/
public function addValidator(PropertyValidatorInterface $validator, int $priority = 99): PropertyInterface
{
if (!$validator->isResolved()) {
$this->isResolved = false;

$this->pendingValidators++;

$validator->onResolve(function () {
if (--$this->pendingValidators === 0) {
$this->resolve();
}
});
}

$this->validators[] = new Validator($validator, $priority);

return $this;
Expand Down Expand Up @@ -169,7 +208,7 @@ public function getOrderedValidators(): array
{
usort(
$this->validators,
function (Validator $validator, Validator $comparedValidator) {
static function (Validator $validator, Validator $comparedValidator): int {
if ($validator->getPriority() == $comparedValidator->getPriority()) {
return 0;
}
Expand All @@ -178,7 +217,7 @@ function (Validator $validator, Validator $comparedValidator) {
);

return array_map(
function (Validator $validator) {
static function (Validator $validator): PropertyValidatorInterface {
return $validator->getValidator();
},
$this->validators
Expand Down Expand Up @@ -274,7 +313,7 @@ public function isReadOnly(): bool
*/
public function setNestedSchema(Schema $schema): PropertyInterface
{
$this->schema = $schema;
$this->nestedSchema = $schema;
return $this;
}

Expand All @@ -283,7 +322,7 @@ public function setNestedSchema(Schema $schema): PropertyInterface
*/
public function getNestedSchema(): ?Schema
{
return $this->schema;
return $this->nestedSchema;
}

/**
Expand Down
8 changes: 5 additions & 3 deletions src/Model/Property/PropertyInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
use PHPModelGenerator\Model\Validator\PropertyValidatorInterface;
use PHPModelGenerator\PropertyProcessor\Decorator\Property\PropertyDecoratorInterface;
use PHPModelGenerator\PropertyProcessor\Decorator\TypeHint\TypeHintDecoratorInterface;
use PHPModelGenerator\Utils\ResolvableInterface;

/**
* Interface PropertyInterface
*
* @package PHPModelGenerator\Model
*/
interface PropertyInterface
interface PropertyInterface extends ResolvableInterface
{
/**
* @return string
Expand Down Expand Up @@ -49,10 +50,11 @@ public function setType(PropertyType $type = null, PropertyType $outputType = nu

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

/**
* @param TypeHintDecoratorInterface $typeHintDecorator
Expand Down
22 changes: 9 additions & 13 deletions src/Model/Property/PropertyProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class PropertyProxy extends AbstractProperty
* PropertyProxy constructor.
*
* @param string $name The name must be provided separately as the name is not bound to the structure of a
* referenced schema. Consequently two properties with different names can refer an identical schema utilizing the
* referenced schema. Consequently, two properties with different names can refer an identical schema utilizing the
* PropertyProxy. By providing a name to each of the proxies the resulting properties will get the correct names.
* @param JsonSchema $jsonSchema
* @param ResolvedDefinitionsCollection $definitionsCollection
Expand Down Expand Up @@ -77,9 +77,9 @@ public function setType(PropertyType $type = null, PropertyType $outputType = nu
/**
* @inheritdoc
*/
public function getTypeHint(bool $outputType = false): string
public function getTypeHint(bool $outputType = false, array $skipDecorators = []): string
{
return $this->getProperty()->getTypeHint($outputType);
return $this->getProperty()->getTypeHint($outputType, $skipDecorators);
}

/**
Expand Down Expand Up @@ -127,9 +127,12 @@ public function filterValidators(callable $filter): PropertyInterface
*/
public function getOrderedValidators(): array
{
return array_map(function (PropertyValidatorInterface $propertyValidator): PropertyValidatorInterface {
return $propertyValidator->withProperty($this);
}, $this->getProperty()->getOrderedValidators());
return array_map(
function (PropertyValidatorInterface $propertyValidator): PropertyValidatorInterface {
return $propertyValidator->withProperty($this);
},
$this->getProperty()->getOrderedValidators()
);
}

/**
Expand Down Expand Up @@ -247,11 +250,4 @@ public function isInternal(): bool
{
return $this->getProperty()->isInternal();
}

public function __clone()
{
$cloneKey = $this->key . uniqid();
$this->definitionsCollection->offsetSet($cloneKey, clone $this->definitionsCollection->offsetGet($this->key));
$this->key = $cloneKey;
}
}
4 changes: 2 additions & 2 deletions src/Model/RenderJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ protected function renderClass(GeneratorConfiguration $generatorConfiguration):
'true' => true,
'baseValidatorsWithoutCompositions' => array_filter(
$this->schema->getBaseValidators(),
function ($validator) {
static function ($validator): bool {
return !is_a($validator, AbstractComposedPropertyValidator::class);
}
),
Expand Down Expand Up @@ -167,7 +167,7 @@ protected function getUseForSchema(GeneratorConfiguration $generatorConfiguratio
);

// filter out non-compound uses and uses which link to the current namespace
$use = array_filter($use, function ($classPath) use ($namespace) {
$use = array_filter($use, static function ($classPath) use ($namespace): bool {
return strstr(trim(str_replace("$namespace", '', $classPath), '\\'), '\\') ||
(!strstr($classPath, '\\') && !empty($namespace));
});
Expand Down
33 changes: 31 additions & 2 deletions src/Model/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ class Schema
/** @var SchemaDefinitionDictionary */
protected $schemaDefinitionDictionary;

/** @var int */
private $resolvedProperties = 0;
/** @var callable[] */
private $onAllPropertiesResolvedCallbacks = [];

/**
* Schema constructor.
*
Expand Down Expand Up @@ -106,12 +111,21 @@ public function getDescription(): string
return $this->description;
}

public function onAllPropertiesResolved(callable $callback): self
{
$this->resolvedProperties === count($this->properties)
? $callback()
: $this->onAllPropertiesResolvedCallbacks[] = $callback;

return $this;
}

/**
* @return PropertyInterface[]
*/
public function getProperties(): array
{
$hasSchemaDependencyValidator = function (PropertyInterface $property): bool {
$hasSchemaDependencyValidator = static function (PropertyInterface $property): bool {
foreach ($property->getValidators() as $validator) {
if ($validator->getValidator() instanceof SchemaDependencyValidator) {
return true;
Expand All @@ -125,7 +139,7 @@ public function getProperties(): array
// of the validation process for correct exception order of the messages
usort(
$this->properties,
function (
static function (
PropertyInterface $property,
PropertyInterface $comparedProperty
) use ($hasSchemaDependencyValidator): int {
Expand All @@ -152,6 +166,16 @@ public function addProperty(PropertyInterface $property): self
{
if (!isset($this->properties[$property->getName()])) {
$this->properties[$property->getName()] = $property;

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

$this->onAllPropertiesResolvedCallbacks = [];
}
}
});
} else {
// TODO tests:
// testConditionalObjectProperty
Expand Down Expand Up @@ -272,6 +296,11 @@ public function getMethods(): array
return $this->methods;
}

public function hasMethod(string $methodKey): bool
{
return isset($this->methods[$methodKey]);
}

/**
* @return string[]
*/
Expand Down
Loading

0 comments on commit b3deda8

Please sign in to comment.