diff --git a/src/Model/Validator/PropertyTemplateValidator.php b/src/Model/Validator/PropertyTemplateValidator.php index 017d651..4fae4db 100644 --- a/src/Model/Validator/PropertyTemplateValidator.php +++ b/src/Model/Validator/PropertyTemplateValidator.php @@ -8,6 +8,7 @@ use PHPMicroTemplate\Render; use PHPModelGenerator\Exception\RenderException; use PHPModelGenerator\Model\Property\PropertyInterface; +use PHPModelGenerator\Model\Schema; /** * Class PropertyTemplateValidator @@ -45,6 +46,13 @@ public function __construct( parent::__construct($property, $exceptionClass, $exceptionParams); } + public function setScope(Schema $schema): void + { + if (isset($this->templateValues['schema'])) { + $this->templateValues['schema'] = $schema; + } + } + /** * Get the source code for the check to perform * diff --git a/src/PropertyProcessor/Property/BaseProcessor.php b/src/PropertyProcessor/Property/BaseProcessor.php index 8bf2ec9..5f85bf8 100644 --- a/src/PropertyProcessor/Property/BaseProcessor.php +++ b/src/PropertyProcessor/Property/BaseProcessor.php @@ -303,7 +303,7 @@ protected function transferComposedPropertiesToSchema(PropertyInterface $propert // If the transferred validator of the composed property is also a composed property strip the nested // composition validations from the added validator. The nested composition will be validated in the object - // generated for the nested composition which will be executed via an instanciation. Consequently the + // generated for the nested composition which will be executed via an instantiation. Consequently, the // validation must not be executed in the outer composition. $this->schema->addBaseValidator( ($validator instanceof ComposedPropertyValidator) diff --git a/src/SchemaProcessor/PostProcessor/Internal/AdditionalPropertiesPostProcessor.php b/src/SchemaProcessor/PostProcessor/Internal/AdditionalPropertiesPostProcessor.php index d1ee2df..ae2cc2e 100644 --- a/src/SchemaProcessor/PostProcessor/Internal/AdditionalPropertiesPostProcessor.php +++ b/src/SchemaProcessor/PostProcessor/Internal/AdditionalPropertiesPostProcessor.php @@ -4,7 +4,7 @@ namespace PHPModelGenerator\SchemaProcessor\PostProcessor\Internal; -use Exception; +use PHPModelGenerator\Exception\Object\InvalidAdditionalPropertiesException; use PHPModelGenerator\Exception\SchemaException; use PHPModelGenerator\Model\GeneratorConfiguration; use PHPModelGenerator\Model\Property\Property; @@ -114,7 +114,7 @@ public function __construct(Schema $schema) array_keys($schema->getJsonSchema()->getJson()['properties'] ?? []) ), ], - Exception::class + InvalidAdditionalPropertiesException::class ); } } diff --git a/src/Utils/RenderHelper.php b/src/Utils/RenderHelper.php index e8ff411..879bc25 100644 --- a/src/Utils/RenderHelper.php +++ b/src/Utils/RenderHelper.php @@ -8,6 +8,7 @@ use PHPModelGenerator\Model\Property\PropertyInterface; use PHPModelGenerator\Model\Schema; use PHPModelGenerator\Model\Validator\ExtractedMethodValidator; +use PHPModelGenerator\Model\Validator\PropertyTemplateValidator; use PHPModelGenerator\Model\Validator\PropertyValidatorInterface; /** @@ -162,6 +163,12 @@ public function getTypeHintAnnotation(PropertyInterface $property, bool $outputT public function renderValidator(PropertyValidatorInterface $validator, Schema $schema): string { + // scoping of the validator might be required as validators from a composition might be transferred to a + // different schema + if ($validator instanceof PropertyTemplateValidator) { + $validator->setScope($schema); + } + if (!$validator instanceof ExtractedMethodValidator) { return " {$validator->getValidatorSetUp()} @@ -184,7 +191,7 @@ public function renderMethods(Schema $schema): string // 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(); + $renderedMethods .= $schema->getMethods()[array_keys($schema->getMethods())[$i]]->getCode() . "\n\n"; } return $renderedMethods; diff --git a/tests/AbstractPHPModelGeneratorTest.php b/tests/AbstractPHPModelGeneratorTest.php index be0e670..4eaa2eb 100644 --- a/tests/AbstractPHPModelGeneratorTest.php +++ b/tests/AbstractPHPModelGeneratorTest.php @@ -138,7 +138,7 @@ protected function generateClassFromFile( string $schemaProviderClass = RecursiveDirectoryProvider::class ): string { return $this->generateClass( - file_get_contents(__DIR__ . '/Schema/' . $this->getStaticClassName() . '/' . $file), + file_get_contents($this->getSchemaFilePath($file)), $generatorConfiguration, $originalClassNames, $implicitNull, @@ -171,16 +171,13 @@ protected function generateClassFromFileTemplate( string $schemaProviderClass = RecursiveDirectoryProvider::class ): string { return $this->generateClass( - call_user_func_array( - 'sprintf', - array_merge( - [file_get_contents(__DIR__ . '/Schema/' . $this->getStaticClassName() . '/' . $file)], - array_map( - static function (string $item) use ($escape): string { - return $escape ? str_replace("'", '"', addcslashes($item, '"\\')) : $item; - }, - $values - ) + sprintf( + file_get_contents($this->getSchemaFilePath($file)), + ...array_map( + static function (string $item) use ($escape): string { + return $escape ? str_replace("'", '"', addcslashes($item, '"\\')) : $item; + }, + $values ) ), $generatorConfiguration, @@ -517,6 +514,11 @@ protected function getGeneratedFiles(): array return $this->generatedFiles; } + protected function getSchemaFilePath(string $file): string + { + return __DIR__ . '/Schema/' . $this->getStaticClassName() . '/' . $file; + } + /** * Generate a unique name for a class * @@ -536,7 +538,7 @@ private function getClassName(): string return $name; } - private function getStaticClassName(): string + protected function getStaticClassName(): string { $parts = explode('\\', static::class); diff --git a/tests/Issues/AbstractIssueTest.php b/tests/Issues/AbstractIssueTest.php new file mode 100644 index 0000000..ce89328 --- /dev/null +++ b/tests/Issues/AbstractIssueTest.php @@ -0,0 +1,17 @@ +\d+)/', $this->getStaticClassName(), $matches); + + return __DIR__ . '/../Schema/Issues/' . $matches['issue'] . '/' . $file; + } +} diff --git a/tests/Issues/Regression/RegressionIssue65Test.php b/tests/Issues/Regression/RegressionIssue65Test.php new file mode 100644 index 0000000..af0c8d4 --- /dev/null +++ b/tests/Issues/Regression/RegressionIssue65Test.php @@ -0,0 +1,45 @@ +generateClassFromFile('regression.json'); + + $object = new $className(['list' => [['id' => 10, 'name' => 'Hans']], 'label' => 'visitors']); + + $this->assertSame('visitors', $object->getLabel()); + $this->assertCount(1, $object->getList()); + $this->assertSame(10, $object->getList()[0]->getId()); + $this->assertSame('Hans', $object->getList()[0]->getName()); + } + + /** + * @dataProvider invalidInputDataProvider + */ + public function testInvalidInput(array $input): void + { + $this->expectException(AllOfException::class); + + $className = $this->generateClassFromFile('regression.json'); + + new $className($input); + } + + public function invalidInputDataProvider(): array + { + return [ + 'invalid label' => [['label' => 10]], + 'invalid list element' => [['list' => [['id' => 10, 'name' => 'Hans'], 10]]], + 'invalid id in list element' => [['list' => [['id' => '10', 'name' => 'Hans']]]], + 'invalid name in list element' => [['list' => [['id' => 10, 'name' => false]]]], + ]; + } +} diff --git a/tests/Schema/Issues/65/regression.json b/tests/Schema/Issues/65/regression.json new file mode 100644 index 0000000..d855893 --- /dev/null +++ b/tests/Schema/Issues/65/regression.json @@ -0,0 +1,40 @@ +{ + "allOf": [ + { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "properties": { + "id": { + "type": "integer" + } + } + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + ] + } + } + } + }, + { + "type": "object", + "properties": { + "label": { + "type": "string" + } + } + } + ] +} \ No newline at end of file