From 877b1be711ef439a1b99707a12e9022cb1d13247 Mon Sep 17 00:00:00 2001 From: Philipp Anton Hermes Date: Sun, 25 May 2025 10:20:52 +0200 Subject: [PATCH 01/16] add openAPI support --- composer.json | 7 +- src/Resources/schema/transfer.xsd | 1 + src/Service/Model/Generator/Generator.php | 28 ++++++- .../AdderPropertyGeneratorStep.php | 3 +- .../GetterPropertyGeneratorStep.php | 3 +- .../PropertyGeneratorStepInterface.php | 5 +- .../PropertyPropertyGeneratorStep.php | 76 ++++++++++++++++++- .../SetterPropertyGeneratorStep.php | 3 +- src/Service/Model/Parser/TransferParser.php | 1 + src/Transfer/TransferTransfer.php | 20 +++++ tests/Data/Address/Transfers/address.xml | 7 +- tests/Data/User/Transfers/user.xml | 7 +- tests/Service/TransferServiceTest.php | 4 +- 13 files changed, 149 insertions(+), 16 deletions(-) diff --git a/composer.json b/composer.json index ba45c4a..8e8c572 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,12 @@ "require-dev": { "phpstan/phpstan": "^2.1", "phpunit/phpunit": "^12.1", - "symfony/security-bundle": "^7.2" + "symfony/security-bundle": "^7.2", + "zircote/swagger-php": "^5.1" + }, + "suggest": { + "symfony/security-bundle": "^7.2", + "zircote/swagger-php": "^5.1" }, "config": { "sort-packages": true diff --git a/src/Resources/schema/transfer.xsd b/src/Resources/schema/transfer.xsd index d27fe7f..e4ecd81 100644 --- a/src/Resources/schema/transfer.xsd +++ b/src/Resources/schema/transfer.xsd @@ -48,6 +48,7 @@ + diff --git a/src/Service/Model/Generator/Generator.php b/src/Service/Model/Generator/Generator.php index ff66879..0ed0bb6 100644 --- a/src/Service/Model/Generator/Generator.php +++ b/src/Service/Model/Generator/Generator.php @@ -54,10 +54,11 @@ protected function generateTransfer(GeneratorConfigTransfer $generatorConfig, Tr $class = $namespace->addClass($transfer->getName() . 'Transfer'); $this->generateInheritance($transfer, $class); + $this->generateAnnotations($transfer, $class); foreach ($transfer->getProperties() as $property) { foreach ($this->propertyGeneratorSteps as $propertyGeneratorStep) { - $propertyGeneratorStep->generate($property, $class); + $propertyGeneratorStep->generate($transfer, $property, $class); } if ($property->isIdentifier()) { @@ -94,6 +95,10 @@ protected function generateUses(TransferTransfer $transfer, PhpNamespace $namesp $namespace->addUse($propertyTransfer->getSingularType()); } } + + if ($transfer->isApi()) { + $namespace->addUse('OpenApi\Annotations', 'OA'); + } } /** @@ -117,6 +122,25 @@ protected function generateInheritance(TransferTransfer $transfer, ClassType $cl } } + /** + * @param TransferTransfer $transfer + * @param ClassType $class + * + * @return void + */ + protected function generateAnnotations(TransferTransfer $transfer, ClassType $class): void + { + if (!$transfer->isApi()) { + return; + } + + $class->addComment( + sprintf('@OA\Schema(schema="%s", title="%s", type="object")', + $transfer->getName(), + $transfer->getName(), + )); + } + /** * @param TransferTransfer $transfer * @param ClassType $class @@ -126,7 +150,7 @@ protected function generateUserProperties( TransferTransfer $transfer, ClassType $class, ): void { - if ($transfer->getType() !== 'user' || !$transfer->getIdentifierProperty()?->getName()) { //TODO validate + if ($transfer->getType() !== 'user' || !$transfer->getIdentifierProperty()?->getName()) { return; } diff --git a/src/Service/Model/Generator/PropertyGeneratorSteps/AdderPropertyGeneratorStep.php b/src/Service/Model/Generator/PropertyGeneratorSteps/AdderPropertyGeneratorStep.php index 1338033..3868d34 100644 --- a/src/Service/Model/Generator/PropertyGeneratorSteps/AdderPropertyGeneratorStep.php +++ b/src/Service/Model/Generator/PropertyGeneratorSteps/AdderPropertyGeneratorStep.php @@ -6,13 +6,14 @@ use Nette\PhpGenerator\ClassType; use PhilippHermes\TransferBundle\Transfer\PropertyTransfer; +use PhilippHermes\TransferBundle\Transfer\TransferTransfer; class AdderPropertyGeneratorStep implements PropertyGeneratorStepInterface { /** * @inheritDoc */ - public function generate(PropertyTransfer $propertyTransfer, ClassType $class): void + public function generate(TransferTransfer $transferTransfer, PropertyTransfer $propertyTransfer, ClassType $class): void { if (!$propertyTransfer->getSingularType()) { return; diff --git a/src/Service/Model/Generator/PropertyGeneratorSteps/GetterPropertyGeneratorStep.php b/src/Service/Model/Generator/PropertyGeneratorSteps/GetterPropertyGeneratorStep.php index 82ec424..3f953a4 100644 --- a/src/Service/Model/Generator/PropertyGeneratorSteps/GetterPropertyGeneratorStep.php +++ b/src/Service/Model/Generator/PropertyGeneratorSteps/GetterPropertyGeneratorStep.php @@ -6,13 +6,14 @@ use Nette\PhpGenerator\ClassType; use PhilippHermes\TransferBundle\Transfer\PropertyTransfer; +use PhilippHermes\TransferBundle\Transfer\TransferTransfer; class GetterPropertyGeneratorStep implements PropertyGeneratorStepInterface { /** * @inheritDoc */ - public function generate(PropertyTransfer $propertyTransfer, ClassType $class): void + public function generate(TransferTransfer $transferTransfer, PropertyTransfer $propertyTransfer, ClassType $class): void { $method = $class->addMethod('get' . ucfirst($propertyTransfer->getName())); $method->setPublic(); diff --git a/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyGeneratorStepInterface.php b/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyGeneratorStepInterface.php index ad76b39..e65aa4e 100644 --- a/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyGeneratorStepInterface.php +++ b/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyGeneratorStepInterface.php @@ -6,13 +6,16 @@ use Nette\PhpGenerator\ClassType; use PhilippHermes\TransferBundle\Transfer\PropertyTransfer; +use PhilippHermes\TransferBundle\Transfer\TransferTransfer; interface PropertyGeneratorStepInterface { /** + * @param TransferTransfer $transferTransfer * @param PropertyTransfer $propertyTransfer * @param ClassType $class + * * @return void */ - public function generate(PropertyTransfer $propertyTransfer, ClassType $class): void; + public function generate(TransferTransfer $transferTransfer, PropertyTransfer $propertyTransfer, ClassType $class): void; } \ No newline at end of file diff --git a/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php b/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php index 29a1c60..bfae671 100644 --- a/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php +++ b/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php @@ -6,19 +6,24 @@ use Nette\PhpGenerator\ClassType; use PhilippHermes\TransferBundle\Transfer\PropertyTransfer; +use PhilippHermes\TransferBundle\Transfer\TransferTransfer; class PropertyPropertyGeneratorStep implements PropertyGeneratorStepInterface { /** * @inheritDoc */ - public function generate(PropertyTransfer $propertyTransfer, ClassType $class): void + public function generate(TransferTransfer $transferTransfer, PropertyTransfer $propertyTransfer, ClassType $class): void { $property = $class->addProperty($propertyTransfer->getName()); $property->setPrivate(); $property->setType(($propertyTransfer->isNullable() ? '?' : '') . $propertyTransfer->getType()); $property->addComment('@var ' . $propertyTransfer->getAnnotationType() . ($propertyTransfer->isNullable() ? '|null' : '')); + if ($transferTransfer->isApi()) { + $property->addComment($this->resolveOAAnnotation($propertyTransfer)); + } + if ($propertyTransfer->isNullable()) { $property->setValue(null); } @@ -27,4 +32,73 @@ public function generate(PropertyTransfer $propertyTransfer, ClassType $class): $property->setValue([]); } } + + protected function resolveOAAnnotation(PropertyTransfer $propertyTransfer): string + { + $type = $this->resolveOAType($propertyTransfer->getType()); + $singularType = $propertyTransfer->getSingularType() ? $this->resolveOAType($propertyTransfer->getSingularType()) : null; + + $annotation = ''; + + if (str_contains($propertyTransfer->getType(), 'Transfer')) { + $annotation = sprintf( + 'ref="#/components/schemas/%s"', + $this->resolveTransferType($propertyTransfer->getType()), + ); + } else { + $annotation = sprintf('type="%s"', $type); + } + + if ($singularType) { + $annotation = sprintf( + '@OA\Items(%s)', + str_contains($propertyTransfer->getSingularType(), 'Transfer') + ? 'ref="#/components/schemas/' . $this->resolveTransferType($propertyTransfer->getSingularType()) . '"' + : 'property="' . $singularType . '"', + ); + } + + if ($propertyTransfer->getType() === 'DateTime' || $propertyTransfer->getType() === 'DateTimeImmutable') { + $annotation .= ', format="date-time"'; + } + + if ($propertyTransfer->isNullable()) { + $annotation .= ', nullable="true"'; + } + + return sprintf( + '@OA\Property(%s)', + $annotation, + ); + } + + /** + * @param PropertyTransfer $propertyTransfer + * + * @return string + */ + protected function resolveOAType(string $type): string + { + return match ($type) { + 'string', 'DateTime', 'DateTimeImmutable' => 'string', + 'int' => 'integer', + 'float' => 'number', + 'bool' => 'boolean', + 'array', 'ArrayObject' => 'array', + default => 'object', + }; + } + + /** + * @param string $type + * + * @return string + */ + function resolveTransferType(string $type): string + { + $parts = explode('\\', $type); + $shortType = end($parts); + + return str_replace('Transfer', '', $shortType); + } } \ No newline at end of file diff --git a/src/Service/Model/Generator/PropertyGeneratorSteps/SetterPropertyGeneratorStep.php b/src/Service/Model/Generator/PropertyGeneratorSteps/SetterPropertyGeneratorStep.php index 13d8efe..6cd348e 100644 --- a/src/Service/Model/Generator/PropertyGeneratorSteps/SetterPropertyGeneratorStep.php +++ b/src/Service/Model/Generator/PropertyGeneratorSteps/SetterPropertyGeneratorStep.php @@ -6,13 +6,14 @@ use Nette\PhpGenerator\ClassType; use PhilippHermes\TransferBundle\Transfer\PropertyTransfer; +use PhilippHermes\TransferBundle\Transfer\TransferTransfer; class SetterPropertyGeneratorStep implements PropertyGeneratorStepInterface { /** * @inheritDoc */ - public function generate(PropertyTransfer $propertyTransfer, ClassType $class): void + public function generate(TransferTransfer $transferTransfer, PropertyTransfer $propertyTransfer, ClassType $class): void { $method = $class->addMethod('set' . ucfirst($propertyTransfer->getName())); diff --git a/src/Service/Model/Parser/TransferParser.php b/src/Service/Model/Parser/TransferParser.php index e81fa76..c9e80a7 100644 --- a/src/Service/Model/Parser/TransferParser.php +++ b/src/Service/Model/Parser/TransferParser.php @@ -61,6 +61,7 @@ public function parse(GeneratorConfigTransfer $generatorConfigTransfer): Transfe $transfer = new TransferTransfer(); $transfer->setName((string)$transferElement['name']); $transfer->setType((string)($transferElement['type'] ?? 'default')); + $transfer->setIsApi(isset($transferElement['api']) && ((string)$transferElement['api'] === 'true')); } foreach ($transferElement->property as $propertyElement) { diff --git a/src/Transfer/TransferTransfer.php b/src/Transfer/TransferTransfer.php index 8ffb2e0..11a7e7e 100644 --- a/src/Transfer/TransferTransfer.php +++ b/src/Transfer/TransferTransfer.php @@ -24,6 +24,8 @@ class TransferTransfer protected ?PropertyTransfer $identifierProperty = null; + protected bool $isApi = false; + /** * @return string */ @@ -137,4 +139,22 @@ public function setIdentifierProperty(?PropertyTransfer $identifierProperty): Tr $this->identifierProperty = $identifierProperty; return $this; } + + /** + * @return bool + */ + public function isApi(): bool + { + return $this->isApi; + } + + /** + * @param bool $isApi + * @return TransferTransfer + */ + public function setIsApi(bool $isApi): TransferTransfer + { + $this->isApi = $isApi; + return $this; + } } \ No newline at end of file diff --git a/tests/Data/Address/Transfers/address.xml b/tests/Data/Address/Transfers/address.xml index 2c624d7..887c2a6 100644 --- a/tests/Data/Address/Transfers/address.xml +++ b/tests/Data/Address/Transfers/address.xml @@ -2,7 +2,12 @@ - + + + + + + \ No newline at end of file diff --git a/tests/Data/User/Transfers/user.xml b/tests/Data/User/Transfers/user.xml index 0fc95c0..e6903cc 100644 --- a/tests/Data/User/Transfers/user.xml +++ b/tests/Data/User/Transfers/user.xml @@ -9,13 +9,8 @@ - + - - - - - \ No newline at end of file diff --git a/tests/Service/TransferServiceTest.php b/tests/Service/TransferServiceTest.php index 2cb5aa4..190c9c2 100644 --- a/tests/Service/TransferServiceTest.php +++ b/tests/Service/TransferServiceTest.php @@ -39,10 +39,12 @@ public function setUp(): void protected function tearDown(): void { parent::tearDown(); + return; unlink(__DIR__ . '/../Data/Generated/AddressTransfer.php'); unlink(__DIR__ . '/../Data/Generated/UserTransfer.php'); unlink(__DIR__ . '/../Data/Generated/CountryTransfer.php'); + unlink(__DIR__ . '/../Data/Generated/FooTransfer.php'); rmdir(__DIR__ . '/../Data/Generated'); } @@ -121,7 +123,7 @@ public function testGenerate(): void $foo->addBar('baaaar'); self::assertSame('baaaar', reset($foo->getBar())); - $this->transferService->clean($config); + //$this->transferService->clean($config); self::assertFileDoesNotExist(__DIR__ . '/../Data/Generated/AddressTransfer.php'); self::assertFileDoesNotExist(__DIR__ . '/../Data/Generated/UserTransfer.php'); From cfda52bbf2ce7452a1697f9f36e2828227683e48 Mon Sep 17 00:00:00 2001 From: Philipp Anton Hermes Date: Tue, 27 May 2025 18:08:41 +0200 Subject: [PATCH 02/16] changed to attributes and inject models --- src/PhilippHermesTransferBundle.php | 50 +++++++++++++++++++ src/Service/Model/Generator/Generator.php | 15 +++--- .../PropertyPropertyGeneratorStep.php | 46 ++++++++++------- tests/Service/TransferServiceTest.php | 2 +- 4 files changed, 87 insertions(+), 26 deletions(-) diff --git a/src/PhilippHermesTransferBundle.php b/src/PhilippHermesTransferBundle.php index 4b7ba11..0775307 100644 --- a/src/PhilippHermesTransferBundle.php +++ b/src/PhilippHermesTransferBundle.php @@ -66,4 +66,54 @@ public function loadExtension(array $config, ContainerConfigurator $container, C ->setArgument('$outputDir', '%transfer.output_dir%') ->setArgument('$namespace', '%transfer.namespace%'); } + + public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void + { + $outputDir = $builder->getParameter('transfer.output_dir'); + $namespace = $builder->getParameter('transfer.namespace'); + + if (!is_dir($outputDir)) { + return; + } + + $models = []; + + foreach (glob($outputDir . '/*Transfer.php') as $filePath) { + $className = $this->classFromPath($filePath, $namespace, $outputDir); + + if (!class_exists($className)) { + require_once $filePath; + } + + $alias = (new \ReflectionClass($className))->getShortName(); + $alias = preg_replace('/Transfer$/', '', $alias); + + $models[] = [ + 'alias' => $alias, + 'type' => $className, + ]; + } + + if ($models !== []) { + $builder->prependExtensionConfig('nelmio_api_doc', [ + 'models' => [ + 'names' => $models, + ], + ]); + } + } + + /** + * @param string $filePath + * @param string $namespace + * @param string $basePath + * + * @return string + */ + private function classFromPath(string $filePath, string $namespace, string $basePath): string + { + $relativePath = str_replace([$basePath, '/', '.php'], ['', '\\', ''], $filePath); + return $namespace . '\\' . $relativePath; + } + } \ No newline at end of file diff --git a/src/Service/Model/Generator/Generator.php b/src/Service/Model/Generator/Generator.php index 0ed0bb6..7799df2 100644 --- a/src/Service/Model/Generator/Generator.php +++ b/src/Service/Model/Generator/Generator.php @@ -97,7 +97,7 @@ protected function generateUses(TransferTransfer $transfer, PhpNamespace $namesp } if ($transfer->isApi()) { - $namespace->addUse('OpenApi\Annotations', 'OA'); + $namespace->addUse('OpenApi\Attributes', 'OA'); } } @@ -134,11 +134,14 @@ protected function generateAnnotations(TransferTransfer $transfer, ClassType $cl return; } - $class->addComment( - sprintf('@OA\Schema(schema="%s", title="%s", type="object")', - $transfer->getName(), - $transfer->getName(), - )); + $class->addAttribute( + 'OpenApi\Attributes\Schema', + [ + 'schema' => $transfer->getName(), + 'title' => $transfer->getName(), + 'type' => 'object', + ] + ); } /** diff --git a/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php b/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php index bfae671..ff90e2a 100644 --- a/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php +++ b/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php @@ -5,6 +5,7 @@ namespace PhilippHermes\TransferBundle\Service\Model\Generator\PropertyGeneratorSteps; use Nette\PhpGenerator\ClassType; +use OpenApi\Attributes\Items; use PhilippHermes\TransferBundle\Transfer\PropertyTransfer; use PhilippHermes\TransferBundle\Transfer\TransferTransfer; @@ -21,7 +22,10 @@ public function generate(TransferTransfer $transferTransfer, PropertyTransfer $p $property->addComment('@var ' . $propertyTransfer->getAnnotationType() . ($propertyTransfer->isNullable() ? '|null' : '')); if ($transferTransfer->isApi()) { - $property->addComment($this->resolveOAAnnotation($propertyTransfer)); + $property->addAttribute( + 'OpenApi\Attributes\Property', + $this->resolveOAAttributeArguments($propertyTransfer), + ); } if ($propertyTransfer->isNullable()) { @@ -33,43 +37,47 @@ public function generate(TransferTransfer $transferTransfer, PropertyTransfer $p } } - protected function resolveOAAnnotation(PropertyTransfer $propertyTransfer): string + /** + * @param PropertyTransfer $propertyTransfer + * + * @return array + */ + protected function resolveOAAttributeArguments(PropertyTransfer $propertyTransfer): array { $type = $this->resolveOAType($propertyTransfer->getType()); $singularType = $propertyTransfer->getSingularType() ? $this->resolveOAType($propertyTransfer->getSingularType()) : null; - $annotation = ''; + $arguments = []; if (str_contains($propertyTransfer->getType(), 'Transfer')) { - $annotation = sprintf( - 'ref="#/components/schemas/%s"', - $this->resolveTransferType($propertyTransfer->getType()), + $arguments['ref'] = sprintf( + '#/components/schemas/%s', + $this->resolveTransferType($propertyTransfer->getType()), ); } else { - $annotation = sprintf('type="%s"', $type); + $arguments['type'] = $type; } if ($singularType) { - $annotation = sprintf( - '@OA\Items(%s)', - str_contains($propertyTransfer->getSingularType(), 'Transfer') - ? 'ref="#/components/schemas/' . $this->resolveTransferType($propertyTransfer->getSingularType()) . '"' - : 'property="' . $singularType . '"', - ); + if (str_contains($propertyTransfer->getSingularType(), 'Transfer')) { + $arguments['items'] = new Items(ref: sprintf( + '#/components/schemas/%s', + $this->resolveTransferType($propertyTransfer->getSingularType()), + )); + } else { + $arguments['items'] = new Items(type: $singularType); + } } if ($propertyTransfer->getType() === 'DateTime' || $propertyTransfer->getType() === 'DateTimeImmutable') { - $annotation .= ', format="date-time"'; + $arguments['format'] = 'date-time'; } if ($propertyTransfer->isNullable()) { - $annotation .= ', nullable="true"'; + $arguments['nullable'] = true; } - return sprintf( - '@OA\Property(%s)', - $annotation, - ); + return $arguments; } /** diff --git a/tests/Service/TransferServiceTest.php b/tests/Service/TransferServiceTest.php index 190c9c2..3d66edc 100644 --- a/tests/Service/TransferServiceTest.php +++ b/tests/Service/TransferServiceTest.php @@ -123,7 +123,7 @@ public function testGenerate(): void $foo->addBar('baaaar'); self::assertSame('baaaar', reset($foo->getBar())); - //$this->transferService->clean($config); + $this->transferService->clean($config); self::assertFileDoesNotExist(__DIR__ . '/../Data/Generated/AddressTransfer.php'); self::assertFileDoesNotExist(__DIR__ . '/../Data/Generated/UserTransfer.php'); From f3d9a22d2bb99da49726ec65685c4d54e14e0e97 Mon Sep 17 00:00:00 2001 From: Philipp Anton Hermes Date: Tue, 27 May 2025 18:10:09 +0200 Subject: [PATCH 03/16] changed to attributes and inject models --- src/PhilippHermesTransferBundle.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PhilippHermesTransferBundle.php b/src/PhilippHermesTransferBundle.php index 0775307..102e85a 100644 --- a/src/PhilippHermesTransferBundle.php +++ b/src/PhilippHermesTransferBundle.php @@ -69,8 +69,8 @@ public function loadExtension(array $config, ContainerConfigurator $container, C public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void { - $outputDir = $builder->getParameter('transfer.output_dir'); - $namespace = $builder->getParameter('transfer.namespace'); + $outputDir = $builder->getParameter('%transfer.output_dir%'); + $namespace = $builder->getParameter('%transfer.namespace%'); if (!is_dir($outputDir)) { return; From cad3b0c4582162e15a80da7cd64eb037eb71ee00 Mon Sep 17 00:00:00 2001 From: Philipp Anton Hermes Date: Tue, 27 May 2025 18:11:57 +0200 Subject: [PATCH 04/16] changed to attributes and inject models --- src/PhilippHermesTransferBundle.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/PhilippHermesTransferBundle.php b/src/PhilippHermesTransferBundle.php index 102e85a..fce85a7 100644 --- a/src/PhilippHermesTransferBundle.php +++ b/src/PhilippHermesTransferBundle.php @@ -69,8 +69,11 @@ public function loadExtension(array $config, ContainerConfigurator $container, C public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void { - $outputDir = $builder->getParameter('%transfer.output_dir%'); - $namespace = $builder->getParameter('%transfer.namespace%'); + $configs = $builder->getExtensionConfig($this->extensionAlias); + $config = array_replace_recursive(...$configs); + + $outputDir = $config['output_dir']; + $namespace = $config['namespace']; if (!is_dir($outputDir)) { return; From 2777980f0d44dac88fddf7fbe5ab48984e4cb501 Mon Sep 17 00:00:00 2001 From: Philipp Anton Hermes Date: Tue, 27 May 2025 18:13:13 +0200 Subject: [PATCH 05/16] changed to attributes and inject models --- src/PhilippHermesTransferBundle.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PhilippHermesTransferBundle.php b/src/PhilippHermesTransferBundle.php index fce85a7..7736b41 100644 --- a/src/PhilippHermesTransferBundle.php +++ b/src/PhilippHermesTransferBundle.php @@ -72,10 +72,10 @@ public function prependExtension(ContainerConfigurator $container, ContainerBuil $configs = $builder->getExtensionConfig($this->extensionAlias); $config = array_replace_recursive(...$configs); - $outputDir = $config['output_dir']; - $namespace = $config['namespace']; + $outputDir = $config['output_dir'] ?? null; + $namespace = $config['namespace'] ?? null; - if (!is_dir($outputDir)) { + if (!$outputDir || !$namespace || !is_dir($outputDir)) { return; } From 42f6fbfb1a60787e67d55fcee4f399260936aadb Mon Sep 17 00:00:00 2001 From: Philipp Anton Hermes Date: Tue, 27 May 2025 18:18:20 +0200 Subject: [PATCH 06/16] changed to attributes and inject models --- src/PhilippHermesTransferBundle.php | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/PhilippHermesTransferBundle.php b/src/PhilippHermesTransferBundle.php index 7736b41..0284b68 100644 --- a/src/PhilippHermesTransferBundle.php +++ b/src/PhilippHermesTransferBundle.php @@ -83,13 +83,7 @@ public function prependExtension(ContainerConfigurator $container, ContainerBuil foreach (glob($outputDir . '/*Transfer.php') as $filePath) { $className = $this->classFromPath($filePath, $namespace, $outputDir); - - if (!class_exists($className)) { - require_once $filePath; - } - - $alias = (new \ReflectionClass($className))->getShortName(); - $alias = preg_replace('/Transfer$/', '', $alias); + $alias = $this->aliasFromPath($filePath); $models[] = [ 'alias' => $alias, @@ -106,17 +100,17 @@ public function prependExtension(ContainerConfigurator $container, ContainerBuil } } - /** - * @param string $filePath - * @param string $namespace - * @param string $basePath - * - * @return string - */ - private function classFromPath(string $filePath, string $namespace, string $basePath): string + protected function classFromPath(string $filePath, string $namespace, string $outputDir): string { - $relativePath = str_replace([$basePath, '/', '.php'], ['', '\\', ''], $filePath); + $relativePath = str_replace([$outputDir, '/', '.php'], ['', '\\', ''], $filePath); + return $namespace . '\\' . $relativePath; } + protected function aliasFromPath(string $filePath): string + { + $filename = basename($filePath, '.php'); + + return preg_replace('/Transfer$/', '', $filename); + } } \ No newline at end of file From 479a3b8ae2a98c231120338cd36c42e59573af6b Mon Sep 17 00:00:00 2001 From: Philipp Anton Hermes Date: Tue, 27 May 2025 18:25:07 +0200 Subject: [PATCH 07/16] remove prepend --- src/PhilippHermesTransferBundle.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PhilippHermesTransferBundle.php b/src/PhilippHermesTransferBundle.php index 0284b68..e2e520b 100644 --- a/src/PhilippHermesTransferBundle.php +++ b/src/PhilippHermesTransferBundle.php @@ -67,6 +67,7 @@ public function loadExtension(array $config, ContainerConfigurator $container, C ->setArgument('$namespace', '%transfer.namespace%'); } + /* public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void { $configs = $builder->getExtensionConfig($this->extensionAlias); @@ -112,5 +113,5 @@ protected function aliasFromPath(string $filePath): string $filename = basename($filePath, '.php'); return preg_replace('/Transfer$/', '', $filename); - } + }*/ } \ No newline at end of file From 056f47d80cc05c9a8c7f550dc928667d07bb35f0 Mon Sep 17 00:00:00 2001 From: Philipp Anton Hermes Date: Tue, 27 May 2025 18:37:22 +0200 Subject: [PATCH 08/16] add prepend and fix attribute generation --- src/PhilippHermesTransferBundle.php | 4 ++-- .../PropertyPropertyGeneratorStep.php | 21 ++++++++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/PhilippHermesTransferBundle.php b/src/PhilippHermesTransferBundle.php index e2e520b..b337aef 100644 --- a/src/PhilippHermesTransferBundle.php +++ b/src/PhilippHermesTransferBundle.php @@ -67,7 +67,7 @@ public function loadExtension(array $config, ContainerConfigurator $container, C ->setArgument('$namespace', '%transfer.namespace%'); } - /* + public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void { $configs = $builder->getExtensionConfig($this->extensionAlias); @@ -113,5 +113,5 @@ protected function aliasFromPath(string $filePath): string $filename = basename($filePath, '.php'); return preg_replace('/Transfer$/', '', $filename); - }*/ + } } \ No newline at end of file diff --git a/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php b/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php index ff90e2a..56de7e8 100644 --- a/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php +++ b/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php @@ -5,6 +5,7 @@ namespace PhilippHermes\TransferBundle\Service\Model\Generator\PropertyGeneratorSteps; use Nette\PhpGenerator\ClassType; +use Nette\PhpGenerator\Literal; use OpenApi\Attributes\Items; use PhilippHermes\TransferBundle\Transfer\PropertyTransfer; use PhilippHermes\TransferBundle\Transfer\TransferTransfer; @@ -60,12 +61,22 @@ protected function resolveOAAttributeArguments(PropertyTransfer $propertyTransfe if ($singularType) { if (str_contains($propertyTransfer->getSingularType(), 'Transfer')) { - $arguments['items'] = new Items(ref: sprintf( - '#/components/schemas/%s', - $this->resolveTransferType($propertyTransfer->getSingularType()), - )); + $arguments['items'] = new Literal( + 'OpenApi\Attributes\Items', + [ + 'ref' => sprintf( + '#/components/schemas/%s', + $this->resolveTransferType($propertyTransfer->getSingularType()), + ), + ], + ); } else { - $arguments['items'] = new Items(type: $singularType); + $arguments['items'] = new Literal( + 'OpenApi\Attributes\Items', + [ + 'type' => $singularType, + ], + ); } } From 57eaa46d4319f875eea6e06c87624855308f8d32 Mon Sep 17 00:00:00 2001 From: Philipp Anton Hermes Date: Tue, 27 May 2025 18:41:06 +0200 Subject: [PATCH 09/16] add prepend and fix attribute generation --- .../PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php b/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php index 56de7e8..2b3aa34 100644 --- a/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php +++ b/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php @@ -62,7 +62,7 @@ protected function resolveOAAttributeArguments(PropertyTransfer $propertyTransfe if ($singularType) { if (str_contains($propertyTransfer->getSingularType(), 'Transfer')) { $arguments['items'] = new Literal( - 'OpenApi\Attributes\Items', + 'new \OpenApi\Attributes\Items(ref: %ref)', [ 'ref' => sprintf( '#/components/schemas/%s', @@ -72,7 +72,7 @@ protected function resolveOAAttributeArguments(PropertyTransfer $propertyTransfe ); } else { $arguments['items'] = new Literal( - 'OpenApi\Attributes\Items', + 'new \OpenApi\Attributes\Items(type: %type)', [ 'type' => $singularType, ], From a94404031f19340c8ddb2541cee5f0ca423e8da3 Mon Sep 17 00:00:00 2001 From: Philipp Anton Hermes Date: Tue, 27 May 2025 18:50:56 +0200 Subject: [PATCH 10/16] fix attribute generation --- .../PropertyPropertyGeneratorStep.php | 11 +++++------ tests/Data/Foo/Transfers/foo.xml | 7 ++++++- tests/Service/TransferServiceTest.php | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php b/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php index 2b3aa34..ec360e1 100644 --- a/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php +++ b/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php @@ -6,7 +6,6 @@ use Nette\PhpGenerator\ClassType; use Nette\PhpGenerator\Literal; -use OpenApi\Attributes\Items; use PhilippHermes\TransferBundle\Transfer\PropertyTransfer; use PhilippHermes\TransferBundle\Transfer\TransferTransfer; @@ -61,18 +60,18 @@ protected function resolveOAAttributeArguments(PropertyTransfer $propertyTransfe if ($singularType) { if (str_contains($propertyTransfer->getSingularType(), 'Transfer')) { - $arguments['items'] = new Literal( - 'new \OpenApi\Attributes\Items(ref: %ref)', + $arguments['items'] = Literal::new( + 'OA\Items', [ 'ref' => sprintf( '#/components/schemas/%s', $this->resolveTransferType($propertyTransfer->getSingularType()), ), - ], + ] ); } else { - $arguments['items'] = new Literal( - 'new \OpenApi\Attributes\Items(type: %type)', + $arguments['items'] = Literal::new( + 'OA\Items', [ 'type' => $singularType, ], diff --git a/tests/Data/Foo/Transfers/foo.xml b/tests/Data/Foo/Transfers/foo.xml index 0f89eec..300b658 100644 --- a/tests/Data/Foo/Transfers/foo.xml +++ b/tests/Data/Foo/Transfers/foo.xml @@ -2,8 +2,13 @@ - + + + + + + \ No newline at end of file diff --git a/tests/Service/TransferServiceTest.php b/tests/Service/TransferServiceTest.php index 3d66edc..190c9c2 100644 --- a/tests/Service/TransferServiceTest.php +++ b/tests/Service/TransferServiceTest.php @@ -123,7 +123,7 @@ public function testGenerate(): void $foo->addBar('baaaar'); self::assertSame('baaaar', reset($foo->getBar())); - $this->transferService->clean($config); + //$this->transferService->clean($config); self::assertFileDoesNotExist(__DIR__ . '/../Data/Generated/AddressTransfer.php'); self::assertFileDoesNotExist(__DIR__ . '/../Data/Generated/UserTransfer.php'); From c856d0461d272286a109088791ed652a85d7e4fa Mon Sep 17 00:00:00 2001 From: Philipp Anton Hermes Date: Tue, 27 May 2025 19:04:29 +0200 Subject: [PATCH 11/16] add compiler pass --- src/PhilippHermesTransferBundle.php | 48 ++----------------- .../RegisterTransferModelsCompilerPass.php | 45 +++++++++++++++++ 2 files changed, 49 insertions(+), 44 deletions(-) create mode 100644 src/Service/DependencyInjection/Compiler/RegisterTransferModelsCompilerPass.php diff --git a/src/PhilippHermesTransferBundle.php b/src/PhilippHermesTransferBundle.php index b337aef..620f106 100644 --- a/src/PhilippHermesTransferBundle.php +++ b/src/PhilippHermesTransferBundle.php @@ -5,6 +5,7 @@ namespace PhilippHermes\TransferBundle; use PhilippHermes\TransferBundle\Command\TransferGenerateCommand; +use PhilippHermes\TransferBundle\Service\DependencyInjection\Compiler\RegisterTransferModelsCompilerPass; use PhilippHermes\TransferBundle\Service\TransferService; use PhilippHermes\TransferBundle\Service\TransferServiceFactory; use PhilippHermes\TransferBundle\Service\TransferServiceInterface; @@ -67,51 +68,10 @@ public function loadExtension(array $config, ContainerConfigurator $container, C ->setArgument('$namespace', '%transfer.namespace%'); } - - public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void - { - $configs = $builder->getExtensionConfig($this->extensionAlias); - $config = array_replace_recursive(...$configs); - - $outputDir = $config['output_dir'] ?? null; - $namespace = $config['namespace'] ?? null; - - if (!$outputDir || !$namespace || !is_dir($outputDir)) { - return; - } - - $models = []; - - foreach (glob($outputDir . '/*Transfer.php') as $filePath) { - $className = $this->classFromPath($filePath, $namespace, $outputDir); - $alias = $this->aliasFromPath($filePath); - - $models[] = [ - 'alias' => $alias, - 'type' => $className, - ]; - } - - if ($models !== []) { - $builder->prependExtensionConfig('nelmio_api_doc', [ - 'models' => [ - 'names' => $models, - ], - ]); - } - } - - protected function classFromPath(string $filePath, string $namespace, string $outputDir): string - { - $relativePath = str_replace([$outputDir, '/', '.php'], ['', '\\', ''], $filePath); - - return $namespace . '\\' . $relativePath; - } - - protected function aliasFromPath(string $filePath): string + public function build(ContainerBuilder $container): void { - $filename = basename($filePath, '.php'); + parent::build($container); - return preg_replace('/Transfer$/', '', $filename); + $container->addCompilerPass(new RegisterTransferModelsCompilerPass()); } } \ No newline at end of file diff --git a/src/Service/DependencyInjection/Compiler/RegisterTransferModelsCompilerPass.php b/src/Service/DependencyInjection/Compiler/RegisterTransferModelsCompilerPass.php new file mode 100644 index 0000000..b9bb732 --- /dev/null +++ b/src/Service/DependencyInjection/Compiler/RegisterTransferModelsCompilerPass.php @@ -0,0 +1,45 @@ +has('nelmio_api_doc.model_describers.model_registry')) { + return; + } + + if (!$container->hasParameter('transfer.output_dir') || !$container->hasParameter('transfer.namespace')) { + return; + } + + $outputDir = $container->getParameter('transfer.output_dir'); + $namespace = $container->getParameter('transfer.namespace'); + + foreach (glob($outputDir . '/*Transfer.php') as $filePath) { + $className = $this->classFromPath($filePath, $outputDir, $namespace); + + if (!class_exists($className)) { + continue; + } + + $alias = (new \ReflectionClass($className))->getShortName(); + $alias = preg_replace('/Transfer$/', '', $alias); + + $container->getDefinition('nelmio_api_doc.model_describers.model_registry') + ->addMethodCall('register', [$alias, $className]); + } + } + + private function classFromPath(string $filePath, string $baseDir, string $namespace): string + { + $relativePath = str_replace([$baseDir . '/', '.php'], ['', ''], $filePath); + return $namespace . '\\' . str_replace('/', '\\', $relativePath); + } +} \ No newline at end of file From 6ef9e56648ae4a4de80057b2bd8d0945d2c8929a Mon Sep 17 00:00:00 2001 From: Philipp Anton Hermes Date: Tue, 27 May 2025 19:21:20 +0200 Subject: [PATCH 12/16] remove compiler pass --- src/PhilippHermesTransferBundle.php | 48 +++++++++++++++++-- .../RegisterTransferModelsCompilerPass.php | 45 ----------------- 2 files changed, 44 insertions(+), 49 deletions(-) delete mode 100644 src/Service/DependencyInjection/Compiler/RegisterTransferModelsCompilerPass.php diff --git a/src/PhilippHermesTransferBundle.php b/src/PhilippHermesTransferBundle.php index 620f106..b337aef 100644 --- a/src/PhilippHermesTransferBundle.php +++ b/src/PhilippHermesTransferBundle.php @@ -5,7 +5,6 @@ namespace PhilippHermes\TransferBundle; use PhilippHermes\TransferBundle\Command\TransferGenerateCommand; -use PhilippHermes\TransferBundle\Service\DependencyInjection\Compiler\RegisterTransferModelsCompilerPass; use PhilippHermes\TransferBundle\Service\TransferService; use PhilippHermes\TransferBundle\Service\TransferServiceFactory; use PhilippHermes\TransferBundle\Service\TransferServiceInterface; @@ -68,10 +67,51 @@ public function loadExtension(array $config, ContainerConfigurator $container, C ->setArgument('$namespace', '%transfer.namespace%'); } - public function build(ContainerBuilder $container): void + + public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void + { + $configs = $builder->getExtensionConfig($this->extensionAlias); + $config = array_replace_recursive(...$configs); + + $outputDir = $config['output_dir'] ?? null; + $namespace = $config['namespace'] ?? null; + + if (!$outputDir || !$namespace || !is_dir($outputDir)) { + return; + } + + $models = []; + + foreach (glob($outputDir . '/*Transfer.php') as $filePath) { + $className = $this->classFromPath($filePath, $namespace, $outputDir); + $alias = $this->aliasFromPath($filePath); + + $models[] = [ + 'alias' => $alias, + 'type' => $className, + ]; + } + + if ($models !== []) { + $builder->prependExtensionConfig('nelmio_api_doc', [ + 'models' => [ + 'names' => $models, + ], + ]); + } + } + + protected function classFromPath(string $filePath, string $namespace, string $outputDir): string + { + $relativePath = str_replace([$outputDir, '/', '.php'], ['', '\\', ''], $filePath); + + return $namespace . '\\' . $relativePath; + } + + protected function aliasFromPath(string $filePath): string { - parent::build($container); + $filename = basename($filePath, '.php'); - $container->addCompilerPass(new RegisterTransferModelsCompilerPass()); + return preg_replace('/Transfer$/', '', $filename); } } \ No newline at end of file diff --git a/src/Service/DependencyInjection/Compiler/RegisterTransferModelsCompilerPass.php b/src/Service/DependencyInjection/Compiler/RegisterTransferModelsCompilerPass.php deleted file mode 100644 index b9bb732..0000000 --- a/src/Service/DependencyInjection/Compiler/RegisterTransferModelsCompilerPass.php +++ /dev/null @@ -1,45 +0,0 @@ -has('nelmio_api_doc.model_describers.model_registry')) { - return; - } - - if (!$container->hasParameter('transfer.output_dir') || !$container->hasParameter('transfer.namespace')) { - return; - } - - $outputDir = $container->getParameter('transfer.output_dir'); - $namespace = $container->getParameter('transfer.namespace'); - - foreach (glob($outputDir . '/*Transfer.php') as $filePath) { - $className = $this->classFromPath($filePath, $outputDir, $namespace); - - if (!class_exists($className)) { - continue; - } - - $alias = (new \ReflectionClass($className))->getShortName(); - $alias = preg_replace('/Transfer$/', '', $alias); - - $container->getDefinition('nelmio_api_doc.model_describers.model_registry') - ->addMethodCall('register', [$alias, $className]); - } - } - - private function classFromPath(string $filePath, string $baseDir, string $namespace): string - { - $relativePath = str_replace([$baseDir . '/', '.php'], ['', ''], $filePath); - return $namespace . '\\' . str_replace('/', '\\', $relativePath); - } -} \ No newline at end of file From 10c51ff41c8188dd155113f63f612928d92374bc Mon Sep 17 00:00:00 2001 From: Philipp Anton Hermes Date: Tue, 27 May 2025 19:33:36 +0200 Subject: [PATCH 13/16] fix prepend --- src/PhilippHermesTransferBundle.php | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/PhilippHermesTransferBundle.php b/src/PhilippHermesTransferBundle.php index b337aef..574ab3a 100644 --- a/src/PhilippHermesTransferBundle.php +++ b/src/PhilippHermesTransferBundle.php @@ -70,11 +70,19 @@ public function loadExtension(array $config, ContainerConfigurator $container, C public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void { - $configs = $builder->getExtensionConfig($this->extensionAlias); - $config = array_replace_recursive(...$configs); - $outputDir = $config['output_dir'] ?? null; - $namespace = $config['namespace'] ?? null; + try { + $outputDir = $builder->getParameter('transfer.output_dir'); + } catch (\Exception) { + $projectDir = $builder->getParameter('kernel.project_dir'); + $outputDir = $projectDir . '/src/Generated/Transfers'; + } + + try { + $namespace = $builder->getParameter('transfer.namespace'); + } catch (\Exception) { + $namespace = 'App\\Generated\\Transfers'; + } if (!$outputDir || !$namespace || !is_dir($outputDir)) { return; @@ -86,6 +94,11 @@ public function prependExtension(ContainerConfigurator $container, ContainerBuil $className = $this->classFromPath($filePath, $namespace, $outputDir); $alias = $this->aliasFromPath($filePath); + $data = file_get_contents($filePath); + if (!str_contains($data, 'use OpenApi\Attributes as OA;')) { + continue; + } + $models[] = [ 'alias' => $alias, 'type' => $className, @@ -105,7 +118,7 @@ protected function classFromPath(string $filePath, string $namespace, string $ou { $relativePath = str_replace([$outputDir, '/', '.php'], ['', '\\', ''], $filePath); - return $namespace . '\\' . $relativePath; + return $namespace . $relativePath; } protected function aliasFromPath(string $filePath): string From fb9ab6af1130ba91f0d62f1882b87361e8b579b5 Mon Sep 17 00:00:00 2001 From: Philipp Hermes Date: Thu, 18 Dec 2025 12:05:36 +0100 Subject: [PATCH 14/16] remove user type update packages --- README.md | 41 ++------- composer.json | 31 +++---- src/PhilippHermesTransferBundle.php | 39 +++++--- src/Resources/schema/transfer.xsd | 10 --- src/Service/Model/Generator/Generator.php | 89 ------------------- .../PropertyPropertyGeneratorStep.php | 4 +- src/Service/Model/Parser/TransferParser.php | 5 +- src/Transfer/PropertyTransfer.php | 39 +------- src/Transfer/TransferTransfer.php | 20 ----- tests/Data/User/Transfers/user.xml | 5 +- tests/Service/TransferServiceTest.php | 23 +---- 11 files changed, 53 insertions(+), 253 deletions(-) diff --git a/README.md b/README.md index 558417c..163d988 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,16 @@ # Transfer Bundle -### Requirements: -* php: >= 8.3 -* symfony: >= 7.2 -* ext-simplexml: * +[![CI](https://github.com/philipphermes/transfer-bundle/actions/workflows/ci.yml/badge.svg)](https://github.com/philipphermes/transfer-bundle/actions/workflows/ci.yml) +[![PHP](https://img.shields.io/badge/php-%3E%3D%208.4-8892BF.svg)]((https://img.shields.io/badge/php-%3E%3D%208.4-8892BF.svg)) +[![Symfony](https://img.shields.io/badge/symfony-8-8892BF.svg)]((https://img.shields.io/badge/symfony-8.0-8892BF.svg)) -## Usage: +## Installation ```shell composer require philipphermes/transfer-bundle ``` -### Configuration: +### Configuration ```php // config/bundles.php @@ -21,12 +20,12 @@ return [ ]; ``` -#### Optional Configs: +#### Optional Configs * `transfer.namespace`: `App\\Generated\\Transfers` * `transfer.schema_dir`: `%kernel.project_dir%/transfers` * `transfer.output_dir`: `%kernel.project_dir%/src/Generated/Transfers` -### Define Transfers: +### Define Transfers * you can create multiple files * if multiple files have the same transfer they will be merged * if you define the same property twice the first on it gets is taken @@ -48,32 +47,6 @@ return [ ``` -#### Security Bundle Integration -If you want to use this feature make sure you have the Security Bundle installed. - -```shell -composer require symfony/security-bundle -``` - -Then you can define your transfer eg. like this: - -```xml - - - - - - - - - -``` - -it will implement the UserInterface and have all required methods like: -* getUserIdentifier -* eraseCredentials -* get/set Roles ### Run ```shell diff --git a/composer.json b/composer.json index 8e8c572..f26d67e 100644 --- a/composer.json +++ b/composer.json @@ -21,35 +21,26 @@ } }, "require": { - "php": ">=8.3", + "php": ">=8.4", "ext-simplexml": "*", - "nette/php-generator": "^4.1", - "symfony/config": "^7.2", - "symfony/console": "^7.2", - "symfony/dependency-injection": "^7.2", - "symfony/filesystem": "^7.2", - "symfony/finder": "^7.2", - "symfony/framework-bundle": "^7.2" + "nette/php-generator": "^v4.2", + "symfony/config": "^8.0", + "symfony/console": "^8.0", + "symfony/dependency-injection": "^8.0", + "symfony/filesystem": "^8.0", + "symfony/finder": "^8.0", + "symfony/framework-bundle": "^8.0" }, "require-dev": { "phpstan/phpstan": "^2.1", "phpunit/phpunit": "^12.1", - "symfony/security-bundle": "^7.2", - "zircote/swagger-php": "^5.1" + "zircote/swagger-php": "^5.7" }, "suggest": { - "symfony/security-bundle": "^7.2", - "zircote/swagger-php": "^5.1" + "symfony/security-bundle": "^8.0", + "zircote/swagger-php": "^5.7" }, "config": { "sort-packages": true - }, - "scripts": { - "phpstan": [ - "vendor/bin/phpstan analyse -c phpstan.neon" - ], - "test": [ - "XDEBUG_MODE=coverage vendor/bin/phpunit" - ] } } diff --git a/src/PhilippHermesTransferBundle.php b/src/PhilippHermesTransferBundle.php index 574ab3a..392480d 100644 --- a/src/PhilippHermesTransferBundle.php +++ b/src/PhilippHermesTransferBundle.php @@ -22,7 +22,6 @@ class PhilippHermesTransferBundle extends AbstractBundle */ public function configure(DefinitionConfigurator $definition): void { - /** @phpstan-ignore-next-line */ $definition->rootNode() ->children() ->scalarNode('schema_dir') @@ -39,10 +38,9 @@ public function configure(DefinitionConfigurator $definition): void } /** - * * @inheritDoc * - * @param array $config + * @param array $config */ public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void { @@ -67,14 +65,18 @@ public function loadExtension(array $config, ContainerConfigurator $container, C ->setArgument('$namespace', '%transfer.namespace%'); } - + /** + * @inheritDoc + */ public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void { - try { $outputDir = $builder->getParameter('transfer.output_dir'); } catch (\Exception) { $projectDir = $builder->getParameter('kernel.project_dir'); + if (!is_string($projectDir)) { + return; + } $outputDir = $projectDir . '/src/Generated/Transfers'; } @@ -84,23 +86,26 @@ public function prependExtension(ContainerConfigurator $container, ContainerBuil $namespace = 'App\\Generated\\Transfers'; } - if (!$outputDir || !$namespace || !is_dir($outputDir)) { + if (!is_string($outputDir) || !is_string($namespace) || !is_dir($outputDir)) { return; } $models = []; + $filePaths = glob($outputDir . '/*Transfer.php'); + if (!$filePaths) { + return; + } - foreach (glob($outputDir . '/*Transfer.php') as $filePath) { + foreach ($filePaths as $filePath) { $className = $this->classFromPath($filePath, $namespace, $outputDir); - $alias = $this->aliasFromPath($filePath); $data = file_get_contents($filePath); - if (!str_contains($data, 'use OpenApi\Attributes as OA;')) { + if (!$data || !str_contains($data, 'use OpenApi\Attributes as OA;')) { continue; } $models[] = [ - 'alias' => $alias, + 'alias' => $this->aliasFromPath($filePath), 'type' => $className, ]; } @@ -114,6 +119,13 @@ public function prependExtension(ContainerConfigurator $container, ContainerBuil } } + /** + * @param string $filePath + * @param string $namespace + * @param string $outputDir + * + * @return string + */ protected function classFromPath(string $filePath, string $namespace, string $outputDir): string { $relativePath = str_replace([$outputDir, '/', '.php'], ['', '\\', ''], $filePath); @@ -121,7 +133,12 @@ protected function classFromPath(string $filePath, string $namespace, string $ou return $namespace . $relativePath; } - protected function aliasFromPath(string $filePath): string + /** + * @param string $filePath + * + * @return string|null + */ + protected function aliasFromPath(string $filePath): ?string { $filename = basename($filePath, '.php'); diff --git a/src/Resources/schema/transfer.xsd b/src/Resources/schema/transfer.xsd index e4ecd81..fce718b 100644 --- a/src/Resources/schema/transfer.xsd +++ b/src/Resources/schema/transfer.xsd @@ -20,13 +20,6 @@ - - - - - - - @@ -41,13 +34,10 @@ - - - diff --git a/src/Service/Model/Generator/Generator.php b/src/Service/Model/Generator/Generator.php index 7799df2..aebec82 100644 --- a/src/Service/Model/Generator/Generator.php +++ b/src/Service/Model/Generator/Generator.php @@ -10,7 +10,6 @@ use PhilippHermes\TransferBundle\Service\Model\Generator\PropertyGeneratorSteps\PropertyGeneratorStepInterface; use PhilippHermes\TransferBundle\Service\Model\Type\PropertyTypeMapper; use PhilippHermes\TransferBundle\Transfer\GeneratorConfigTransfer; -use PhilippHermes\TransferBundle\Transfer\PropertyTransfer; use PhilippHermes\TransferBundle\Transfer\TransferCollectionTransfer; use PhilippHermes\TransferBundle\Transfer\TransferTransfer; @@ -33,7 +32,6 @@ public function generate( TransferCollectionTransfer $transferCollectionTransfer, ): void { foreach ($transferCollectionTransfer->getTransfers() as $transfer) { - $transfer = $this->addRoleProperty($transfer); $this->generateTransfer($generatorConfigTransfer, $transfer); } } @@ -53,25 +51,14 @@ protected function generateTransfer(GeneratorConfigTransfer $generatorConfig, Tr $this->generateUses($transfer, $namespace); $class = $namespace->addClass($transfer->getName() . 'Transfer'); - $this->generateInheritance($transfer, $class); $this->generateAnnotations($transfer, $class); foreach ($transfer->getProperties() as $property) { foreach ($this->propertyGeneratorSteps as $propertyGeneratorStep) { $propertyGeneratorStep->generate($transfer, $property, $class); } - - if ($property->isIdentifier()) { - $transfer->setIdentifierProperty($property); - } - - if ($property->isSensitive()) { - $transfer->addSensitiveProperty($property); - } } - $this->generateUserProperties($transfer, $class); - if (!is_dir($generatorConfig->getOutputDirectory())) { mkdir($generatorConfig->getOutputDirectory(), 0777, true); } @@ -101,27 +88,6 @@ protected function generateUses(TransferTransfer $transfer, PhpNamespace $namesp } } - /** - * @param TransferTransfer $transfer - * @param ClassType $class - * @return void - */ - protected function generateInheritance(TransferTransfer $transfer, ClassType $class): void - { - if ($transfer->getType() !== 'user') { - return; - } - - $class->addImplement('Symfony\Component\Security\Core\User\UserInterface'); - - foreach ($transfer->getProperties() as $property) { - if ($property->getName() === 'password') { - $class->addImplement('Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface'); - break; - } - } - } - /** * @param TransferTransfer $transfer * @param ClassType $class @@ -143,59 +109,4 @@ protected function generateAnnotations(TransferTransfer $transfer, ClassType $cl ] ); } - - /** - * @param TransferTransfer $transfer - * @param ClassType $class - * @return void - */ - protected function generateUserProperties( - TransferTransfer $transfer, - ClassType $class, - ): void { - if ($transfer->getType() !== 'user' || !$transfer->getIdentifierProperty()?->getName()) { - return; - } - - $methodUserIdentifier = $class->addMethod('getUserIdentifier'); - $methodUserIdentifier->setPublic(); - $methodUserIdentifier->setReturnType('string'); - $methodUserIdentifier->setComment('@return string'); - $methodUserIdentifier->addBody('return $this->' . $transfer->getIdentifierProperty()->getName() . ';'); - - $methodEraseCredentials = $class->addMethod('eraseCredentials'); - $methodEraseCredentials->setPublic(); - $methodEraseCredentials->setReturnType('void'); - $methodEraseCredentials->setComment('@return void'); - - foreach ($transfer->getSensitiveProperties() as $property) { - $methodEraseCredentials->addBody('$this->' . $property->getName() . ' = null;'); - } - } - - /** - * @param TransferTransfer $transfer - * @return TransferTransfer - */ - protected function addRoleProperty(TransferTransfer $transfer): TransferTransfer - { - if ($transfer->getType() !== 'user') { - return $transfer; - } - - foreach ($transfer->getProperties() as $propertyTransfer) { - if ($propertyTransfer->getName() === 'roles') { - return $transfer; - } - } - - return $transfer->addProperty((new PropertyTransfer()) - ->setName('roles') - ->setSingular('role') - ->setType('array') - ->setSingularType('string') - ->setAnnotationType('array') - ->setSingularAnnotationType('string'), - ); - } } \ No newline at end of file diff --git a/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php b/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php index ec360e1..ba52ab8 100644 --- a/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php +++ b/src/Service/Model/Generator/PropertyGeneratorSteps/PropertyPropertyGeneratorStep.php @@ -59,7 +59,7 @@ protected function resolveOAAttributeArguments(PropertyTransfer $propertyTransfe } if ($singularType) { - if (str_contains($propertyTransfer->getSingularType(), 'Transfer')) { + if ($propertyTransfer->getSingularType() && str_contains($propertyTransfer->getSingularType(), 'Transfer')) { $arguments['items'] = Literal::new( 'OA\Items', [ @@ -91,7 +91,7 @@ protected function resolveOAAttributeArguments(PropertyTransfer $propertyTransfe } /** - * @param PropertyTransfer $propertyTransfer + * @param string $type * * @return string */ diff --git a/src/Service/Model/Parser/TransferParser.php b/src/Service/Model/Parser/TransferParser.php index c9e80a7..56835d1 100644 --- a/src/Service/Model/Parser/TransferParser.php +++ b/src/Service/Model/Parser/TransferParser.php @@ -60,7 +60,6 @@ public function parse(GeneratorConfigTransfer $generatorConfigTransfer): Transfe if (!$transfer) { $transfer = new TransferTransfer(); $transfer->setName((string)$transferElement['name']); - $transfer->setType((string)($transferElement['type'] ?? 'default')); $transfer->setIsApi(isset($transferElement['api']) && ((string)$transferElement['api'] === 'true')); } @@ -96,9 +95,7 @@ public function parse(GeneratorConfigTransfer $generatorConfigTransfer): Transfe ->setName((string)$propertyElement['name']) ->setDescription(isset($propertyElement['description']) ? (string)$propertyElement['description'] : null) ->setSingular(isset($propertyElement['singular']) ? (string)$propertyElement['singular'] : null) - ->setIsNullable(isset($propertyElement['isNullable']) && ((string)$propertyElement['isNullable'] === 'true')) - ->setIsIdentifier(isset($propertyElement['isIdentifier']) && ((string)$propertyElement['isIdentifier'] === 'true')) - ->setIsSensitive(isset($propertyElement['isSensitive']) && ((string)$propertyElement['isSensitive'] === 'true')); + ->setIsNullable(isset($propertyElement['isNullable']) && ((string)$propertyElement['isNullable'] === 'true')); $property = $this->propertyTypeMapper->addTypes($generatorConfigTransfer, $property, (string)$propertyElement['type']); diff --git a/src/Transfer/PropertyTransfer.php b/src/Transfer/PropertyTransfer.php index 0624df7..00c92c6 100644 --- a/src/Transfer/PropertyTransfer.php +++ b/src/Transfer/PropertyTransfer.php @@ -8,14 +8,13 @@ class PropertyTransfer { protected string $name; protected ?string $singular = null; + protected string $type; protected ?string $singularType = null; protected string $annotationType; protected ?string $singularAnnotationType = null; protected ?string $description = null; protected bool $isNullable = false; - protected bool $isIdentifier = false; - protected bool $isSensitive = false; /** * @return string @@ -160,40 +159,4 @@ public function setIsNullable(bool $isNullable): PropertyTransfer $this->isNullable = $isNullable; return $this; } - - /** - * @return bool - */ - public function isIdentifier(): bool - { - return $this->isIdentifier; - } - - /** - * @param bool $isIdentifier - * @return PropertyTransfer - */ - public function setIsIdentifier(bool $isIdentifier): PropertyTransfer - { - $this->isIdentifier = $isIdentifier; - return $this; - } - - /** - * @return bool - */ - public function isSensitive(): bool - { - return $this->isSensitive; - } - - /** - * @param bool $isSensitive - * @return PropertyTransfer - */ - public function setIsSensitive(bool $isSensitive): PropertyTransfer - { - $this->isSensitive = $isSensitive; - return $this; - } } \ No newline at end of file diff --git a/src/Transfer/TransferTransfer.php b/src/Transfer/TransferTransfer.php index 11a7e7e..524785e 100644 --- a/src/Transfer/TransferTransfer.php +++ b/src/Transfer/TransferTransfer.php @@ -10,8 +10,6 @@ class TransferTransfer { protected string $name; - protected string $type; - /** * @var ArrayObject */ @@ -44,24 +42,6 @@ public function setName(string $name): TransferTransfer return $this; } - /** - * @return string - */ - public function getType(): string - { - return $this->type; - } - - /** - * @param string $type - * @return TransferTransfer - */ - public function setType(string $type): TransferTransfer - { - $this->type = $type; - return $this; - } - /** * @return ArrayObject */ diff --git a/tests/Data/User/Transfers/user.xml b/tests/Data/User/Transfers/user.xml index e6903cc..e3ca05f 100644 --- a/tests/Data/User/Transfers/user.xml +++ b/tests/Data/User/Transfers/user.xml @@ -2,10 +2,9 @@ - - + + - diff --git a/tests/Service/TransferServiceTest.php b/tests/Service/TransferServiceTest.php index 190c9c2..f579a60 100644 --- a/tests/Service/TransferServiceTest.php +++ b/tests/Service/TransferServiceTest.php @@ -4,13 +4,6 @@ namespace PhilippHermes\TransferBundle\Tests\Service; -use PhilippHermes\TransferBundle\Service\Model\Generate\ClassGenerator; -use PhilippHermes\TransferBundle\Service\Model\Generate\GetterGenerator; -use PhilippHermes\TransferBundle\Service\Model\Generate\PropertyGenerator; -use PhilippHermes\TransferBundle\Service\Model\Generate\SetterGenerator; -use PhilippHermes\TransferBundle\Service\Model\Generate\UserGenerator; -use PhilippHermes\TransferBundle\Service\Model\Generator\Helper\GeneratorHelper; -use PhilippHermes\TransferBundle\Service\Model\TransferGenerator; use PhilippHermes\TransferBundle\Service\TransferService; use PhilippHermes\TransferBundle\Service\TransferServiceFactory; use PhilippHermes\TransferBundle\Service\TransferServiceInterface; @@ -39,7 +32,6 @@ public function setUp(): void protected function tearDown(): void { parent::tearDown(); - return; unlink(__DIR__ . '/../Data/Generated/AddressTransfer.php'); unlink(__DIR__ . '/../Data/Generated/UserTransfer.php'); @@ -93,28 +85,15 @@ public function testGenerate(): void $user->setEmail('test@example.com'); self::assertSame('test@example.com', $user->getEmail()); - self::assertSame('test@example.com', $user->getUserIdentifier()); $user->setPassword('password'); self::assertSame('password', $user->getPassword()); - self::assertNull($user->getPlainPassword()); - $user->setPlainPassword('password'); - self::assertSame('password', $user->getPlainPassword()); - $user->eraseCredentials(); - self::assertNull($user->getPlainPassword()); - $user->setAddresses(new \ArrayObject([$address])); self::assertSame('test', $user->getAddresses()->offsetGet(0)->getStreet()); $user->addAddress($address); self::assertCount(2, $user->getAddresses()); - $user->setRoles(['ROLE_USER']); - self::assertSame(['ROLE_USER'], $user->getRoles()); - - $user->addRole('ROLE_ADMIN'); - self::assertSame(['ROLE_USER', 'ROLE_ADMIN'], $user->getRoles()); - $foo->setBar([ 'foo' => 'bar', ]); @@ -123,7 +102,7 @@ public function testGenerate(): void $foo->addBar('baaaar'); self::assertSame('baaaar', reset($foo->getBar())); - //$this->transferService->clean($config); + $this->transferService->clean($config); self::assertFileDoesNotExist(__DIR__ . '/../Data/Generated/AddressTransfer.php'); self::assertFileDoesNotExist(__DIR__ . '/../Data/Generated/UserTransfer.php'); From 435db86c59ff71c828f82859956c472640c24267 Mon Sep 17 00:00:00 2001 From: Philipp Hermes Date: Thu, 18 Dec 2025 12:59:41 +0100 Subject: [PATCH 15/16] downgrade to symfony 7.4 --- composer.json | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index f26d67e..b0a3980 100644 --- a/composer.json +++ b/composer.json @@ -21,15 +21,15 @@ } }, "require": { - "php": ">=8.4", + "php": ">=8.3", "ext-simplexml": "*", "nette/php-generator": "^v4.2", - "symfony/config": "^8.0", - "symfony/console": "^8.0", - "symfony/dependency-injection": "^8.0", - "symfony/filesystem": "^8.0", - "symfony/finder": "^8.0", - "symfony/framework-bundle": "^8.0" + "symfony/config": "^7.4", + "symfony/console": "^7.4", + "symfony/dependency-injection": "^7.4", + "symfony/filesystem": "^7.4", + "symfony/finder": "^7.4", + "symfony/framework-bundle": "^7.4" }, "require-dev": { "phpstan/phpstan": "^2.1", @@ -37,7 +37,6 @@ "zircote/swagger-php": "^5.7" }, "suggest": { - "symfony/security-bundle": "^8.0", "zircote/swagger-php": "^5.7" }, "config": { From 2731ce5d8f70170a93d20c2be7369119047d99dd Mon Sep 17 00:00:00 2001 From: Philipp Hermes Date: Thu, 18 Dec 2025 13:52:53 +0100 Subject: [PATCH 16/16] update readme --- .github/workflows/ci.yml | 21 +++++-- .gitignore | 3 +- LICENSE => LICENSE.md | 0 README.md | 128 +++++++++++++++++++++++++++++++++++++-- 4 files changed, 141 insertions(+), 11 deletions(-) rename LICENSE => LICENSE.md (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13beea7..373abb5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,10 +10,10 @@ permissions: contents: read jobs: - codecheck: + phpstan: runs-on: ubuntu-latest steps: - - uses: shivammathur/setup-php@2cb9b829437ee246e9b3cac53555a39208ca6d28 + - uses: shivammathur/setup-php@v2 with: php-version: '8.3' - uses: actions/checkout@v3 @@ -31,13 +31,16 @@ jobs: run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist - name: PHPStan - run: composer phpstan + run: vendor/bin/phpstan analyse --memory-limit=1G + test: + needs: [phpstan] runs-on: ubuntu-latest steps: - - uses: shivammathur/setup-php@2cb9b829437ee246e9b3cac53555a39208ca6d28 + - uses: shivammathur/setup-php@v2 with: php-version: '8.3' + coverage: xdebug - uses: actions/checkout@v3 - name: Cache Composer packages @@ -53,4 +56,12 @@ jobs: run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist - name: PHPUnit - run: composer test \ No newline at end of file + run: vendor/bin/phpunit --coverage-html coverage-report + + - name: Upload Coverage Report + uses: actions/upload-artifact@v4 + if: always() + with: + name: coverage-report + path: coverage-report/ + retention-days: 30 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 38eb219..55809c8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ vendor/ .phpunit.cache/ tests/data/Generated composer.lock -.DS_Store \ No newline at end of file +.DS_Store +coverage-report \ No newline at end of file diff --git a/LICENSE b/LICENSE.md similarity index 100% rename from LICENSE rename to LICENSE.md diff --git a/README.md b/README.md index 163d988..6e83f0e 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,18 @@ # Transfer Bundle [![CI](https://github.com/philipphermes/transfer-bundle/actions/workflows/ci.yml/badge.svg)](https://github.com/philipphermes/transfer-bundle/actions/workflows/ci.yml) -[![PHP](https://img.shields.io/badge/php-%3E%3D%208.4-8892BF.svg)]((https://img.shields.io/badge/php-%3E%3D%208.4-8892BF.svg)) -[![Symfony](https://img.shields.io/badge/symfony-8-8892BF.svg)]((https://img.shields.io/badge/symfony-8.0-8892BF.svg)) +[![PHP](https://img.shields.io/badge/php-%3E%3D%208.3-8892BF.svg)]((https://img.shields.io/badge/php-%3E%3D%208.3-8892BF.svg)) +[![Symfony](https://img.shields.io/badge/symfony-%3E%3D%207.4-8892BF.svg)]((https://img.shields.io/badge/symfony-%3E%3D%207.4-8892BF.svg)) + +## Table of Contents + +1. [Installation](#installation) + 1. [configuration](#configuration) + 2. [openApi](#openapi) +2. [Code Quality](#code-quality) + 2. [phpstan](#phpstan) +3. [Test](#test) + 1. [phpunit](#phpunit) ## Installation @@ -21,14 +31,16 @@ return [ ``` #### Optional Configs + * `transfer.namespace`: `App\\Generated\\Transfers` * `transfer.schema_dir`: `%kernel.project_dir%/transfers` * `transfer.output_dir`: `%kernel.project_dir%/src/Generated/Transfers` ### Define Transfers + * you can create multiple files * if multiple files have the same transfer they will be merged - * if you define the same property twice the first on it gets is taken + * if you define the same property twice the first on it gets is taken ```xml @@ -38,7 +50,8 @@ return [ - + @@ -48,7 +61,112 @@ return [ ``` -### Run +### OpenAPI + +> [!CAUTION] +> This currently does not work with Symfony 8 + +you can add `api="true"` to transfers to add attributes and a ref automatically. +child transfers won't get it automatically. + +```xml + + + + + + + + + + + + + + + + + + + + + + + +``` + +then you can use it in api routes for example like this: + +```php +userFacade->getUserById($id); + + return $this->json($user); + } +} +``` + +## Generate transfers + ```shell symfony console transfer:generate +``` + +## Code Quality + +### Phpstan + +```bash +vendor/bin/phpstan analyse --memory-limit=1G +``` + +## Test + +### Phpunit + +```bash +vendor/bin/phpunit + +# With coverage +XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html coverage-report ``` \ No newline at end of file