Skip to content

Commit 45b9e8f

Browse files
committed
Add JSON format option for orm:mapping:describe command output
1 parent a1fdc6e commit 45b9e8f

File tree

3 files changed

+136
-7
lines changed

3 files changed

+136
-7
lines changed

src/Tools/Console/Command/MappingDescribeCommand.php

Lines changed: 99 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Doctrine\ORM\Mapping\FieldMapping;
1111
use Doctrine\Persistence\Mapping\MappingException;
1212
use InvalidArgumentException;
13+
use JsonException;
1314
use Symfony\Component\Console\Completion\CompletionInput;
1415
use Symfony\Component\Console\Completion\CompletionSuggestions;
1516
use Symfony\Component\Console\Input\InputArgument;
@@ -52,27 +53,44 @@ final class MappingDescribeCommand extends AbstractEntityManagerCommand
5253
protected function configure(): void
5354
{
5455
$this->setName('orm:mapping:describe')
55-
->addArgument('entityName', InputArgument::REQUIRED, 'Full or partial name of entity')
56-
->setDescription('Display information about mapped objects')
57-
->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on')
58-
->setHelp(<<<'EOT'
56+
->addArgument('entityName', InputArgument::REQUIRED, 'Full or partial name of entity')
57+
->setDescription('Display information about mapped objects')
58+
->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on')
59+
->addOption(
60+
'format',
61+
null,
62+
InputOption::VALUE_REQUIRED,
63+
'Output format (text, json)',
64+
'text',
65+
array_map(static fn (MappingDescribeCommandFormat $format) => $format->value, MappingDescribeCommandFormat::cases()),
66+
)
67+
->setHelp(<<<'EOT'
5968
The %command.full_name% command describes the metadata for the given full or partial entity class name.
6069
6170
<info>%command.full_name%</info> My\Namespace\Entity\MyEntity
6271
6372
Or:
6473
6574
<info>%command.full_name%</info> MyEntity
75+
76+
To output the metadata in JSON format, use the <info>--format</info> option:
77+
78+
<info>%command.full_name% My\Namespace\Entity\MyEntity --format=json</info>
79+
80+
To use a specific entity manager (e.g., for multi-DB projects), use the <info>--em</info> option:
81+
82+
<info>%command.full_name% My\Namespace\Entity\MyEntity --em=my_custom_entity_manager</info>
83+
6684
EOT);
6785
}
6886

6987
protected function execute(InputInterface $input, OutputInterface $output): int
7088
{
71-
$ui = new SymfonyStyle($input, $output);
72-
89+
$ui = new SymfonyStyle($input, $output);
7390
$entityManager = $this->getEntityManager($input);
91+
$format = MappingDescribeCommandFormat::from($input->getOption('format'));
7492

75-
$this->displayEntity($input->getArgument('entityName'), $entityManager, $ui);
93+
$this->displayEntity($input->getArgument('entityName'), $entityManager, $ui, $format);
7694

7795
return 0;
7896
}
@@ -89,6 +107,10 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti
89107

90108
$suggestions->suggestValues(array_values($entities));
91109
}
110+
111+
if ($input->mustSuggestOptionValuesFor('format')) {
112+
$suggestions->suggestValues(array_map(static fn (MappingDescribeCommandFormat $format) => $format->value, MappingDescribeCommandFormat::cases()));
113+
}
92114
}
93115

94116
/**
@@ -100,9 +122,47 @@ private function displayEntity(
100122
string $entityName,
101123
EntityManagerInterface $entityManager,
102124
SymfonyStyle $ui,
125+
MappingDescribeCommandFormat $format,
103126
): void {
104127
$metadata = $this->getClassMetadata($entityName, $entityManager);
105128

129+
if ($format === MappingDescribeCommandFormat::JSON) {
130+
$ui->text(json_encode(
131+
[
132+
'name' => $metadata->name,
133+
'rootEntityName' => $metadata->rootEntityName,
134+
'customGeneratorDefinition' => $this->formatValueToJson($metadata->customGeneratorDefinition),
135+
'customRepositoryClassName' => $metadata->customRepositoryClassName,
136+
'isMappedSuperclass' => $metadata->isMappedSuperclass,
137+
'isEmbeddedClass' => $metadata->isEmbeddedClass,
138+
'parentClasses' => $metadata->parentClasses,
139+
'subClasses' => $metadata->subClasses,
140+
'embeddedClasses' => $metadata->embeddedClasses,
141+
'identifier' => $metadata->identifier,
142+
'inheritanceType' => $metadata->inheritanceType,
143+
'discriminatorColumn' => $this->formatValueToJson($metadata->discriminatorColumn),
144+
'discriminatorValue' => $metadata->discriminatorValue,
145+
'discriminatorMap' => $metadata->discriminatorMap,
146+
'generatorType' => $metadata->generatorType,
147+
'table' => $this->formatValueToJson($metadata->table),
148+
'isIdentifierComposite' => $metadata->isIdentifierComposite,
149+
'containsForeignIdentifier' => $metadata->containsForeignIdentifier,
150+
'containsEnumIdentifier' => $metadata->containsEnumIdentifier,
151+
'sequenceGeneratorDefinition' => $this->formatValueToJson($metadata->sequenceGeneratorDefinition),
152+
'changeTrackingPolicy' => $metadata->changeTrackingPolicy,
153+
'isVersioned' => $metadata->isVersioned,
154+
'versionField' => $metadata->versionField,
155+
'isReadOnly' => $metadata->isReadOnly,
156+
'entityListeners' => $metadata->entityListeners,
157+
'associationMappings' => $this->formatMappingsToJson($metadata->associationMappings),
158+
'fieldMappings' => $this->formatMappingsToJson($metadata->fieldMappings),
159+
],
160+
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR,
161+
));
162+
163+
return;
164+
}
165+
106166
$ui->table(
107167
['Field', 'Value'],
108168
array_merge(
@@ -240,6 +300,22 @@ private function formatValue(mixed $value): string
240300
throw new InvalidArgumentException(sprintf('Do not know how to format value "%s"', print_r($value, true)));
241301
}
242302

303+
/** @throws JsonException */
304+
private function formatValueToJson(mixed $value): mixed
305+
{
306+
if (is_object($value)) {
307+
$value = (array) $value;
308+
}
309+
310+
if (is_array($value)) {
311+
foreach ($value as $k => $v) {
312+
$value[$k] = $this->formatValueToJson($v);
313+
}
314+
}
315+
316+
return $value;
317+
}
318+
243319
/**
244320
* Add the given label and value to the two column table output
245321
*
@@ -281,6 +357,22 @@ private function formatMappings(array $propertyMappings): array
281357
return $output;
282358
}
283359

360+
/**
361+
* @param array<string, FieldMapping|AssociationMapping> $propertyMappings
362+
*
363+
* @return array<string, mixed>
364+
*/
365+
private function formatMappingsToJson(array $propertyMappings): array
366+
{
367+
$output = [];
368+
369+
foreach ($propertyMappings as $propertyName => $mapping) {
370+
$output[$propertyName] = $this->formatValueToJson((array) $mapping);
371+
}
372+
373+
return $output;
374+
}
375+
284376
/**
285377
* Format the entity listeners
286378
*
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\ORM\Tools\Console\Command;
6+
7+
enum MappingDescribeCommandFormat: string
8+
{
9+
case TEXT = 'text';
10+
case JSON = 'json';
11+
}

tests/Tests/ORM/Tools/Console/Command/MappingDescribeCommandTest.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
use Symfony\Component\Console\Tester\CommandCompletionTester;
1717
use Symfony\Component\Console\Tester\CommandTester;
1818

19+
use function json_decode;
20+
1921
/**
2022
* Tests for {@see \Doctrine\ORM\Tools\Console\Command\MappingDescribeCommand}
2123
*/
@@ -56,6 +58,25 @@ public function testShowSpecificFuzzySingle(): void
5658
self::assertStringContainsString('Root entity name', $display);
5759
}
5860

61+
public function testShowSpecificFuzzySingleJson(): void
62+
{
63+
$this->tester->execute([
64+
'command' => $this->command->getName(),
65+
'entityName' => 'AttractionInfo',
66+
'--format' => 'json',
67+
]);
68+
69+
$display = $this->tester->getDisplay();
70+
$decodedJson = json_decode($display, true);
71+
72+
self::assertJson($display);
73+
self::assertSame(AttractionInfo::class, $decodedJson['name']);
74+
self::assertArrayHasKey('rootEntityName', $decodedJson);
75+
self::assertArrayHasKey('fieldMappings', $decodedJson);
76+
self::assertArrayHasKey('associationMappings', $decodedJson);
77+
self::assertArrayHasKey('id', $decodedJson['fieldMappings']);
78+
}
79+
5980
public function testShowSpecificFuzzyAmbiguous(): void
6081
{
6182
$this->expectException(InvalidArgumentException::class);
@@ -111,5 +132,10 @@ public static function provideCompletionSuggestions(): iterable
111132
'Doctrine\\\\Tests\\\\Models\\\\Cache\\\\Bar',
112133
],
113134
];
135+
136+
yield 'format option value' => [
137+
['--format='],
138+
['text', 'json'],
139+
];
114140
}
115141
}

0 commit comments

Comments
 (0)