Skip to content

Commit a5a236c

Browse files
authored
Merge pull request #2 from netgen/NGSTACK-1017-iri-template-generation
Ngstack 1017 iri template generation feature
2 parents d14b4d3 + 6cb12a4 commit a5a236c

4 files changed

Lines changed: 232 additions & 1 deletion

File tree

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Netgen\ApiPlatformExtras\Command;
6+
7+
use Exception;
8+
use JsonException;
9+
use Netgen\ApiPlatformExtras\Service\IriTemplatesService;
10+
use Symfony\Component\Console\Command\Command;
11+
use Symfony\Component\Console\Input\InputArgument;
12+
use Symfony\Component\Console\Input\InputInterface;
13+
use Symfony\Component\Console\Output\OutputInterface;
14+
use Symfony\Component\Console\Style\SymfonyStyle;
15+
use Symfony\Component\Filesystem\Filesystem;
16+
17+
use function count;
18+
use function json_encode;
19+
use function sprintf;
20+
21+
use const JSON_PRETTY_PRINT;
22+
use const JSON_THROW_ON_ERROR;
23+
use const JSON_UNESCAPED_SLASHES;
24+
25+
final class GenerateIriTemplatesCommand extends Command
26+
{
27+
public function __construct(
28+
private IriTemplatesService $iriTemplatesService,
29+
private Filesystem $filesystem,
30+
) {
31+
parent::__construct('netgen:api-platform-extras:generate-iri-templates');
32+
}
33+
34+
protected function configure(): void
35+
{
36+
$this
37+
->addArgument(
38+
'output',
39+
InputArgument::REQUIRED,
40+
'The output JSON file path',
41+
)
42+
->setDescription('Generate IRI templates and write them to a JSON file')
43+
->setHelp(
44+
<<<'HELP'
45+
The <info>%command.name%</info> command generates IRI templates from all API Platform resources
46+
and writes them to the specified JSON file.
47+
48+
<info>php %command.full_name% output.json</info>
49+
<info>php %command.full_name% /path/to/iri-templates.json</info>
50+
HELP
51+
);
52+
}
53+
54+
protected function execute(InputInterface $input, OutputInterface $output): int
55+
{
56+
$io = new SymfonyStyle($input, $output);
57+
$outputPath = $input->getArgument('output');
58+
59+
try {
60+
$iriTemplates = $this->iriTemplatesService->getIriTemplatesData();
61+
} catch (Exception $e) {
62+
$io->error(sprintf('Failed to generate IRI templates: %s', $e->getMessage()));
63+
64+
return Command::FAILURE;
65+
}
66+
67+
$content = false;
68+
69+
try {
70+
$content = json_encode($iriTemplates, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
71+
} catch (JsonException) {
72+
}
73+
74+
if ($content === false) {
75+
$io->error('Failed to encode IRI templates to JSON');
76+
77+
return Command::FAILURE;
78+
}
79+
80+
try {
81+
$this->filesystem->dumpFile($outputPath, $content);
82+
$io->success(sprintf('IRI templates written to %s', $outputPath));
83+
$io->info(sprintf('Generated %d IRI templates', count($iriTemplates)));
84+
85+
return Command::SUCCESS;
86+
} catch (Exception $e) {
87+
$io->error(sprintf('Failed to write file: %s', $e->getMessage()));
88+
89+
return Command::FAILURE;
90+
}
91+
}
92+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Netgen\ApiPlatformExtras\DependencyInjection\CompilerPass;
6+
7+
use Netgen\ApiPlatformExtras\Command\GenerateIriTemplatesCommand;
8+
use Netgen\ApiPlatformExtras\Service\IriTemplatesService;
9+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
10+
use Symfony\Component\DependencyInjection\ContainerBuilder;
11+
use Symfony\Component\DependencyInjection\Definition;
12+
use Symfony\Component\DependencyInjection\Reference;
13+
14+
final class IriTemplateGeneratorCompilerPass implements CompilerPassInterface
15+
{
16+
private const string FEATURE_ENABLED_PARAMETER = 'netgen_api_platform_extras.features.iri_template_generator.enabled';
17+
18+
public function process(ContainerBuilder $container): void
19+
{
20+
if (
21+
!$container->hasParameter(self::FEATURE_ENABLED_PARAMETER)
22+
|| $container->getParameter(self::FEATURE_ENABLED_PARAMETER) === false
23+
) {
24+
return;
25+
}
26+
27+
$container
28+
->setDefinition(
29+
IriTemplatesService::class,
30+
new Definition(IriTemplatesService::class),
31+
)
32+
->setArguments([
33+
new Reference('api_platform.metadata.resource.metadata_collection_factory.cached'),
34+
new Reference('api_platform.metadata.resource.name_collection_factory.cached'),
35+
new Reference('router'),
36+
]);
37+
38+
$container
39+
->setDefinition(
40+
GenerateIriTemplatesCommand::class,
41+
new Definition(GenerateIriTemplatesCommand::class),
42+
)
43+
->addTag('console.command')
44+
->setArguments(
45+
[
46+
new Reference(IriTemplatesService::class),
47+
new Reference('filesystem'),
48+
],
49+
);
50+
}
51+
}

src/NetgenApiPlatformExtrasBundle.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44

55
namespace Netgen\ApiPlatformExtras;
66

7+
use Netgen\ApiPlatformExtras\DependencyInjection\CompilerPass\IriTemplateGeneratorCompilerPass;
8+
use Symfony\Component\DependencyInjection\ContainerBuilder;
79
use Symfony\Component\HttpKernel\Bundle\Bundle;
810

9-
final class NetgenApiPlatformExtrasBundle extends Bundle {}
11+
final class NetgenApiPlatformExtrasBundle extends Bundle
12+
{
13+
public function build(ContainerBuilder $container): void
14+
{
15+
$container->addCompilerPass(
16+
new IriTemplateGeneratorCompilerPass(),
17+
);
18+
}
19+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Netgen\ApiPlatformExtras\Service;
6+
7+
use ApiPlatform\Metadata\Exception\ResourceClassNotFoundException;
8+
use ApiPlatform\Metadata\Get;
9+
use ApiPlatform\Metadata\HttpOperation;
10+
use ApiPlatform\Metadata\Operations;
11+
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
12+
use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
13+
use Symfony\Component\Routing\Route;
14+
use Symfony\Component\Routing\RouterInterface;
15+
16+
use function preg_replace;
17+
18+
final class IriTemplatesService
19+
{
20+
public function __construct(
21+
private ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory,
22+
private ResourceNameCollectionFactoryInterface $resourceExtractor,
23+
private RouterInterface $router,
24+
) {}
25+
26+
/**
27+
* @return array<string, string>
28+
*/
29+
public function getIriTemplatesData(): array
30+
{
31+
$resourceClasses = $this->resourceExtractor->create();
32+
$routeCollection = $this->router->getRouteCollection();
33+
$iriTemplates = [];
34+
35+
foreach ($resourceClasses as $class) {
36+
try {
37+
$resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($class);
38+
} catch (ResourceClassNotFoundException) {
39+
continue;
40+
}
41+
42+
/** @var \ApiPlatform\Metadata\ApiResource $resourceMetadata */
43+
foreach ($resourceMetadataCollection as $resourceMetadata) {
44+
/** @var Operations<HttpOperation> $operations */
45+
$operations = $resourceMetadata->getOperations();
46+
47+
foreach ($operations as $operation) {
48+
if (!$operation instanceof Get) {
49+
continue;
50+
}
51+
52+
/** @var string $operationName */
53+
$operationName = $operation->getName();
54+
$route = $routeCollection->get($operationName);
55+
56+
if (!$route instanceof Route) {
57+
continue;
58+
}
59+
60+
$iriTemplates[$resourceMetadata->getShortName()] = $this->sanitizePath($route->getPath());
61+
62+
break;
63+
}
64+
}
65+
}
66+
67+
return $iriTemplates;
68+
}
69+
70+
private function sanitizePath(string $path): string
71+
{
72+
return preg_replace(
73+
'/\.\{_format}$/',
74+
'',
75+
$path,
76+
) ?? '';
77+
}
78+
}

0 commit comments

Comments
 (0)