Skip to content

Commit 256d6cb

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

File tree

3 files changed

+130
-4
lines changed

3 files changed

+130
-4
lines changed

src/Tools/Console/Command/MappingDescribeCommand.php

Lines changed: 93 additions & 4 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,9 +53,17 @@ 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')
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+
MappingDescribeCommandFormat::TEXT->value,
65+
array_map(static fn (MappingDescribeCommandFormat $format) => $format->value, MappingDescribeCommandFormat::cases()),
66+
)
5867
->setHelp(<<<'EOT'
5968
The %command.full_name% command describes the metadata for the given full or partial entity class name.
6069
@@ -63,16 +72,22 @@ protected function configure(): void
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+
<info>%command.full_name% My\Namespace\Entity\MyEntity --format=json</info>
78+
6679
EOT);
6780
}
6881

6982
protected function execute(InputInterface $input, OutputInterface $output): int
7083
{
7184
$ui = new SymfonyStyle($input, $output);
7285

86+
$format = MappingDescribeCommandFormat::from($input->getOption('format'));
87+
7388
$entityManager = $this->getEntityManager($input);
7489

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

7792
return 0;
7893
}
@@ -89,6 +104,10 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti
89104

90105
$suggestions->suggestValues(array_values($entities));
91106
}
107+
108+
if ($input->mustSuggestOptionValuesFor('format')) {
109+
$suggestions->suggestValues(array_map(static fn (MappingDescribeCommandFormat $format) => $format->value, MappingDescribeCommandFormat::cases()));
110+
}
92111
}
93112

94113
/**
@@ -100,9 +119,47 @@ private function displayEntity(
100119
string $entityName,
101120
EntityManagerInterface $entityManager,
102121
SymfonyStyle $ui,
122+
MappingDescribeCommandFormat $format,
103123
): void {
104124
$metadata = $this->getClassMetadata($entityName, $entityManager);
105125

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

300+
/** @throws JsonException */
301+
private function formatValueAsJson(mixed $value): mixed
302+
{
303+
if (is_object($value)) {
304+
$value = (array) $value;
305+
}
306+
307+
if (is_array($value)) {
308+
foreach ($value as $k => $v) {
309+
$value[$k] = $this->formatValueAsJson($v);
310+
}
311+
}
312+
313+
return $value;
314+
}
315+
243316
/**
244317
* Add the given label and value to the two column table output
245318
*
@@ -281,6 +354,22 @@ private function formatMappings(array $propertyMappings): array
281354
return $output;
282355
}
283356

357+
/**
358+
* @param array<string, FieldMapping|AssociationMapping> $propertyMappings
359+
*
360+
* @return array<string, mixed>
361+
*/
362+
private function formatMappingsAsJson(array $propertyMappings): array
363+
{
364+
$output = [];
365+
366+
foreach ($propertyMappings as $propertyName => $mapping) {
367+
$output[$propertyName] = $this->formatValueAsJson((array) $mapping);
368+
}
369+
370+
return $output;
371+
}
372+
284373
/**
285374
* Format the entity listeners
286375
*
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)