diff --git a/src/Model/Property/AbstractProperty.php b/src/Model/Property/AbstractProperty.php index dc1153c..ade0c08 100644 --- a/src/Model/Property/AbstractProperty.php +++ b/src/Model/Property/AbstractProperty.php @@ -7,6 +7,7 @@ use PHPModelGenerator\Exception\SchemaException; use PHPModelGenerator\Model\SchemaDefinition\JsonSchema; use PHPModelGenerator\Model\SchemaDefinition\JsonSchemaTrait; +use PHPModelGenerator\Utils\ResolvableTrait; /** * Class AbstractProperty @@ -15,7 +16,7 @@ */ abstract class AbstractProperty implements PropertyInterface { - use JsonSchemaTrait; + use JsonSchemaTrait, ResolvableTrait; /** @var string */ protected $name = ''; @@ -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) diff --git a/src/Model/Property/CompositionPropertyDecorator.php b/src/Model/Property/CompositionPropertyDecorator.php index 621ee88..25ea933 100644 --- a/src/Model/Property/CompositionPropertyDecorator.php +++ b/src/Model/Property/CompositionPropertyDecorator.php @@ -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(); + }); } /** diff --git a/src/Model/Property/Property.php b/src/Model/Property/Property.php index fb5e872..ca8346a 100644 --- a/src/Model/Property/Property.php +++ b/src/Model/Property/Property.php @@ -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. * @@ -59,6 +63,8 @@ public function __construct(string $name, ?PropertyType $type, JsonSchema $jsonS $this->type = $type; $this->description = $description; + + $this->resolve(); } /** @@ -94,8 +100,17 @@ 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 @@ -103,17 +118,29 @@ public function getTypeHint(bool $outputType = false): string $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'; } /** @@ -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; @@ -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; } @@ -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 @@ -274,7 +313,7 @@ public function isReadOnly(): bool */ public function setNestedSchema(Schema $schema): PropertyInterface { - $this->schema = $schema; + $this->nestedSchema = $schema; return $this; } @@ -283,7 +322,7 @@ public function setNestedSchema(Schema $schema): PropertyInterface */ public function getNestedSchema(): ?Schema { - return $this->schema; + return $this->nestedSchema; } /** diff --git a/src/Model/Property/PropertyInterface.php b/src/Model/Property/PropertyInterface.php index b784d57..cb13895 100644 --- a/src/Model/Property/PropertyInterface.php +++ b/src/Model/Property/PropertyInterface.php @@ -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 @@ -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 diff --git a/src/Model/Property/PropertyProxy.php b/src/Model/Property/PropertyProxy.php index f152ae6..e7d0541 100644 --- a/src/Model/Property/PropertyProxy.php +++ b/src/Model/Property/PropertyProxy.php @@ -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 @@ -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); } /** @@ -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() + ); } /** @@ -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; - } } diff --git a/src/Model/RenderJob.php b/src/Model/RenderJob.php index ae9e79f..6c618d8 100644 --- a/src/Model/RenderJob.php +++ b/src/Model/RenderJob.php @@ -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); } ), @@ -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)); }); diff --git a/src/Model/Schema.php b/src/Model/Schema.php index 733de75..ed7a17a 100644 --- a/src/Model/Schema.php +++ b/src/Model/Schema.php @@ -56,6 +56,11 @@ class Schema /** @var SchemaDefinitionDictionary */ protected $schemaDefinitionDictionary; + /** @var int */ + private $resolvedProperties = 0; + /** @var callable[] */ + private $onAllPropertiesResolvedCallbacks = []; + /** * Schema constructor. * @@ -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; @@ -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 { @@ -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 @@ -272,6 +296,11 @@ public function getMethods(): array return $this->methods; } + public function hasMethod(string $methodKey): bool + { + return isset($this->methods[$methodKey]); + } + /** * @return string[] */ diff --git a/src/Model/SchemaDefinition/SchemaDefinition.php b/src/Model/SchemaDefinition/SchemaDefinition.php index d24f5ba..17af985 100644 --- a/src/Model/SchemaDefinition/SchemaDefinition.php +++ b/src/Model/SchemaDefinition/SchemaDefinition.php @@ -31,6 +31,8 @@ class SchemaDefinition protected $schema; /** @var ResolvedDefinitionsCollection */ protected $resolvedPaths; + /** @var array */ + protected $unresolvedProxies = []; /** * SchemaDefinition constructor. @@ -92,21 +94,37 @@ public function resolveReference( $this->resolvedPaths->offsetSet($key, null); try { - $this->resolvedPaths->offsetSet($key, (new PropertyFactory(new PropertyProcessorFactory())) + $property = (new PropertyFactory(new PropertyProcessorFactory())) ->create( $propertyMetaDataCollection, $this->schemaProcessor, $this->schema, $propertyName, $this->source->withJson($jsonSchema) - ) - ); + ); + $this->resolvedPaths->offsetSet($key, $property); + + /** @var PropertyProxy $proxy */ + foreach ($this->unresolvedProxies[$key] ?? [] as $proxy) { + $proxy->resolve(); + } + + unset($this->unresolvedProxies[$key]); + + return $property; } catch (PHPModelGeneratorException $exception) { $this->resolvedPaths->offsetUnset($key); throw $exception; } } - return new PropertyProxy($propertyName, $this->source, $this->resolvedPaths, $key); + $proxy = new PropertyProxy($propertyName, $this->source, $this->resolvedPaths, $key); + $this->unresolvedProxies[$key][] = $proxy; + + if ($this->resolvedPaths->offsetGet($key)) { + $proxy->resolve(); + } + + return $proxy; } } diff --git a/src/Model/Validator/AbstractComposedPropertyValidator.php b/src/Model/Validator/AbstractComposedPropertyValidator.php index 2cc749e..f080814 100644 --- a/src/Model/Validator/AbstractComposedPropertyValidator.php +++ b/src/Model/Validator/AbstractComposedPropertyValidator.php @@ -11,7 +11,7 @@ * * @package PHPModelGenerator\Model\Validator */ -abstract class AbstractComposedPropertyValidator extends PropertyTemplateValidator +abstract class AbstractComposedPropertyValidator extends ExtractedMethodValidator { /** @var string */ protected $compositionProcessor; diff --git a/src/Model/Validator/AbstractPropertyValidator.php b/src/Model/Validator/AbstractPropertyValidator.php index 69f7561..2e9990c 100644 --- a/src/Model/Validator/AbstractPropertyValidator.php +++ b/src/Model/Validator/AbstractPropertyValidator.php @@ -6,6 +6,7 @@ use PHPModelGenerator\Model\Property\PropertyInterface; use PHPModelGenerator\Model\Validator; +use PHPModelGenerator\Utils\ResolvableTrait; /** * Class AbstractPropertyValidator @@ -14,6 +15,8 @@ */ abstract class AbstractPropertyValidator implements PropertyValidatorInterface { + use ResolvableTrait; + /** @var string */ protected $exceptionClass; /** @var array */ @@ -79,8 +82,10 @@ public function getValidatorSetUp(): string */ protected function removeRequiredPropertyValidator(PropertyInterface $property): void { - $property->filterValidators(function (Validator $validator): bool { - return !is_a($validator->getValidator(), RequiredPropertyValidator::class); + $property->onResolve(static function () use ($property): void { + $property->filterValidators(static function (Validator $validator): bool { + return !is_a($validator->getValidator(), RequiredPropertyValidator::class); + }); }); } } diff --git a/src/Model/Validator/AdditionalPropertiesValidator.php b/src/Model/Validator/AdditionalPropertiesValidator.php index 73913c6..494fd9f 100644 --- a/src/Model/Validator/AdditionalPropertiesValidator.php +++ b/src/Model/Validator/AdditionalPropertiesValidator.php @@ -61,12 +61,17 @@ public function __construct( $propertiesStructure->withJson($propertiesStructure->getJson()[static::ADDITIONAL_PROPERTIES_KEY]) ); + $this->validationProperty->onResolve(function (): void { + $this->resolve(); + }); + $patternProperties = array_keys($schema->getJsonSchema()->getJson()['patternProperties'] ?? []); parent::__construct( new Property($propertyName ?? $schema->getClassName(), null, $propertiesStructure), DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'AdditionalProperties.phptpl', [ + 'schema' => $schema, 'validationProperty' => $this->validationProperty, 'additionalProperties' => RenderHelper::varExportArray( array_keys($propertiesStructure->getJson()[static::PROPERTIES_KEY] ?? []) diff --git a/src/Model/Validator/ArrayItemValidator.php b/src/Model/Validator/ArrayItemValidator.php index b39a9eb..65b91ca 100644 --- a/src/Model/Validator/ArrayItemValidator.php +++ b/src/Model/Validator/ArrayItemValidator.php @@ -21,7 +21,7 @@ * * @package PHPModelGenerator\Model\Validator */ -class ArrayItemValidator extends PropertyTemplateValidator +class ArrayItemValidator extends ExtractedMethodValidator { /** @var string */ private $variableSuffix = ''; @@ -57,12 +57,18 @@ public function __construct( $itemStructure ); - $property->addTypeHintDecorator(new ArrayTypeHintDecorator($this->nestedProperty)); + $this->nestedProperty->onResolve(function () use ($property): void { + $this->resolve(); + + $property->addTypeHintDecorator(new ArrayTypeHintDecorator($this->nestedProperty)); + }); parent::__construct( + $schemaProcessor->getGeneratorConfiguration(), $property, DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'ArrayItem.phptpl', [ + 'schema' => $schema, 'nestedProperty' => $this->nestedProperty, 'viewHelper' => new RenderHelper($schemaProcessor->getGeneratorConfiguration()), 'generatorConfiguration' => $schemaProcessor->getGeneratorConfiguration(), diff --git a/src/Model/Validator/ArrayTupleValidator.php b/src/Model/Validator/ArrayTupleValidator.php index 5581751..cabcb8b 100644 --- a/src/Model/Validator/ArrayTupleValidator.php +++ b/src/Model/Validator/ArrayTupleValidator.php @@ -45,23 +45,29 @@ public function __construct( $propertyFactory = new PropertyFactory(new PropertyProcessorFactory()); $this->tupleProperties = []; + foreach ($propertiesStructure->getJson() as $tupleIndex => $tupleItem) { $tupleItemName = "tuple item #$tupleIndex of array $propertyName"; // an item of the array behaves like a nested property to add item-level validation - $this->tupleProperties[] = $propertyFactory->create( + $tupleProperty = $propertyFactory->create( new PropertyMetaDataCollection([$tupleItemName]), $schemaProcessor, $schema, $tupleItemName, $propertiesStructure->withJson($tupleItem) ); + + $this->tupleProperties[] = $tupleProperty; } + $this->resolve(); + parent::__construct( new Property($propertyName, null, $propertiesStructure), DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'ArrayTuple.phptpl', [ + 'schema' => $schema, 'tupleProperties' => &$this->tupleProperties, 'viewHelper' => new RenderHelper($schemaProcessor->getGeneratorConfiguration()), 'generatorConfiguration' => $schemaProcessor->getGeneratorConfiguration(), diff --git a/src/Model/Validator/ComposedPropertyValidator.php b/src/Model/Validator/ComposedPropertyValidator.php index c8172fa..c1b4858 100644 --- a/src/Model/Validator/ComposedPropertyValidator.php +++ b/src/Model/Validator/ComposedPropertyValidator.php @@ -5,6 +5,7 @@ namespace PHPModelGenerator\Model\Validator; use PHPModelGenerator\Exception\ComposedValue\InvalidComposedValueException; +use PHPModelGenerator\Model\GeneratorConfiguration; use PHPModelGenerator\Model\Property\CompositionPropertyDecorator; use PHPModelGenerator\Model\Property\PropertyInterface; use PHPModelGenerator\Model\Validator; @@ -16,21 +17,17 @@ */ class ComposedPropertyValidator extends AbstractComposedPropertyValidator { - /** - * ComposedPropertyValidator constructor. - * - * @param PropertyInterface $property - * @param CompositionPropertyDecorator[] $composedProperties - * @param string $compositionProcessor - * @param array $validatorVariables - */ public function __construct( + GeneratorConfiguration $generatorConfiguration, PropertyInterface $property, array $composedProperties, string $compositionProcessor, array $validatorVariables ) { + $this->isResolved = true; + parent::__construct( + $generatorConfiguration, $property, DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'ComposedItem.phptpl', $validatorVariables, @@ -65,9 +62,12 @@ public function withoutNestedCompositionValidation(): self { $validator = clone $this; + /** @var CompositionPropertyDecorator $composedProperty */ foreach ($validator->composedProperties as $composedProperty) { - $composedProperty->filterValidators(function (Validator $validator): bool { - return !is_a($validator->getValidator(), AbstractComposedPropertyValidator::class); + $composedProperty->onResolve(static function () use ($composedProperty): void { + $composedProperty->filterValidators(static function (Validator $validator): bool { + return !is_a($validator->getValidator(), AbstractComposedPropertyValidator::class); + }); }); } diff --git a/src/Model/Validator/ConditionalPropertyValidator.php b/src/Model/Validator/ConditionalPropertyValidator.php index d8e46b3..70d9a16 100644 --- a/src/Model/Validator/ConditionalPropertyValidator.php +++ b/src/Model/Validator/ConditionalPropertyValidator.php @@ -5,7 +5,7 @@ namespace PHPModelGenerator\Model\Validator; use PHPModelGenerator\Exception\ComposedValue\ConditionalException; -use PHPModelGenerator\Model\Property\CompositionPropertyDecorator; +use PHPModelGenerator\Model\GeneratorConfiguration; use PHPModelGenerator\Model\Property\PropertyInterface; use PHPModelGenerator\PropertyProcessor\ComposedValue\IfProcessor; @@ -16,19 +16,16 @@ */ class ConditionalPropertyValidator extends AbstractComposedPropertyValidator { - /** - * ConditionalPropertyValidator constructor. - * - * @param PropertyInterface $property - * @param CompositionPropertyDecorator[] $composedProperties - * @param array $validatorVariables - */ public function __construct( + GeneratorConfiguration $generatorConfiguration, PropertyInterface $property, array $composedProperties, array $validatorVariables ) { + $this->isResolved = true; + parent::__construct( + $generatorConfiguration, $property, DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'ConditionalComposedItem.phptpl', $validatorVariables, diff --git a/src/Model/Validator/ExtractedMethodValidator.php b/src/Model/Validator/ExtractedMethodValidator.php new file mode 100644 index 0000000..9574045 --- /dev/null +++ b/src/Model/Validator/ExtractedMethodValidator.php @@ -0,0 +1,77 @@ +generatorConfiguration = $generatorConfiguration; + + $this->extractedMethodName = sprintf( + 'validate%s_%s_%s', + str_replace(' ', '', ucfirst($property->getAttribute())), + str_replace('Validator', '', substr(strrchr(static::class, '\\'), 1)), + uniqid() + ); + + parent::__construct($property, $template, $templateValues, $exceptionClass, $exceptionParams); + } + + public function getMethod(): MethodInterface + { + return new class ($this, $this->generatorConfiguration) implements MethodInterface { + /** @var ExtractedMethodValidator */ + private $validator; + /** @var GeneratorConfiguration */ + private $generatorConfiguration; + + public function __construct( + ExtractedMethodValidator $validator, + GeneratorConfiguration $generatorConfiguration + ) { + $this->validator = $validator; + $this->generatorConfiguration = $generatorConfiguration; + } + + public function getCode(): string + { + $renderHelper = new RenderHelper($this->generatorConfiguration); + return "private function {$this->validator->getExtractedMethodName()}(&\$value): void { + {$this->validator->getValidatorSetUp()} + + if ({$this->validator->getCheck()}) { + {$renderHelper->validationError($this->validator)} + } + }"; + } + }; + } + + public function getExtractedMethodName(): string + { + return $this->extractedMethodName; + } +} diff --git a/src/Model/Validator/FilterValidator.php b/src/Model/Validator/FilterValidator.php index 22523c3..dd87c00 100644 --- a/src/Model/Validator/FilterValidator.php +++ b/src/Model/Validator/FilterValidator.php @@ -46,6 +46,8 @@ public function __construct( array $filterOptions = [], ?TransformingFilterInterface $transformingFilter = null ) { + $this->isResolved = true; + $this->filter = $filter; $this->filterOptions = $filterOptions; @@ -62,7 +64,7 @@ public function __construct( // check if the given value has a type matched by the filter 'typeCheck' => !empty($filter->getAcceptedTypes()) ? '(' . - implode(' && ', array_map(function (string $type) use ($property): string { + implode(' && ', array_map(static function (string $type) use ($property): string { return ReflectionTypeCheckValidator::fromType($type, $property)->getCheck(); }, $this->mapDataTypes($filter->getAcceptedTypes()))) . ')' @@ -209,7 +211,7 @@ private function validateFilterCompatibilityWithTransformedType( */ private function mapDataTypes(array $acceptedTypes): array { - return array_map(function (string $jsonSchemaType): string { + return array_map(static function (string $jsonSchemaType): string { switch ($jsonSchemaType) { case 'integer': return 'int'; case 'number': return 'float'; diff --git a/src/Model/Validator/FormatValidator.php b/src/Model/Validator/FormatValidator.php index 76be1a1..a4e692a 100644 --- a/src/Model/Validator/FormatValidator.php +++ b/src/Model/Validator/FormatValidator.php @@ -31,6 +31,7 @@ public function __construct( FormatValidatorInterface $validator, array $exceptionParams = [] ) { + $this->isResolved = true; $this->validator = $validator; parent::__construct($property, FormatException::class, $exceptionParams); diff --git a/src/Model/Validator/MultiTypeCheckValidator.php b/src/Model/Validator/MultiTypeCheckValidator.php index 4f2c0e0..f7efe6b 100644 --- a/src/Model/Validator/MultiTypeCheckValidator.php +++ b/src/Model/Validator/MultiTypeCheckValidator.php @@ -38,7 +38,7 @@ public function __construct(array $types, PropertyInterface $property, bool $all join( ' && ', array_map( - function (string $allowedType) use ($property) : string { + static function (string $allowedType) use ($property) : string { return ReflectionTypeCheckValidator::fromType($allowedType, $property)->getCheck(); }, $types diff --git a/src/Model/Validator/NoAdditionalPropertiesValidator.php b/src/Model/Validator/NoAdditionalPropertiesValidator.php index f5610c3..aa53fce 100644 --- a/src/Model/Validator/NoAdditionalPropertiesValidator.php +++ b/src/Model/Validator/NoAdditionalPropertiesValidator.php @@ -23,6 +23,8 @@ class NoAdditionalPropertiesValidator extends PropertyTemplateValidator */ public function __construct(PropertyInterface $property, array $json) { + $this->isResolved = true; + parent::__construct( $property, DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'NoAdditionalProperties.phptpl', diff --git a/src/Model/Validator/PatternPropertiesValidator.php b/src/Model/Validator/PatternPropertiesValidator.php index 470b6ef..c28ea3b 100644 --- a/src/Model/Validator/PatternPropertiesValidator.php +++ b/src/Model/Validator/PatternPropertiesValidator.php @@ -59,6 +59,10 @@ public function __construct( $propertyStructure ); + $this->validationProperty->onResolve(function (): void { + $this->resolve(); + }); + parent::__construct( new Property($schema->getClassName(), null, $propertyStructure), DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'PatternProperties.phptpl', diff --git a/src/Model/Validator/PropertyDependencyValidator.php b/src/Model/Validator/PropertyDependencyValidator.php index 250cdcb..2c1f17f 100644 --- a/src/Model/Validator/PropertyDependencyValidator.php +++ b/src/Model/Validator/PropertyDependencyValidator.php @@ -23,6 +23,8 @@ class PropertyDependencyValidator extends PropertyTemplateValidator */ public function __construct(PropertyInterface $property, array $dependencies) { + $this->isResolved = true; + parent::__construct( $property, DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'PropertyDependency.phptpl', diff --git a/src/Model/Validator/PropertyNamesValidator.php b/src/Model/Validator/PropertyNamesValidator.php index f8859be..6f753f1 100644 --- a/src/Model/Validator/PropertyNamesValidator.php +++ b/src/Model/Validator/PropertyNamesValidator.php @@ -36,10 +36,12 @@ public function __construct( Schema $schema, JsonSchema $propertiesNames ) { + $this->isResolved = true; + $nameValidationProperty = (new StringProcessor(new PropertyMetaDataCollection(), $schemaProcessor, $schema)) ->process('property name', $propertiesNames) // the property name validator doesn't need type checks or required checks so simply filter them out - ->filterValidators(function (Validator $validator): bool { + ->filterValidators(static function (Validator $validator): bool { return !is_a($validator->getValidator(), RequiredPropertyValidator::class) && !is_a($validator->getValidator(), TypeCheckValidator::class); }); diff --git a/src/Model/Validator/PropertyValidator.php b/src/Model/Validator/PropertyValidator.php index 96b286e..6026081 100644 --- a/src/Model/Validator/PropertyValidator.php +++ b/src/Model/Validator/PropertyValidator.php @@ -30,6 +30,7 @@ public function __construct( string $exceptionClass, array $exceptionParams = [] ) { + $this->isResolved = true; $this->check = $check; parent::__construct($property, $exceptionClass, $exceptionParams); diff --git a/src/Model/Validator/PropertyValidatorInterface.php b/src/Model/Validator/PropertyValidatorInterface.php index 850829d..9893186 100644 --- a/src/Model/Validator/PropertyValidatorInterface.php +++ b/src/Model/Validator/PropertyValidatorInterface.php @@ -5,13 +5,14 @@ namespace PHPModelGenerator\Model\Validator; use PHPModelGenerator\Model\Property\PropertyInterface; +use PHPModelGenerator\Utils\ResolvableInterface; /** * Interface PropertyValidatorInterface * * @package PHPModelGenerator\Model\Validator */ -interface PropertyValidatorInterface +interface PropertyValidatorInterface extends ResolvableInterface { /** * Get the source code for the check to perform diff --git a/src/Model/Validator/SchemaDependencyValidator.php b/src/Model/Validator/SchemaDependencyValidator.php index b5468af..01134b9 100644 --- a/src/Model/Validator/SchemaDependencyValidator.php +++ b/src/Model/Validator/SchemaDependencyValidator.php @@ -31,6 +31,8 @@ class SchemaDependencyValidator extends PropertyTemplateValidator */ public function __construct(SchemaProcessor $schemaProcessor, PropertyInterface $property, Schema $schema) { + $this->isResolved = true; + parent::__construct( $property, DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'SchemaDependency.phptpl', diff --git a/src/PropertyProcessor/ComposedValue/AbstractComposedValueProcessor.php b/src/PropertyProcessor/ComposedValue/AbstractComposedValueProcessor.php index 8d6b184..13bade1 100644 --- a/src/PropertyProcessor/ComposedValue/AbstractComposedValueProcessor.php +++ b/src/PropertyProcessor/ComposedValue/AbstractComposedValueProcessor.php @@ -36,6 +36,8 @@ abstract class AbstractComposedValueProcessor extends AbstractValueProcessor private static $generatedMergedProperties = []; /** @var bool */ private $rootLevelComposition; + /** @var PropertyInterface|null */ + private $mergedProperty = null; /** * AbstractComposedValueProcessor constructor. @@ -73,12 +75,27 @@ protected function generateValidators(PropertyInterface $property, JsonSchema $p $compositionProperties = $this->getCompositionProperties($property, $propertySchema); - $this->transferPropertyType($property, $compositionProperties); + $resolvedCompositions = 0; + foreach ($compositionProperties as $compositionProperty) { + $compositionProperty->onResolve( + function () use (&$resolvedCompositions, $property, $compositionProperties, $propertySchema): void { + if (++$resolvedCompositions === count($compositionProperties)) { + $this->transferPropertyType($property, $compositionProperties); + + $this->mergedProperty = !$this->rootLevelComposition + && $this instanceof MergedComposedPropertiesInterface + ? $this->createMergedProperty($property, $compositionProperties, $propertySchema) + : null; + } + } + ); + } $availableAmount = count($compositionProperties); $property->addValidator( new ComposedPropertyValidator( + $this->schemaProcessor->getGeneratorConfiguration(), $property, $compositionProperties, static::class, @@ -89,13 +106,10 @@ protected function generateValidators(PropertyInterface $property, JsonSchema $p 'availableAmount' => $availableAmount, 'composedValueValidation' => $this->getComposedValueValidation($availableAmount), // if the property is a composed property the resulting value of a validation must be proposed - // to be the final value after the validations (eg. object instantiations may be performed). + // to be the final value after the validations (e.g. object instantiations may be performed). // Otherwise (eg. a NotProcessor) the value must be proposed before the validation 'postPropose' => $this instanceof ComposedPropertiesInterface, - 'mergedProperty' => - !$this->rootLevelComposition && $this instanceof MergedComposedPropertiesInterface - ? $this->createMergedProperty($property, $compositionProperties, $propertySchema) - : null, + 'mergedProperty' => &$this->mergedProperty, 'onlyForDefinedValues' => $propertySchema->getJson()['onlyForDefinedValues'] && $this instanceof ComposedPropertiesInterface, @@ -141,19 +155,21 @@ protected function getCompositionProperties(PropertyInterface $property, JsonSch ) ); - $compositionProperty->filterValidators(function (Validator $validator): bool { - return !is_a($validator->getValidator(), RequiredPropertyValidator::class) && - !is_a($validator->getValidator(), ComposedPropertyValidator::class); + $compositionProperty->onResolve(function () use ($compositionProperty, $property): void { + $compositionProperty->filterValidators(static function (Validator $validator): bool { + return !is_a($validator->getValidator(), RequiredPropertyValidator::class) && + !is_a($validator->getValidator(), ComposedPropertyValidator::class); + }); + + // only create a composed type hint if we aren't a AnyOf or an AllOf processor and the + // compositionProperty contains no object. This results in objects being composed each separately for a + // OneOf processor (e.g. string|ObjectA|ObjectB). For a merged composed property the objects are merged + // together, so it results in string|MergedObject + if (!($this instanceof MergedComposedPropertiesInterface && $compositionProperty->getNestedSchema())) { + $property->addTypeHintDecorator(new CompositionTypeHintDecorator($compositionProperty)); + } }); - // only create a composed type hint if we aren't a AnyOf or an AllOf processor and the compositionProperty - // contains no object. This results in objects being composed each separately for a OneOf processor - // (eg. string|ObjectA|ObjectB). For a merged composed property the objects are merged together so it - // results in string|MergedObject - if (!($this instanceof MergedComposedPropertiesInterface && $compositionProperty->getNestedSchema())) { - $property->addTypeHintDecorator(new CompositionTypeHintDecorator($compositionProperty)); - } - $compositionProperties[] = $compositionProperty; } @@ -171,7 +187,7 @@ private function transferPropertyType(PropertyInterface $property, array $compos $compositionPropertyTypes = array_values( array_unique( array_map( - function (CompositionPropertyDecorator $property): string { + static function (CompositionPropertyDecorator $property): string { return $property->getType() ? $property->getType()->getName() : ''; }, $compositionProperties @@ -296,24 +312,30 @@ private function transferPropertiesToMergedSchema(Schema $mergedPropertySchema, continue; } - foreach ($property->getNestedSchema()->getProperties() as $nestedProperty) { - $mergedPropertySchema->addProperty( - // don't validate fields in merged properties. All fields were validated before corresponding to - // the defined constraints of the composition property. - (clone $nestedProperty)->filterValidators(function (): bool { - return false; - }) - ); - - // the parent schema needs to know about all imports of the nested classes as all properties of the - // nested classes are available in the parent schema (combined schema merging) - $this->schema->addNamespaceTransferDecorator( - new SchemaNamespaceTransferDecorator($property->getNestedSchema()) - ); - } - - // make sure the merged schema knows all imports of the parent schema - $mergedPropertySchema->addNamespaceTransferDecorator(new SchemaNamespaceTransferDecorator($this->schema)); + $property->getNestedSchema()->onAllPropertiesResolved( + function () use ($property, $mergedPropertySchema): void { + foreach ($property->getNestedSchema()->getProperties() as $nestedProperty) { + $mergedPropertySchema->addProperty( + // don't validate fields in merged properties. All fields were validated before + // corresponding to the defined constraints of the composition property. + (clone $nestedProperty)->filterValidators(static function (): bool { + return false; + }) + ); + + // the parent schema needs to know about all imports of the nested classes as all properties + // of the nested classes are available in the parent schema (combined schema merging) + $this->schema->addNamespaceTransferDecorator( + new SchemaNamespaceTransferDecorator($property->getNestedSchema()) + ); + } + + // make sure the merged schema knows all imports of the parent schema + $mergedPropertySchema->addNamespaceTransferDecorator( + new SchemaNamespaceTransferDecorator($this->schema) + ); + } + ); } } diff --git a/src/PropertyProcessor/ComposedValue/IfProcessor.php b/src/PropertyProcessor/ComposedValue/IfProcessor.php index 78ded75..6ef67ae 100644 --- a/src/PropertyProcessor/ComposedValue/IfProcessor.php +++ b/src/PropertyProcessor/ComposedValue/IfProcessor.php @@ -67,9 +67,11 @@ protected function generateValidators(PropertyInterface $property, JsonSchema $p ) ); - $compositionProperty->filterValidators(function (Validator $validator): bool { - return !is_a($validator->getValidator(), RequiredPropertyValidator::class) && - !is_a($validator->getValidator(), ComposedPropertyValidator::class); + $compositionProperty->onResolve(static function () use ($compositionProperty): void { + $compositionProperty->filterValidators(static function (Validator $validator): bool { + return !is_a($validator->getValidator(), RequiredPropertyValidator::class) && + !is_a($validator->getValidator(), ComposedPropertyValidator::class); + }); }); $properties[$compositionElement] = $compositionProperty; @@ -77,6 +79,7 @@ protected function generateValidators(PropertyInterface $property, JsonSchema $p $property->addValidator( new ConditionalPropertyValidator( + $this->schemaProcessor->getGeneratorConfiguration(), $property, $properties, [ diff --git a/src/PropertyProcessor/Decorator/TypeHint/ArrayTypeHintDecorator.php b/src/PropertyProcessor/Decorator/TypeHint/ArrayTypeHintDecorator.php index 94e5e6b..114de5e 100644 --- a/src/PropertyProcessor/Decorator/TypeHint/ArrayTypeHintDecorator.php +++ b/src/PropertyProcessor/Decorator/TypeHint/ArrayTypeHintDecorator.php @@ -16,6 +16,8 @@ class ArrayTypeHintDecorator implements TypeHintDecoratorInterface /** @var PropertyInterface */ protected $nestedProperty; + private $recursiveArrayCheck = 0; + /** * ArrayTypeHintDecorator constructor. * @@ -31,8 +33,23 @@ public function __construct(PropertyInterface $nestedProperty) */ public function decorate(string $input, bool $outputType = false): string { - return implode('|', array_map(function (string $typeHint): string { - return "{$typeHint}[]"; - }, explode('|', $this->nestedProperty->getTypeHint($outputType)))); + // TODO: provide better type hints. Currently provides e.g. "string|array[]" instead of "string|string[]" for a recursive string array + if (++$this->recursiveArrayCheck > 1) { + return $this->nestedProperty->getTypeHint($outputType, [self::class]); + } + + $result = implode( + '|', + array_map( + static function (string $typeHint): string { + return "{$typeHint}[]"; + }, + explode('|', $this->nestedProperty->getTypeHint($outputType)) + ) + ); + + $this->recursiveArrayCheck--; + + return $result; } } diff --git a/src/PropertyProcessor/Decorator/TypeHint/TypeHintTransferDecorator.php b/src/PropertyProcessor/Decorator/TypeHint/TypeHintTransferDecorator.php index 5485727..0c64c74 100644 --- a/src/PropertyProcessor/Decorator/TypeHint/TypeHintTransferDecorator.php +++ b/src/PropertyProcessor/Decorator/TypeHint/TypeHintTransferDecorator.php @@ -7,7 +7,7 @@ use PHPModelGenerator\Model\Property\PropertyInterface; /** - * Class ArrayTypeHintDecorator + * Class TypeHintTransferDecorator * * @package PHPModelGenerator\PropertyProcessor\Decorator\Property */ diff --git a/src/PropertyProcessor/Filter/FilterProcessor.php b/src/PropertyProcessor/Filter/FilterProcessor.php index a12d716..9562c62 100644 --- a/src/PropertyProcessor/Filter/FilterProcessor.php +++ b/src/PropertyProcessor/Filter/FilterProcessor.php @@ -168,7 +168,7 @@ private function addTransformedValuePassThrough( } if ($validator instanceof EnumValidator) { - $property->filterValidators(function (Validator $validator): bool { + $property->filterValidators(static function (Validator $validator): bool { return !is_a($validator->getValidator(), EnumValidator::class); }); @@ -206,7 +206,7 @@ private function extendTypeCheckValidatorToAllowTransformedValue( ): void { $typeCheckValidator = null; - $property->filterValidators(function (Validator $validator) use (&$typeCheckValidator): bool { + $property->filterValidators(static function (Validator $validator) use (&$typeCheckValidator): bool { if (is_a($validator->getValidator(), TypeCheckValidator::class)) { $typeCheckValidator = $validator->getValidator(); return false; diff --git a/src/PropertyProcessor/Property/AbstractPropertyProcessor.php b/src/PropertyProcessor/Property/AbstractPropertyProcessor.php index 6829df4..f665913 100644 --- a/src/PropertyProcessor/Property/AbstractPropertyProcessor.php +++ b/src/PropertyProcessor/Property/AbstractPropertyProcessor.php @@ -105,7 +105,7 @@ protected function addEnumValidator(PropertyInterface $property, array $allowedV // no type information provided - inherit the types from the enum values if (!$property->getType()) { $typesOfEnum = array_unique(array_map( - function ($value): string { + static function ($value): string { return TypeConverter::gettypeToInternal(gettype($value)); }, $allowedValues @@ -137,7 +137,7 @@ protected function addDependencyValidator(PropertyInterface $property, array $de array_walk( $dependencies, - function ($dependency, $index) use (&$propertyDependency): void { + static function ($dependency, $index) use (&$propertyDependency): void { $propertyDependency = $propertyDependency && is_int($index) && is_string($dependency); } ); @@ -179,7 +179,7 @@ private function transferDependentPropertiesToBaseSchema(Schema $dependencySchem (clone $property) ->setRequired(false) ->setType(null) - ->filterValidators(function (): bool { + ->filterValidators(static function (): bool { return false; }) ); diff --git a/src/PropertyProcessor/Property/BaseProcessor.php b/src/PropertyProcessor/Property/BaseProcessor.php index 65eda43..8bf2ec9 100644 --- a/src/PropertyProcessor/Property/BaseProcessor.php +++ b/src/PropertyProcessor/Property/BaseProcessor.php @@ -316,23 +316,29 @@ protected function transferComposedPropertiesToSchema(PropertyInterface $propert } foreach ($validator->getComposedProperties() as $composedProperty) { - if (!$composedProperty->getNestedSchema()) { - throw new SchemaException( - sprintf( - "No nested schema for composed property %s in file %s found", - $property->getName(), - $property->getJsonSchema()->getFile() - ) + $composedProperty->onResolve(function () use ($composedProperty, $property, $validator): void { + if (!$composedProperty->getNestedSchema()) { + throw new SchemaException( + sprintf( + "No nested schema for composed property %s in file %s found", + $property->getName(), + $property->getJsonSchema()->getFile() + ) + ); + } + + $composedProperty->getNestedSchema()->onAllPropertiesResolved( + function () use ($composedProperty, $validator): void { + foreach ($composedProperty->getNestedSchema()->getProperties() as $property) { + $this->schema->addProperty( + $this->cloneTransferredProperty($property, $validator->getCompositionProcessor()) + ); + + $composedProperty->appendAffectedObjectProperty($property); + } + } ); - } - - foreach ($composedProperty->getNestedSchema()->getProperties() as $property) { - $this->schema->addProperty( - $this->cloneTransferredProperty($property, $validator->getCompositionProcessor()) - ); - - $composedProperty->appendAffectedObjectProperty($property); - } + }); } } } @@ -351,7 +357,7 @@ private function cloneTransferredProperty( string $compositionProcessor ): PropertyInterface { $transferredProperty = (clone $property) - ->filterValidators(function (Validator $validator): bool { + ->filterValidators(static function (Validator $validator): bool { return is_a($validator->getValidator(), PropertyTemplateValidator::class); }); diff --git a/src/PropertyProcessor/Property/MultiTypeProcessor.php b/src/PropertyProcessor/Property/MultiTypeProcessor.php index 386e872..d4f9c70 100644 --- a/src/PropertyProcessor/Property/MultiTypeProcessor.php +++ b/src/PropertyProcessor/Property/MultiTypeProcessor.php @@ -77,35 +77,46 @@ public function process(string $propertyName, JsonSchema $propertySchema): Prope { $property = parent::process($propertyName, $propertySchema); - foreach ($property->getValidators() as $validator) { - $this->checks[] = $validator->getValidator()->getCheck(); - } - - $subProperties = $this->processSubProperties($propertyName, $propertySchema, $property); + $property->onResolve(function () use ($property, $propertyName, $propertySchema): void { + foreach ($property->getValidators() as $validator) { + $this->checks[] = $validator->getValidator()->getCheck(); + } - if (empty($this->allowedPropertyTypes)) { - return $property; - } + $subProperties = $this->processSubProperties($propertyName, $propertySchema, $property); + + $processedSubProperties = 0; + foreach ($subProperties as $subProperty) { + $subProperty->onResolve(function () use ($property, $subProperties, &$processedSubProperties) { + if (++$processedSubProperties === count($subProperties)) { + if (empty($this->allowedPropertyTypes)) { + return; + } + + $property->addTypeHintDecorator( + new TypeHintDecorator( + array_map( + static function (PropertyInterface $subProperty): string { + return $subProperty->getTypeHint(); + }, + $subProperties + ) + ) + ); + + $property->addValidator( + new MultiTypeCheckValidator( + array_unique($this->allowedPropertyTypes), + $property, + $this->isImplicitNullAllowed($property) + ), + 2 + ); + } + }); + } + }); - $property->addTypeHintDecorator( - new TypeHintDecorator( - array_map( - function (PropertyInterface $subProperty): string { - return $subProperty->getTypeHint(); - }, - $subProperties - ) - ) - ); - - return $property->addValidator( - new MultiTypeCheckValidator( - array_unique($this->allowedPropertyTypes), - $property, - $this->isImplicitNullAllowed($property) - ), - 2 - ); + return $property; } /** @@ -165,11 +176,14 @@ protected function processSubProperties( $json['type'] = $type; $subProperty = $propertyProcessor->process($propertyName, $propertySchema->withJson($json)); - $this->transferValidators($subProperty, $property); - if ($subProperty->getDecorators()) { - $property->addDecorator(new PropertyTransferDecorator($subProperty)); - } + $subProperty->onResolve(function () use ($property, $subProperty): void { + $this->transferValidators($subProperty, $property); + + if ($subProperty->getDecorators()) { + $property->addDecorator(new PropertyTransferDecorator($subProperty)); + } + }); if ($defaultValue !== null && $propertyProcessor instanceof AbstractTypedValueProcessor) { try { diff --git a/src/SchemaProcessor/Hook/SchemaHookResolver.php b/src/SchemaProcessor/Hook/SchemaHookResolver.php index a1e997c..e2ad9e6 100644 --- a/src/SchemaProcessor/Hook/SchemaHookResolver.php +++ b/src/SchemaProcessor/Hook/SchemaHookResolver.php @@ -51,7 +51,7 @@ private function getHooks(string $filterHook): array { return array_filter( $this->schema->getSchemaHooks(), - function (SchemaHookInterface $hook) use ($filterHook): bool { + static function (SchemaHookInterface $hook) use ($filterHook): bool { return is_a($hook, $filterHook); } ); @@ -61,7 +61,7 @@ private function resolveHook(string $filterHook, ...$parameters): string { return join( "\n\n", - array_map(function ($hook) use ($parameters): string { + array_map(static function ($hook) use ($parameters): string { return $hook->getCode(...$parameters); }, $this->getHooks($filterHook)) ); diff --git a/src/SchemaProcessor/PostProcessor/AdditionalPropertiesAccessorPostProcessor.php b/src/SchemaProcessor/PostProcessor/AdditionalPropertiesAccessorPostProcessor.php index 05aecb9..4bb5394 100644 --- a/src/SchemaProcessor/PostProcessor/AdditionalPropertiesAccessorPostProcessor.php +++ b/src/SchemaProcessor/PostProcessor/AdditionalPropertiesAccessorPostProcessor.php @@ -104,12 +104,12 @@ private function addSetAdditionalPropertyMethod( ): void { $objectProperties = RenderHelper::varExportArray( array_map( - function (PropertyInterface $property): string { + static function (PropertyInterface $property): string { return $property->getName(); }, array_filter( $schema->getProperties(), - function (PropertyInterface $property): bool { + static function (PropertyInterface $property): bool { return !$property->isInternal(); } ) diff --git a/src/SchemaProcessor/PostProcessor/Internal/CompositionValidationPostProcessor.php b/src/SchemaProcessor/PostProcessor/Internal/CompositionValidationPostProcessor.php index bdc19e9..326b722 100644 --- a/src/SchemaProcessor/PostProcessor/Internal/CompositionValidationPostProcessor.php +++ b/src/SchemaProcessor/PostProcessor/Internal/CompositionValidationPostProcessor.php @@ -152,7 +152,7 @@ public function getCode(PropertyInterface $property, bool $batchUpdate = false): return join( "\n", array_map( - function ($validatorIndex) { + static function (int $validatorIndex): string { return sprintf('$this->validateComposition_%s($modelData);', $validatorIndex); }, array_unique($this->validatorPropertyMap[$property->getName()] ?? []) diff --git a/src/SchemaProcessor/PostProcessor/Internal/ExtendObjectPropertiesMatchingPatternPropertiesPostProcessor.php b/src/SchemaProcessor/PostProcessor/Internal/ExtendObjectPropertiesMatchingPatternPropertiesPostProcessor.php index 2ddc964..1c63d53 100644 --- a/src/SchemaProcessor/PostProcessor/Internal/ExtendObjectPropertiesMatchingPatternPropertiesPostProcessor.php +++ b/src/SchemaProcessor/PostProcessor/Internal/ExtendObjectPropertiesMatchingPatternPropertiesPostProcessor.php @@ -86,7 +86,7 @@ protected function transferPatternPropertiesFilterToProperty( ): void { $patternPropertiesValidators = array_filter( $schema->getBaseValidators(), - function (PropertyValidatorInterface $validator): bool { + static function (PropertyValidatorInterface $validator): bool { return $validator instanceof PatternPropertiesValidator; }); @@ -98,7 +98,7 @@ function (PropertyValidatorInterface $validator): bool { $propertyHasTransformingFilter = !empty( array_filter( $property->getValidators(), - function (Validator $validator): bool { + static function (Validator $validator): bool { return $validator->getValidator() instanceof FilterValidator && $validator->getValidator()->getFilter() instanceof TransformingFilterInterface; } diff --git a/src/SchemaProcessor/PostProcessor/Internal/PatternPropertiesPostProcessor.php b/src/SchemaProcessor/PostProcessor/Internal/PatternPropertiesPostProcessor.php index a2f631d..cd41fd3 100644 --- a/src/SchemaProcessor/PostProcessor/Internal/PatternPropertiesPostProcessor.php +++ b/src/SchemaProcessor/PostProcessor/Internal/PatternPropertiesPostProcessor.php @@ -51,7 +51,10 @@ public function process(Schema $schema, GeneratorConfiguration $generatorConfigu $patternHashes[$validator->getKey()] = array_reduce( $schema->getProperties(), - function (array $carry, PropertyInterface $property) use ($schemaProperties, $validator): array { + static function ( + array $carry, + PropertyInterface $property + ) use ($schemaProperties, $validator): array { if (in_array($property->getName(), $schemaProperties) && preg_match('/' . addcslashes($validator->getPattern(), '/') . '/', $property->getName()) ) { diff --git a/src/SchemaProcessor/PostProcessor/PatternPropertiesAccessorPostProcessor.php b/src/SchemaProcessor/PostProcessor/PatternPropertiesAccessorPostProcessor.php index 22e4fca..942331f 100644 --- a/src/SchemaProcessor/PostProcessor/PatternPropertiesAccessorPostProcessor.php +++ b/src/SchemaProcessor/PostProcessor/PatternPropertiesAccessorPostProcessor.php @@ -79,16 +79,18 @@ private function addGetPatternPropertiesMethod( */ private function getReturnTypeAnnotationForGetPatternProperties(array $patternTypes): string { - $baseTypes = array_unique(array_map( - function (PropertyType $type): string { + $baseTypes = array_unique( + array_map( + static function (PropertyType $type): string { return $type->getName(); }, - $patternTypes) + $patternTypes + ) ); $nullable = array_reduce( $patternTypes, - function (bool $carry, PropertyType $type): bool { + static function (bool $carry, PropertyType $type): bool { return $carry || $type->isNullable(); }, false diff --git a/src/SchemaProcessor/SchemaProcessor.php b/src/SchemaProcessor/SchemaProcessor.php index daa186c..1b08f40 100644 --- a/src/SchemaProcessor/SchemaProcessor.php +++ b/src/SchemaProcessor/SchemaProcessor.php @@ -234,7 +234,7 @@ protected function setCurrentClassPath(string $jsonSchemaFile): void { $path = str_replace($this->baseSource, '', dirname($jsonSchemaFile)); $pieces = array_map( - function ($directory) { + static function (string $directory): string { return ucfirst($directory); }, explode(DIRECTORY_SEPARATOR, $path) diff --git a/src/Templates/Model.phptpl b/src/Templates/Model.phptpl index ffb4d7b..8b77391 100644 --- a/src/Templates/Model.phptpl +++ b/src/Templates/Model.phptpl @@ -192,10 +192,7 @@ class {{ class }} {% if schema.getInterfaces() %}implements {{ viewHelper.joinCl protected function validate{{ viewHelper.ucfirst(property.getAttribute()) }}($value, array $modelData) { {% foreach property.getOrderedValidators() as validator %} - {{ validator.getValidatorSetUp() }} - if ({{ validator.getCheck() }}) { - {{ viewHelper.validationError(validator) }} - } + {{ viewHelper.renderValidator(validator, schema) }} {% endforeach %} return $value; @@ -203,9 +200,7 @@ class {{ class }} {% if schema.getInterfaces() %}implements {{ viewHelper.joinCl {% endif %} {% endforeach %} - {% foreach schema.getMethods() as method %} - {{ method.getCode(generatorConfiguration) }} - {% endforeach %} + {{ viewHelper.renderMethods(schema) }} } // @codeCoverageIgnoreEnd diff --git a/src/Templates/Validator/ArrayItem.phptpl b/src/Templates/Validator/ArrayItem.phptpl index 57d527d..0c14f33 100644 --- a/src/Templates/Validator/ArrayItem.phptpl +++ b/src/Templates/Validator/ArrayItem.phptpl @@ -12,10 +12,7 @@ is_array($value) && (function (&$items) use (&$invalidItems{{ suffix }}) { {{ viewHelper.resolvePropertyDecorator(nestedProperty) }} {% foreach nestedProperty.getOrderedValidators() as validator %} - {{ validator.getValidatorSetUp() }} - if ({{ validator.getCheck() }}) { - {{ viewHelper.validationError(validator) }} - } + {{ viewHelper.renderValidator(validator, schema) }} {% endforeach %} {% if generatorConfiguration.collectErrors() %} diff --git a/src/Templates/Validator/ArrayTuple.phptpl b/src/Templates/Validator/ArrayTuple.phptpl index b4bd5ed..425e9d1 100644 --- a/src/Templates/Validator/ArrayTuple.phptpl +++ b/src/Templates/Validator/ArrayTuple.phptpl @@ -19,10 +19,7 @@ is_array($value) && (function (&$items) use (&$invalidTuples) { {{ viewHelper.resolvePropertyDecorator(tuple) }} {% foreach tuple.getOrderedValidators() as validator %} - {{ validator.getValidatorSetUp() }} - if ({{ validator.getCheck() }}) { - {{ viewHelper.validationError(validator) }} - } + {{ viewHelper.renderValidator(validator, schema) }} {% endforeach %} {% if generatorConfiguration.collectErrors() %} diff --git a/src/Templates/Validator/ArrayUnique.phptpl b/src/Templates/Validator/ArrayUnique.phptpl index d010620..23189f9 100644 --- a/src/Templates/Validator/ArrayUnique.phptpl +++ b/src/Templates/Validator/ArrayUnique.phptpl @@ -1,4 +1,4 @@ -is_array($value) && (function (array $items): bool { +is_array($value) && (static function (array $items): bool { if (empty($items)) { return false; } diff --git a/src/Templates/Validator/NoAdditionalProperties.phptpl b/src/Templates/Validator/NoAdditionalProperties.phptpl index 445d8aa..5c8138c 100644 --- a/src/Templates/Validator/NoAdditionalProperties.phptpl +++ b/src/Templates/Validator/NoAdditionalProperties.phptpl @@ -1,11 +1,11 @@ -$additionalProperties = (function () use ($modelData): array { +$additionalProperties = (static function () use ($modelData): array { $additionalProperties = array_diff(array_keys($modelData), {{ properties }}); {% if pattern %} // filter out all pattern properties $additionalProperties = array_filter( $additionalProperties, - function (string $property): bool { + static function (string $property): bool { return preg_match('/{{ pattern }}/', $property) !== 1; } ); diff --git a/src/Templates/Validator/PropertyDependency.phptpl b/src/Templates/Validator/PropertyDependency.phptpl index 03809fa..d147dff 100644 --- a/src/Templates/Validator/PropertyDependency.phptpl +++ b/src/Templates/Validator/PropertyDependency.phptpl @@ -1,4 +1,4 @@ -array_key_exists('{{ property.getName() }}', $modelData) && (function () use ($modelData, &$missingAttributes) { +array_key_exists('{{ property.getName() }}', $modelData) && (static function () use ($modelData, &$missingAttributes) { foreach ({{ dependencies }} as $dependency) { if (!array_key_exists($dependency, $modelData)) { $missingAttributes[] = $dependency; diff --git a/src/Utils/RenderHelper.php b/src/Utils/RenderHelper.php index fd32c94..e8ff411 100644 --- a/src/Utils/RenderHelper.php +++ b/src/Utils/RenderHelper.php @@ -6,6 +6,8 @@ use PHPModelGenerator\Model\GeneratorConfiguration; use PHPModelGenerator\Model\Property\PropertyInterface; +use PHPModelGenerator\Model\Schema; +use PHPModelGenerator\Model\Validator\ExtractedMethodValidator; use PHPModelGenerator\Model\Validator\PropertyValidatorInterface; /** @@ -158,6 +160,36 @@ public function getTypeHintAnnotation(PropertyInterface $property, bool $outputT return implode('|', array_unique(explode('|', $typeHint))); } + public function renderValidator(PropertyValidatorInterface $validator, Schema $schema): string + { + if (!$validator instanceof ExtractedMethodValidator) { + return " +{$validator->getValidatorSetUp()} +if ({$validator->getCheck()}) { + {$this->validationError($validator)} +} +"; + } + + if (!$schema->hasMethod($validator->getExtractedMethodName())) { + $schema->addMethod($validator->getExtractedMethodName(), $validator->getMethod()); + } + + return "\$this->{$validator->getExtractedMethodName()}(\$value);"; + } + + public function renderMethods(Schema $schema): string + { + $renderedMethods = ''; + + // don't change to a foreach loop as the render process of a method might add additional methods + for ($i = 0; $i < count($schema->getMethods()); $i++) { + $renderedMethods .= $schema->getMethods()[array_keys($schema->getMethods())[$i]]->getCode(); + } + + return $renderedMethods; + } + public static function varExportArray(array $values): string { return preg_replace('(\d+\s=>)', '', var_export($values, true)); diff --git a/src/Utils/ResolvableInterface.php b/src/Utils/ResolvableInterface.php new file mode 100644 index 0000000..6614b79 --- /dev/null +++ b/src/Utils/ResolvableInterface.php @@ -0,0 +1,18 @@ +isResolved + ? $callback() + : $this->onResolveCallbacks[] = $callback; + } + + public function isResolved(): bool + { + return $this->isResolved; + } + + public function resolve(): void + { + $this->isResolved = true; + + foreach ($this->onResolveCallbacks as $callback) { + $callback(); + } + + $this->onResolveCallbacks = []; + } +} diff --git a/tests/AbstractPHPModelGeneratorTest.php b/tests/AbstractPHPModelGeneratorTest.php index bbf2824..be0e670 100644 --- a/tests/AbstractPHPModelGeneratorTest.php +++ b/tests/AbstractPHPModelGeneratorTest.php @@ -150,7 +150,7 @@ protected function generateClassFromFile( * Generate a class from a file template and apply all $values via sprintf to the template * * @param string $file - * @param array $values + * @param string[] $values * @param GeneratorConfiguration|null $generatorConfiguration * @param bool $escape * @param bool $implicitNull @@ -176,7 +176,7 @@ protected function generateClassFromFileTemplate( array_merge( [file_get_contents(__DIR__ . '/Schema/' . $this->getStaticClassName() . '/' . $file)], array_map( - function ($item) use ($escape) { + static function (string $item) use ($escape): string { return $escape ? str_replace("'", '"', addcslashes($item, '"\\')) : $item; }, $values @@ -417,7 +417,8 @@ protected function assertErrorRegistryContainsException( $this->fail("Error exception $expectedException not found in error registry exception"); } - public function validationMethodDataProvider(): array { + public function validationMethodDataProvider(): array + { return [ 'Error Collection' => [new GeneratorConfiguration()], 'Direct Exception' => [(new GeneratorConfiguration())->setCollectErrors(false)], diff --git a/tests/Basic/BasicSchemaGenerationTest.php b/tests/Basic/BasicSchemaGenerationTest.php index 2df67b7..91aab96 100644 --- a/tests/Basic/BasicSchemaGenerationTest.php +++ b/tests/Basic/BasicSchemaGenerationTest.php @@ -65,7 +65,7 @@ public function testGetterAndSetterAreGeneratedForMutableObjects(bool $implicitN public function testSetterLogicIsNotExecutedWhenValueIsIdentical(): void { - $this->modifyModelGenerator = function (ModelGenerator $modelGenerator): void { + $this->modifyModelGenerator = static function (ModelGenerator $modelGenerator): void { $modelGenerator->addPostProcessor(new class () extends PostProcessor { public function process(Schema $schema, GeneratorConfiguration $generatorConfiguration): void { diff --git a/tests/Basic/IdenticalNestedSchemaTest.php b/tests/Basic/IdenticalNestedSchemaTest.php index b72b2a6..3898b17 100644 --- a/tests/Basic/IdenticalNestedSchemaTest.php +++ b/tests/Basic/IdenticalNestedSchemaTest.php @@ -97,7 +97,7 @@ public function testIdenticalReferencedSchemaInMultipleFilesAreMappedToOneClass( $this->assertSame(get_class($object1->getMember()), get_class($object2->getMember())); } - public function identicalReferencedSchemaDataProvider() + public function identicalReferencedSchemaDataProvider(): array { return [ 'In same namespace' => [ diff --git a/tests/Basic/SchemaHookTest.php b/tests/Basic/SchemaHookTest.php index beb3014..00cf2c1 100644 --- a/tests/Basic/SchemaHookTest.php +++ b/tests/Basic/SchemaHookTest.php @@ -206,7 +206,7 @@ public function getCode(): string protected function addSchemaHook(SchemaHookInterface $schemaHook): void { - $this->modifyModelGenerator = function (ModelGenerator $modelGenerator) use ($schemaHook): void { + $this->modifyModelGenerator = static function (ModelGenerator $modelGenerator) use ($schemaHook): void { $modelGenerator->addPostProcessor(new class ($schemaHook) extends PostProcessor { private $schemaHook; diff --git a/tests/Objects/ArrayPropertyTest.php b/tests/Objects/ArrayPropertyTest.php index 79e1f1e..6e1590e 100644 --- a/tests/Objects/ArrayPropertyTest.php +++ b/tests/Objects/ArrayPropertyTest.php @@ -2,6 +2,8 @@ namespace PHPModelGenerator\Tests\Objects; +use PHPModelGenerator\Exception\Arrays\InvalidItemException; +use PHPModelGenerator\Exception\Arrays\MinItemsException; use PHPModelGenerator\Exception\FileSystemException; use PHPModelGenerator\Exception\RenderException; use PHPModelGenerator\Exception\SchemaException; @@ -867,6 +869,7 @@ public function invalidObjectArrayDataProvider(): array ) ); } + public function invalidCombinedObjectArrayDataProvider(): array { return $this->combineDataProvider( @@ -951,4 +954,49 @@ public function invalidCombinedObjectArrayDataProvider(): array ) ); } + + /** + * @dataProvider validRecursiveArrayDataProvider + */ + public function testValidRecursiveArray(array $input): void + { + $className = $this->generateClassFromFile('RecursiveArray.json'); + + $object = new $className(['property' => $input]); + + $this->assertSame($input, $object->getProperty()); + } + + public function validRecursiveArrayDataProvider(): array + { + return [ + 'only string' => [['Hello']], + 'only nested array' => [[['Hello']]], + 'string and nested array' => [[['Hello'], 'World']], + 'two level nested array' => [[[['Hello'], 'World'], '!']], + ]; + } + + /** + * @dataProvider invalidRecursiveArrayDataProvider + */ + public function testInvalidRecursiveArrayThrowsAnException(string $expectedException, array $input): void + { + $this->expectException($expectedException); + + $className = $this->generateClassFromFile('RecursiveArray.json'); + + new $className(['property' => $input]); + } + + public function invalidRecursiveArrayDataProvider(): array + { + return [ + 'empty array' => [MinItemsException::class, []], + 'empty nested array' => [InvalidItemException::class, [[]]], + 'string with empty nested array' => [InvalidItemException::class, ['Hello', []]], + 'invalid type' => [InvalidItemException::class, [2]], + 'invalid nested type' => [InvalidItemException::class, ['Hello', [2]]], + ]; + } } diff --git a/tests/Objects/EnumPropertyTest.php b/tests/Objects/EnumPropertyTest.php index 7be832e..acc0173 100644 --- a/tests/Objects/EnumPropertyTest.php +++ b/tests/Objects/EnumPropertyTest.php @@ -384,7 +384,7 @@ public function testEmptyEnumThrowsSchemaException(): void protected function generateEnumClass(string $type, array $enumValues, $required = false): string { $enumValues = array_map( - function ($item) { + static function ($item): string { return var_export($item, true); }, $enumValues diff --git a/tests/Objects/MultiTypePropertyTest.php b/tests/Objects/MultiTypePropertyTest.php index 93dca82..73af16d 100644 --- a/tests/Objects/MultiTypePropertyTest.php +++ b/tests/Objects/MultiTypePropertyTest.php @@ -2,7 +2,10 @@ namespace PHPModelGenerator\Tests\Objects; +use PHPModelGenerator\Exception\Arrays\InvalidItemException; +use PHPModelGenerator\Exception\Arrays\MinItemsException; use PHPModelGenerator\Exception\FileSystemException; +use PHPModelGenerator\Exception\Generic\InvalidTypeException; use PHPModelGenerator\Exception\RenderException; use PHPModelGenerator\Exception\SchemaException; use PHPModelGenerator\Model\GeneratorConfiguration; @@ -120,7 +123,7 @@ public function testInvalidProvidedValueThrowsAnException($propertyValue, string new $className(['property' => $propertyValue]); } - public function invalidValueDataProvider() + public function invalidValueDataProvider(): array { return [ 'Bool' => [true, 'Invalid type for property. Requires [float, string, array], got boolean'], @@ -208,6 +211,85 @@ public function invalidNestedObjectDataProvider(): array <<generateClassFromFile('RecursiveMultiTypeProperty.json'); + + $object = new $className(['property' => $input]); + $this->assertSame($input, $object->getProperty()); + } + + public function validRecursiveMultiTypeDataProvider(): array + { + return [ + 'string' => ['Test'], + 'array' => [['Test1', 'Test2']], + 'nested array' => [[['Test1', 'Test2'], 'Test3']], + ]; + } + + /** + * @dataProvider invalidRecursiveMultiTypeDataProvider + */ + public function testInvalidRecursiveMultiType($input, string $expectedException, string $exceptionMessage): void + { + $this->expectException($expectedException); + $this->expectExceptionMessage($exceptionMessage); + + $className = $this->generateClassFromFile('RecursiveMultiTypeProperty.json'); + + new $className(['property' => $input]); + } + + public function invalidRecursiveMultiTypeDataProvider(): array + { + return [ + 'int' => [ + 1, + InvalidTypeException::class, + 'Invalid type for property. Requires [string, array], got integer', + ], + 'invalid item in array' => [ + ['Test1', 1], + InvalidItemException::class, + << [ + [], + MinItemsException::class, + 'Array property must not contain less than 2 items', + ], + 'invalid item in nested array' => [ + ['Test1', [3, 'Test3']], + InvalidItemException::class, + << [ + ['Test1', []], + InvalidItemException::class, + <<generateClassFromFile('RecursiveTupleArray.json'); + + $object = new $className(['property' => $input]); + + $this->assertSame($input, $object->getProperty()); + } + + public function validRecursiveTupleDataProvider(): array + { + return [ + 'string' => [['abc', 'def']], + 'one level nested' => [['abc', ['abc', 'def']]], + 'two level nested' => [['abc', ['abc', ['abc', 'def']]]], + ]; + } + + /** + * @dataProvider invalidRecursiveTupleDataProvider + */ + public function testInvalidRecursiveTuple(array $input): void + { + $this->expectException(InvalidTupleException::class); + + $className = $this->generateClassFromFile('RecursiveTupleArray.json'); + + new $className(['property' => $input]); + } + + public function invalidRecursiveTupleDataProvider(): array + { + return [ + 'invalid first tuple' => [[1, 'def']], + 'invalid second tuple' => [['abc', 1]], + 'one level nested - invalid first tuple' => [[1, ['abc', 'def']]], + 'one level nested - invalid nested first tuple' => [['abc', [1, 'def']]], + 'one level nested - invalid nested second tuple' => [['abc', ['abc', 1]]], + ]; + } } diff --git a/tests/PostProcessor/AdditionalPropertiesAccessorPostProcessorTest.php b/tests/PostProcessor/AdditionalPropertiesAccessorPostProcessorTest.php index 76a942f..9351a82 100644 --- a/tests/PostProcessor/AdditionalPropertiesAccessorPostProcessorTest.php +++ b/tests/PostProcessor/AdditionalPropertiesAccessorPostProcessorTest.php @@ -29,9 +29,9 @@ class AdditionalPropertiesAccessorPostProcessorTest extends AbstractPHPModelGene { protected function addPostProcessor(bool $addForModelsWithoutAdditionalPropertiesDefinition) { - $this->modifyModelGenerator = function (ModelGenerator $generator) use ( + $this->modifyModelGenerator = static function (ModelGenerator $generator) use ( $addForModelsWithoutAdditionalPropertiesDefinition - ) { + ): void { $generator->addPostProcessor( new AdditionalPropertiesAccessorPostProcessor($addForModelsWithoutAdditionalPropertiesDefinition) ); @@ -324,7 +324,7 @@ public function invalidAdditionalPropertyDataProvider(): array public function testSetterSchemaHooksAreResolvedInSetAdditionalProperties(): void { - $this->modifyModelGenerator = function (ModelGenerator $modelGenerator): void { + $this->modifyModelGenerator = static function (ModelGenerator $modelGenerator): void { $modelGenerator ->addPostProcessor(new AdditionalPropertiesAccessorPostProcessor()) ->addPostProcessor(new class () extends PostProcessor { @@ -409,7 +409,7 @@ public function testAdditionalPropertiesAreSerialized(bool $implicitNull): void public function testAdditionalPropertiesAreSerializedWithoutAdditionalPropertiesAccessorPostProcessor(): void { - $this->modifyModelGenerator = function (ModelGenerator $generator): void { + $this->modifyModelGenerator = static function (ModelGenerator $generator): void { $generator->addPostProcessor(new PopulatePostProcessor()); }; @@ -430,7 +430,7 @@ public function testAdditionalPropertiesAreSerializedWithoutAdditionalProperties public function testAdditionalPropertiesAreNotSerializedWhenNotDefinedWithoutExplicitAccessorMethods(): void { - $this->modifyModelGenerator = function (ModelGenerator $generator): void { + $this->modifyModelGenerator = static function (ModelGenerator $generator): void { $generator->addPostProcessor(new PopulatePostProcessor()); }; @@ -448,7 +448,7 @@ public function testAdditionalPropertiesAreNotSerializedWhenNotDefinedWithoutExp public function testAdditionalPropertiesAreSerializedWhenNotDefinedWithExplicitAccessorMethods(): void { - $this->modifyModelGenerator = function (ModelGenerator $generator): void { + $this->modifyModelGenerator = static function (ModelGenerator $generator): void { $generator ->addPostProcessor(new PopulatePostProcessor()) ->addPostProcessor(new AdditionalPropertiesAccessorPostProcessor(true)); diff --git a/tests/PostProcessor/PatternPropertiesAccessorPostProcessorTest.php b/tests/PostProcessor/PatternPropertiesAccessorPostProcessorTest.php index a4f12dd..f0f148a 100644 --- a/tests/PostProcessor/PatternPropertiesAccessorPostProcessorTest.php +++ b/tests/PostProcessor/PatternPropertiesAccessorPostProcessorTest.php @@ -28,7 +28,7 @@ class PatternPropertiesAccessorPostProcessorTest extends AbstractPHPModelGenerat { protected function addPostProcessors(PostProcessor ...$postProcessors): void { - $this->modifyModelGenerator = function (ModelGenerator $generator) use ($postProcessors): void { + $this->modifyModelGenerator = static function (ModelGenerator $generator) use ($postProcessors): void { foreach ($postProcessors as $postProcessor) { $generator->addPostProcessor($postProcessor); } diff --git a/tests/PostProcessor/PopulatePostProcessorTest.php b/tests/PostProcessor/PopulatePostProcessorTest.php index caff403..2379d25 100644 --- a/tests/PostProcessor/PopulatePostProcessorTest.php +++ b/tests/PostProcessor/PopulatePostProcessorTest.php @@ -26,7 +26,7 @@ public function setUp(): void { parent::setUp(); - $this->modifyModelGenerator = function (ModelGenerator $generator): void { + $this->modifyModelGenerator = static function (ModelGenerator $generator): void { $generator->addPostProcessor(new PopulatePostProcessor()); }; } @@ -215,7 +215,7 @@ public function invalidPopulateDataProvider(): array public function testSetterBeforeValidationHookInsidePopulateIsResolved(): void { - $this->modifyModelGenerator = function (ModelGenerator $modelGenerator): void { + $this->modifyModelGenerator = static function (ModelGenerator $modelGenerator): void { $modelGenerator->addPostProcessor(new PopulatePostProcessor()); $modelGenerator->addPostProcessor(new class () extends PostProcessor { public function process(Schema $schema, GeneratorConfiguration $generatorConfiguration): void @@ -259,7 +259,7 @@ public function testSetterAfterValidationHookInsidePopulateIsResolved( array $populateValues ): void { - $this->modifyModelGenerator = function (ModelGenerator $modelGenerator): void { + $this->modifyModelGenerator = static function (ModelGenerator $modelGenerator): void { $modelGenerator->addPostProcessor(new PopulatePostProcessor()); $modelGenerator->addPostProcessor(new class () extends PostProcessor { public function process(Schema $schema, GeneratorConfiguration $generatorConfiguration): void diff --git a/tests/Schema/ArrayPropertyTest/RecursiveArray.json b/tests/Schema/ArrayPropertyTest/RecursiveArray.json new file mode 100644 index 0000000..766040f --- /dev/null +++ b/tests/Schema/ArrayPropertyTest/RecursiveArray.json @@ -0,0 +1,24 @@ +{ + "definitions": { + "list": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/list" + }, + { + "type": "string" + } + ] + }, + "minItems": 1 + } + }, + "type": "object", + "properties": { + "property": { + "$ref": "#/definitions/list" + } + } +} \ No newline at end of file diff --git a/tests/Schema/MultiTypePropertyTest/RecursiveMultiTypeProperty.json b/tests/Schema/MultiTypePropertyTest/RecursiveMultiTypeProperty.json new file mode 100644 index 0000000..1f2d952 --- /dev/null +++ b/tests/Schema/MultiTypePropertyTest/RecursiveMultiTypeProperty.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "definitions": { + "property": { + "type": ["string", "array"], + "minLength": 4, + "items": { + "$ref": "#/definitions/property" + }, + "minItems": 2 + } + }, + "properties": { + "property": { + "$ref": "#/definitions/property" + } + } +} \ No newline at end of file diff --git a/tests/Schema/TupleArrayPropertyTest/RecursiveTupleArray.json b/tests/Schema/TupleArrayPropertyTest/RecursiveTupleArray.json new file mode 100644 index 0000000..f59aef6 --- /dev/null +++ b/tests/Schema/TupleArrayPropertyTest/RecursiveTupleArray.json @@ -0,0 +1,29 @@ +{ + "definitions": { + "list": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "oneOf": [ + { + "$ref": "#/definitions/list" + }, + { + "type": "string" + } + ] + } + ], + "minItems": 1 + } + }, + "type": "object", + "properties": { + "property": { + "$ref": "#/definitions/list" + } + } +} \ No newline at end of file diff --git a/tests/SchemaProvider/OpenAPIv3ProviderTest.php b/tests/SchemaProvider/OpenAPIv3ProviderTest.php index b2a3cd2..323c9e8 100644 --- a/tests/SchemaProvider/OpenAPIv3ProviderTest.php +++ b/tests/SchemaProvider/OpenAPIv3ProviderTest.php @@ -88,13 +88,13 @@ public function referencedSchemaDataProvider(): array 'Empty data path reference' => [ '#/components/modules/person', [], - function ($person) { + function ($person): void { $this->assertNull($person->getName()); $this->assertIsArray($person->getChildren()); $this->assertEmpty($person->getChildren()); }, [], - function ($car) { + function ($car): void { $this->assertNull($car->getPs()); $this->assertNull($car->getOwner()); }, @@ -109,7 +109,7 @@ function ($car) { ], ], ], - function ($person) { + function ($person): void { $this->assertSame('Hannes', $person->getName()); $this->assertCount(1, $person->getChildren()); $this->assertSame('Erwin', $person->getChildren()[0]->getName()); @@ -121,7 +121,7 @@ function ($person) { 'name' => 'Susi', ], ], - function ($car) { + function ($car): void { $this->assertSame(150, $car->getPs()); $this->assertSame('Susi', $car->getOwner()->getName()); $this->assertEmpty($car->getOwner()->getChildren()); @@ -142,7 +142,7 @@ function ($car) { ], ], ], - function ($person) { + function ($person): void { $this->assertSame('Hannes', $person->getName()); $this->assertCount(1, $person->getChildren()); $this->assertSame('Erwin', $person->getChildren()[0]->getName()); @@ -161,7 +161,7 @@ function ($person) { ], ], ], - function ($car) { + function ($car): void { $this->assertSame(150, $car->getPs()); $this->assertSame('Susi', $car->getOwner()->getName()); $this->assertCount(1, $car->getOwner()->getChildren());