From e10fea16ca9893303dfcba20331bc60ff6593b74 Mon Sep 17 00:00:00 2001 From: Jakov Knezovic Date: Wed, 17 Dec 2025 17:41:10 +0100 Subject: [PATCH 1/9] NGSTACK-1017 add iri templates service --- src/Service/IriTemplatesService.php | 75 +++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/Service/IriTemplatesService.php diff --git a/src/Service/IriTemplatesService.php b/src/Service/IriTemplatesService.php new file mode 100644 index 0000000..affd9b9 --- /dev/null +++ b/src/Service/IriTemplatesService.php @@ -0,0 +1,75 @@ + + */ + public function getIriTemplatesData(): array + { + $resourceClasses = $this->resourceExtractor->create(); + $routeCollection = $this->router->getRouteCollection(); + $iriTemplates = []; + + foreach ($resourceClasses as $class) { + /** @var ApiResource $resourceMetadata */ + foreach ($this->resourceMetadataCollectionFactory->create($class) as $resourceMetadata) { + /** @var Operations $operations */ + $operations = $resourceMetadata->getOperations(); + + foreach ($operations as $operation) { + if (!$operation instanceof Get) { + continue; + } + + /** @var string $operationName */ + $operationName = $operation->getName(); + $route = $routeCollection->get($operationName); + + if (!$route instanceof Route) { + continue; + } + + $iriTemplates[$resourceMetadata->getShortName()] = $this->sanitizePath($route->getPath()); + + break; + } + } + } + + return $iriTemplates; + } + + private function sanitizePath(string $path): string + { + return preg_replace( + '/\.\{_format}$/', + '', + $path, + ) ?? ''; + } +} From 64a780987b376024974d6942e427f5e1601e9758 Mon Sep 17 00:00:00 2001 From: Jakov Knezovic Date: Wed, 17 Dec 2025 17:44:03 +0100 Subject: [PATCH 2/9] NGSTACK-1017 add command for writing generated iri templates to json file --- src/Command/GenerateIriTemplatesCommand.php | 94 +++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 src/Command/GenerateIriTemplatesCommand.php diff --git a/src/Command/GenerateIriTemplatesCommand.php b/src/Command/GenerateIriTemplatesCommand.php new file mode 100644 index 0000000..095d014 --- /dev/null +++ b/src/Command/GenerateIriTemplatesCommand.php @@ -0,0 +1,94 @@ +addArgument( + 'output', + InputArgument::REQUIRED, + 'The output JSON file path', + ) + ->setHelp( + <<<'HELP' + The %command.name% command generates IRI templates from all API Platform resources + and writes them to the specified JSON file. + + php %command.full_name% output.json + php %command.full_name% /path/to/iri-templates.json + HELP + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $outputPath = $input->getArgument('output'); + + try { + $iriTemplates = $this->iriTemplatesService->getIriTemplatesData(); + } catch (\Exception $e) { + $io->error(sprintf('Failed to generate IRI templates: %s', $e->getMessage())); + + return Command::FAILURE; + } + + $content = false; + + try { + $content = json_encode($iriTemplates, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } catch (\JsonException) { + } + + if ($content === false) { + $io->error('Failed to encode IRI templates to JSON'); + + return Command::FAILURE; + } + + try { + $this->filesystem->dumpFile($outputPath, $content); + $io->success(sprintf('IRI templates written to %s', $outputPath)); + $io->info(sprintf('Generated %d IRI templates', count($iriTemplates))); + + return Command::SUCCESS; + } catch (\Exception $e) { + $io->error(sprintf('Failed to write file: %s', $e->getMessage())); + + return Command::FAILURE; + } + } +} From 44338298f839efc861724591b270a7fae28db459 Mon Sep 17 00:00:00 2001 From: Jakov Knezovic Date: Wed, 17 Dec 2025 18:30:57 +0100 Subject: [PATCH 3/9] NGSTACK-1017 add abstract feature compiler pass --- .../CompilerPass/FeatureCompilerPass.php | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/DependencyInjection/CompilerPass/FeatureCompilerPass.php diff --git a/src/DependencyInjection/CompilerPass/FeatureCompilerPass.php b/src/DependencyInjection/CompilerPass/FeatureCompilerPass.php new file mode 100644 index 0000000..6383c4b --- /dev/null +++ b/src/DependencyInjection/CompilerPass/FeatureCompilerPass.php @@ -0,0 +1,40 @@ +getFeatureAlias()); + } + + protected function getFeatureEnabledParameterPath(): string + { + return sprintf('%s%s', $this->getFeatureParameterBasePath(), 'enabled'); + } +} From 693361dc01b4cb279d14dda63d7dff3228e530ff Mon Sep 17 00:00:00 2001 From: Jakov Knezovic Date: Wed, 17 Dec 2025 18:31:31 +0100 Subject: [PATCH 4/9] NGSTACK-1017 add iri templates generator compiler pass --- .../IriTemplateGeneratorCompilerPass.php | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/DependencyInjection/CompilerPass/IriTemplateGeneratorCompilerPass.php diff --git a/src/DependencyInjection/CompilerPass/IriTemplateGeneratorCompilerPass.php b/src/DependencyInjection/CompilerPass/IriTemplateGeneratorCompilerPass.php new file mode 100644 index 0000000..f822aaf --- /dev/null +++ b/src/DependencyInjection/CompilerPass/IriTemplateGeneratorCompilerPass.php @@ -0,0 +1,34 @@ +getParameter($this->getFeatureEnabledParameterPath()) === false) { + return; + } + + $container->setDefinition(IriTemplatesService::class, new Definition(IriTemplatesService::class)) + ->setAutowired(true) + ->setPublic(true); + + $container->setDefinition(GenerateIriTemplatesCommand::class, new Definition(GenerateIriTemplatesCommand::class)) + ->addTag( + 'console.command', + [ + 'name' => 'api-platform-extras:generate-iri-templates', 'description' => 'Generate IRI templates and write them to a JSON file', + ], + ) + ->setAutowired(true) + ->setPublic(true); + } +} From 1840b91a55b7c8706e9cb4b65f21e246236ced33 Mon Sep 17 00:00:00 2001 From: Jakov Knezovic Date: Wed, 17 Dec 2025 18:32:11 +0100 Subject: [PATCH 5/9] NGSTACK-1017 add the compiler pass to extension build --- src/NetgenApiPlatformExtrasBundle.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/NetgenApiPlatformExtrasBundle.php b/src/NetgenApiPlatformExtrasBundle.php index 3d21b34..a17ae90 100644 --- a/src/NetgenApiPlatformExtrasBundle.php +++ b/src/NetgenApiPlatformExtrasBundle.php @@ -4,6 +4,16 @@ namespace Netgen\ApiPlatformExtras; +use Netgen\ApiPlatformExtras\DependencyInjection\CompilerPass\IriTemplateGeneratorCompilerPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; -final class NetgenApiPlatformExtrasBundle extends Bundle {} +final class NetgenApiPlatformExtrasBundle extends Bundle +{ + public function build(ContainerBuilder $container): void + { + $container->addCompilerPass( + new IriTemplateGeneratorCompilerPass(), + ); + } +} From e508ca7e9da3b9c6b293f4fdc38355d51d937d9f Mon Sep 17 00:00:00 2001 From: Jakov Knezovic Date: Wed, 17 Dec 2025 18:38:16 +0100 Subject: [PATCH 6/9] NGSTACK-1017 add breaks --- .../IriTemplateGeneratorCompilerPass.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/DependencyInjection/CompilerPass/IriTemplateGeneratorCompilerPass.php b/src/DependencyInjection/CompilerPass/IriTemplateGeneratorCompilerPass.php index f822aaf..940729c 100644 --- a/src/DependencyInjection/CompilerPass/IriTemplateGeneratorCompilerPass.php +++ b/src/DependencyInjection/CompilerPass/IriTemplateGeneratorCompilerPass.php @@ -17,15 +17,24 @@ public function process(ContainerBuilder $container): void return; } - $container->setDefinition(IriTemplatesService::class, new Definition(IriTemplatesService::class)) + $container + ->setDefinition( + IriTemplatesService::class, + new Definition(IriTemplatesService::class), + ) ->setAutowired(true) ->setPublic(true); - $container->setDefinition(GenerateIriTemplatesCommand::class, new Definition(GenerateIriTemplatesCommand::class)) + $container + ->setDefinition( + GenerateIriTemplatesCommand::class, + new Definition(GenerateIriTemplatesCommand::class), + ) ->addTag( 'console.command', [ - 'name' => 'api-platform-extras:generate-iri-templates', 'description' => 'Generate IRI templates and write them to a JSON file', + 'name' => 'api-platform-extras:generate-iri-templates', + 'description' => 'Generate IRI templates and write them to a JSON file', ], ) ->setAutowired(true) From c4811eefe3a4ae75c8361b09967dc68b04e24a2c Mon Sep 17 00:00:00 2001 From: Jakov Knezovic Date: Thu, 15 Jan 2026 10:51:56 +0100 Subject: [PATCH 7/9] NGSTACK-1017 simplify feature compiler pass logic, remove abstract feature compiler pass --- .../CompilerPass/FeatureCompilerPass.php | 40 ------------------- .../IriTemplateGeneratorCompilerPass.php | 16 +++++--- 2 files changed, 10 insertions(+), 46 deletions(-) delete mode 100644 src/DependencyInjection/CompilerPass/FeatureCompilerPass.php diff --git a/src/DependencyInjection/CompilerPass/FeatureCompilerPass.php b/src/DependencyInjection/CompilerPass/FeatureCompilerPass.php deleted file mode 100644 index 6383c4b..0000000 --- a/src/DependencyInjection/CompilerPass/FeatureCompilerPass.php +++ /dev/null @@ -1,40 +0,0 @@ -getFeatureAlias()); - } - - protected function getFeatureEnabledParameterPath(): string - { - return sprintf('%s%s', $this->getFeatureParameterBasePath(), 'enabled'); - } -} diff --git a/src/DependencyInjection/CompilerPass/IriTemplateGeneratorCompilerPass.php b/src/DependencyInjection/CompilerPass/IriTemplateGeneratorCompilerPass.php index 940729c..79dc990 100644 --- a/src/DependencyInjection/CompilerPass/IriTemplateGeneratorCompilerPass.php +++ b/src/DependencyInjection/CompilerPass/IriTemplateGeneratorCompilerPass.php @@ -6,14 +6,20 @@ use Netgen\ApiPlatformExtras\Command\GenerateIriTemplatesCommand; use Netgen\ApiPlatformExtras\Service\IriTemplatesService; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; -final class IriTemplateGeneratorCompilerPass extends FeatureCompilerPass +final class IriTemplateGeneratorCompilerPass implements CompilerPassInterface { + private const FEATURE_ENABLED_PARAMETER = 'netgen_api_platform_extras.features.iri_template_generator.enabled'; + public function process(ContainerBuilder $container): void { - if ($container->getParameter($this->getFeatureEnabledParameterPath()) === false) { + if ( + !$container->hasParameter(self::FEATURE_ENABLED_PARAMETER) + || $container->getParameter(self::FEATURE_ENABLED_PARAMETER) === false + ) { return; } @@ -22,8 +28,7 @@ public function process(ContainerBuilder $container): void IriTemplatesService::class, new Definition(IriTemplatesService::class), ) - ->setAutowired(true) - ->setPublic(true); + ->setAutowired(true); $container ->setDefinition( @@ -37,7 +42,6 @@ public function process(ContainerBuilder $container): void 'description' => 'Generate IRI templates and write them to a JSON file', ], ) - ->setAutowired(true) - ->setPublic(true); + ->setAutowired(true); } } From a36809a5546a9e4063be5612907be0f70b38fc41 Mon Sep 17 00:00:00 2001 From: Jakov Knezovic Date: Thu, 15 Jan 2026 16:36:30 +0100 Subject: [PATCH 8/9] NGSTACK-1017 codestyle fixes --- src/Command/GenerateIriTemplatesCommand.php | 16 +++++++------- .../IriTemplateGeneratorCompilerPass.php | 20 +++++++++++------- src/Service/IriTemplatesService.php | 21 +++++++++++-------- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/Command/GenerateIriTemplatesCommand.php b/src/Command/GenerateIriTemplatesCommand.php index 095d014..9781c47 100644 --- a/src/Command/GenerateIriTemplatesCommand.php +++ b/src/Command/GenerateIriTemplatesCommand.php @@ -4,8 +4,9 @@ namespace Netgen\ApiPlatformExtras\Command; +use Exception; +use JsonException; use Netgen\ApiPlatformExtras\Service\IriTemplatesService; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -21,17 +22,13 @@ use const JSON_THROW_ON_ERROR; use const JSON_UNESCAPED_SLASHES; -#[AsCommand( - name: 'api-platform-extras:generate-iri-templates', - description: 'Generate IRI templates and write them to a JSON file', -)] final class GenerateIriTemplatesCommand extends Command { public function __construct( private readonly IriTemplatesService $iriTemplatesService, private readonly Filesystem $filesystem, ) { - parent::__construct(); + parent::__construct('netgen:api-platform-extras:generate-iri-templates'); } protected function configure(): void @@ -42,6 +39,7 @@ protected function configure(): void InputArgument::REQUIRED, 'The output JSON file path', ) + ->setDescription('Generate IRI templates and write them to a JSON file') ->setHelp( <<<'HELP' The %command.name% command generates IRI templates from all API Platform resources @@ -60,7 +58,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int try { $iriTemplates = $this->iriTemplatesService->getIriTemplatesData(); - } catch (\Exception $e) { + } catch (Exception $e) { $io->error(sprintf('Failed to generate IRI templates: %s', $e->getMessage())); return Command::FAILURE; @@ -70,7 +68,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int try { $content = json_encode($iriTemplates, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); - } catch (\JsonException) { + } catch (JsonException) { } if ($content === false) { @@ -85,7 +83,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io->info(sprintf('Generated %d IRI templates', count($iriTemplates))); return Command::SUCCESS; - } catch (\Exception $e) { + } catch (Exception $e) { $io->error(sprintf('Failed to write file: %s', $e->getMessage())); return Command::FAILURE; diff --git a/src/DependencyInjection/CompilerPass/IriTemplateGeneratorCompilerPass.php b/src/DependencyInjection/CompilerPass/IriTemplateGeneratorCompilerPass.php index 79dc990..4a3307c 100644 --- a/src/DependencyInjection/CompilerPass/IriTemplateGeneratorCompilerPass.php +++ b/src/DependencyInjection/CompilerPass/IriTemplateGeneratorCompilerPass.php @@ -9,10 +9,11 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; final class IriTemplateGeneratorCompilerPass implements CompilerPassInterface { - private const FEATURE_ENABLED_PARAMETER = 'netgen_api_platform_extras.features.iri_template_generator.enabled'; + private const string FEATURE_ENABLED_PARAMETER = 'netgen_api_platform_extras.features.iri_template_generator.enabled'; public function process(ContainerBuilder $container): void { @@ -28,20 +29,23 @@ public function process(ContainerBuilder $container): void IriTemplatesService::class, new Definition(IriTemplatesService::class), ) - ->setAutowired(true); + ->setArguments([ + new Reference('api_platform.metadata.resource.metadata_collection_factory.cached'), + new Reference('api_platform.metadata.resource.name_collection_factory.cached'), + new Reference('router'), + ]); $container ->setDefinition( GenerateIriTemplatesCommand::class, new Definition(GenerateIriTemplatesCommand::class), ) - ->addTag( - 'console.command', + ->addTag('console.command') + ->setArguments( [ - 'name' => 'api-platform-extras:generate-iri-templates', - 'description' => 'Generate IRI templates and write them to a JSON file', + new Reference(IriTemplatesService::class), + new Reference('filesystem'), ], - ) - ->setAutowired(true); + ); } } diff --git a/src/Service/IriTemplatesService.php b/src/Service/IriTemplatesService.php index affd9b9..9ec6d20 100644 --- a/src/Service/IriTemplatesService.php +++ b/src/Service/IriTemplatesService.php @@ -4,7 +4,6 @@ namespace Netgen\ApiPlatformExtras\Service; -use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Exception\ResourceClassNotFoundException; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\HttpOperation; @@ -16,17 +15,15 @@ use function preg_replace; -final readonly class IriTemplatesService +final class IriTemplatesService { public function __construct( - private ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, - private ResourceNameCollectionFactoryInterface $resourceExtractor, - private RouterInterface $router, + private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, + private readonly ResourceNameCollectionFactoryInterface $resourceExtractor, + private readonly RouterInterface $router, ) {} /** - * @throws ResourceClassNotFoundException - * * @return array */ public function getIriTemplatesData(): array @@ -36,8 +33,14 @@ public function getIriTemplatesData(): array $iriTemplates = []; foreach ($resourceClasses as $class) { - /** @var ApiResource $resourceMetadata */ - foreach ($this->resourceMetadataCollectionFactory->create($class) as $resourceMetadata) { + try { + $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($class); + } catch (ResourceClassNotFoundException) { + continue; + } + + /** @var \ApiPlatform\Metadata\ApiResource $resourceMetadata */ + foreach ($resourceMetadataCollection as $resourceMetadata) { /** @var Operations $operations */ $operations = $resourceMetadata->getOperations(); From 6cb12a4709fb12db7232f069f20e7e894e68bdd0 Mon Sep 17 00:00:00 2001 From: Jakov Knezovic Date: Mon, 19 Jan 2026 10:37:30 +0100 Subject: [PATCH 9/9] NGSTACK-1017 remove readonly from class properties --- src/Command/GenerateIriTemplatesCommand.php | 4 ++-- src/Service/IriTemplatesService.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Command/GenerateIriTemplatesCommand.php b/src/Command/GenerateIriTemplatesCommand.php index 9781c47..3fbcc71 100644 --- a/src/Command/GenerateIriTemplatesCommand.php +++ b/src/Command/GenerateIriTemplatesCommand.php @@ -25,8 +25,8 @@ final class GenerateIriTemplatesCommand extends Command { public function __construct( - private readonly IriTemplatesService $iriTemplatesService, - private readonly Filesystem $filesystem, + private IriTemplatesService $iriTemplatesService, + private Filesystem $filesystem, ) { parent::__construct('netgen:api-platform-extras:generate-iri-templates'); } diff --git a/src/Service/IriTemplatesService.php b/src/Service/IriTemplatesService.php index 9ec6d20..750f9da 100644 --- a/src/Service/IriTemplatesService.php +++ b/src/Service/IriTemplatesService.php @@ -18,9 +18,9 @@ final class IriTemplatesService { public function __construct( - private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, - private readonly ResourceNameCollectionFactoryInterface $resourceExtractor, - private readonly RouterInterface $router, + private ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, + private ResourceNameCollectionFactoryInterface $resourceExtractor, + private RouterInterface $router, ) {} /**