1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Doctrine\ORM\Tools\Console\Command; |
6
|
|
|
|
7
|
|
|
use Doctrine\Common\Persistence\Mapping\MappingException; |
8
|
|
|
use Doctrine\ORM\EntityManagerInterface; |
9
|
|
|
use Doctrine\ORM\Mapping\AssociationMetadata; |
10
|
|
|
use Doctrine\ORM\Mapping\ClassMetadata; |
11
|
|
|
use Doctrine\ORM\Mapping\ColumnMetadata; |
12
|
|
|
use Doctrine\ORM\Mapping\ComponentMetadata; |
13
|
|
|
use Doctrine\ORM\Mapping\FieldMetadata; |
14
|
|
|
use Doctrine\ORM\Mapping\Property; |
15
|
|
|
use Doctrine\ORM\Mapping\TableMetadata; |
16
|
|
|
use InvalidArgumentException; |
17
|
|
|
use Symfony\Component\Console\Command\Command; |
18
|
|
|
use Symfony\Component\Console\Input\InputArgument; |
19
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
20
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
21
|
|
|
use Symfony\Component\Console\Style\SymfonyStyle; |
22
|
|
|
use const JSON_UNESCAPED_SLASHES; |
23
|
|
|
use const JSON_UNESCAPED_UNICODE; |
24
|
|
|
use function array_filter; |
25
|
|
|
use function array_map; |
26
|
|
|
use function array_merge; |
27
|
|
|
use function count; |
28
|
|
|
use function current; |
29
|
|
|
use function get_class; |
30
|
|
|
use function implode; |
31
|
|
|
use function is_array; |
32
|
|
|
use function is_bool; |
33
|
|
|
use function is_object; |
34
|
|
|
use function is_scalar; |
35
|
|
|
use function json_encode; |
36
|
|
|
use function preg_match; |
37
|
|
|
use function preg_quote; |
38
|
|
|
use function print_r; |
39
|
|
|
use function sprintf; |
40
|
|
|
use function strtolower; |
41
|
|
|
use function ucfirst; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Show information about mapped entities. |
45
|
|
|
*/ |
46
|
|
|
final class MappingDescribeCommand extends Command |
47
|
|
|
{ |
48
|
|
|
/** |
49
|
|
|
* {@inheritdoc} |
50
|
|
|
*/ |
51
|
3 |
|
protected function configure() |
52
|
|
|
{ |
53
|
3 |
|
$this->setName('orm:mapping:describe') |
54
|
3 |
|
->addArgument('entityName', InputArgument::REQUIRED, 'Full or partial name of entity') |
55
|
3 |
|
->setDescription('Display information about mapped objects') |
56
|
3 |
|
->setHelp(<<<'EOT' |
57
|
3 |
|
The %command.full_name% command describes the metadata for the given full or partial entity class name. |
58
|
|
|
|
59
|
|
|
<info>%command.full_name%</info> My\Namespace\Entity\MyEntity |
60
|
|
|
|
61
|
|
|
Or: |
62
|
|
|
|
63
|
|
|
<info>%command.full_name%</info> MyEntity |
64
|
|
|
EOT |
65
|
|
|
); |
66
|
3 |
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* {@inheritdoc} |
70
|
|
|
*/ |
71
|
3 |
|
protected function execute(InputInterface $input, OutputInterface $output) |
72
|
|
|
{ |
73
|
3 |
|
$ui = new SymfonyStyle($input, $output); |
74
|
|
|
|
75
|
|
|
/** @var EntityManagerInterface $entityManager */ |
76
|
3 |
|
$entityManager = $this->getHelper('em')->getEntityManager(); |
77
|
|
|
|
78
|
3 |
|
$this->displayEntity($input->getArgument('entityName'), $entityManager, $ui); |
79
|
|
|
|
80
|
1 |
|
return 0; |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Display all the mapping information for a single Entity. |
85
|
|
|
* |
86
|
|
|
* @param string $entityName Full or partial entity class name |
87
|
|
|
*/ |
88
|
3 |
|
private function displayEntity($entityName, EntityManagerInterface $entityManager, SymfonyStyle $ui) |
89
|
|
|
{ |
90
|
3 |
|
$metadata = $this->getClassMetadata($entityName, $entityManager); |
91
|
1 |
|
$parentValue = $metadata->getParent() === null ? '<comment>None</comment>' : ''; |
92
|
|
|
|
93
|
1 |
|
$ui->table( |
94
|
1 |
|
['Field', 'Value'], |
95
|
1 |
|
array_merge( |
96
|
|
|
[ |
97
|
1 |
|
$this->formatField('Name', $metadata->getClassName()), |
98
|
1 |
|
$this->formatField('Root entity name', $metadata->getRootClassName()), |
99
|
1 |
|
$this->formatField('Custom repository class', $metadata->getCustomRepositoryClassName()), |
100
|
1 |
|
$this->formatField('Mapped super class?', $metadata->isMappedSuperclass), |
101
|
1 |
|
$this->formatField('Embedded class?', $metadata->isEmbeddedClass), |
102
|
1 |
|
$this->formatField('Parent classes', $parentValue), |
103
|
|
|
], |
104
|
1 |
|
$this->formatParentClasses($metadata), |
105
|
|
|
[ |
106
|
1 |
|
$this->formatField('Sub classes', $metadata->getSubClasses()), |
107
|
1 |
|
$this->formatField('Embedded classes', $metadata->getSubClasses()), |
108
|
1 |
|
$this->formatField('Identifier', $metadata->getIdentifier()), |
109
|
1 |
|
$this->formatField('Inheritance type', $metadata->inheritanceType), |
110
|
1 |
|
$this->formatField('Discriminator column', ''), |
111
|
|
|
], |
112
|
1 |
|
$this->formatColumn($metadata->discriminatorColumn), |
113
|
|
|
[ |
114
|
1 |
|
$this->formatField('Discriminator value', $metadata->discriminatorValue), |
115
|
1 |
|
$this->formatField('Discriminator map', $metadata->discriminatorMap), |
116
|
1 |
|
$this->formatField('Table', ''), |
117
|
|
|
], |
118
|
1 |
|
$this->formatTable($metadata->table), |
119
|
|
|
[ |
120
|
1 |
|
$this->formatField('Composite identifier?', $metadata->isIdentifierComposite()), |
121
|
1 |
|
$this->formatField('Change tracking policy', $metadata->changeTrackingPolicy), |
122
|
1 |
|
$this->formatField('Versioned?', $metadata->isVersioned()), |
123
|
1 |
|
$this->formatField('Version field', ($metadata->isVersioned() ? $metadata->versionProperty->getName() : '')), |
124
|
1 |
|
$this->formatField('Read only?', $metadata->isReadOnly()), |
125
|
|
|
|
126
|
1 |
|
$this->formatEntityListeners($metadata->entityListeners), |
127
|
|
|
], |
128
|
1 |
|
[$this->formatField('Property mappings:', '')], |
129
|
1 |
|
$this->formatPropertyMappings($metadata->getDeclaredPropertiesIterator()) |
130
|
|
|
) |
131
|
|
|
); |
132
|
1 |
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Return all mapped entity class names |
136
|
|
|
* |
137
|
|
|
* @return string[] |
138
|
|
|
*/ |
139
|
3 |
|
private function getMappedEntities(EntityManagerInterface $entityManager) |
140
|
|
|
{ |
141
|
3 |
|
$entityClassNames = $entityManager->getConfiguration() |
142
|
3 |
|
->getMetadataDriverImpl() |
143
|
3 |
|
->getAllClassNames(); |
144
|
|
|
|
145
|
3 |
|
if (! $entityClassNames) { |
|
|
|
|
146
|
|
|
throw new InvalidArgumentException( |
147
|
|
|
'You do not have any mapped Doctrine ORM entities according to the current configuration. ' . |
148
|
|
|
'If you have entities or mapping files you should check your mapping configuration for errors.' |
149
|
|
|
); |
150
|
|
|
} |
151
|
|
|
|
152
|
3 |
|
return $entityClassNames; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* Return the class metadata for the given entity |
157
|
|
|
* name |
158
|
|
|
* |
159
|
|
|
* @param string $entityName Full or partial entity name |
160
|
|
|
* |
161
|
|
|
* @return ClassMetadata |
162
|
|
|
*/ |
163
|
3 |
|
private function getClassMetadata($entityName, EntityManagerInterface $entityManager) |
164
|
|
|
{ |
165
|
|
|
try { |
166
|
3 |
|
return $entityManager->getClassMetadata($entityName); |
|
|
|
|
167
|
3 |
|
} catch (MappingException $e) { |
|
|
|
|
168
|
|
|
} |
169
|
|
|
|
170
|
3 |
|
$matches = array_filter( |
171
|
3 |
|
$this->getMappedEntities($entityManager), |
172
|
|
|
static function ($mappedEntity) use ($entityName) { |
173
|
3 |
|
return preg_match('{' . preg_quote($entityName) . '}', $mappedEntity); |
174
|
3 |
|
} |
175
|
|
|
); |
176
|
|
|
|
177
|
3 |
|
if (! $matches) { |
|
|
|
|
178
|
1 |
|
throw new InvalidArgumentException(sprintf( |
179
|
1 |
|
'Could not find any mapped Entity classes matching "%s"', |
180
|
1 |
|
$entityName |
181
|
|
|
)); |
182
|
|
|
} |
183
|
|
|
|
184
|
2 |
|
if (count($matches) > 1) { |
185
|
1 |
|
throw new InvalidArgumentException(sprintf( |
186
|
1 |
|
'Entity name "%s" is ambiguous, possible matches: "%s"', |
187
|
1 |
|
$entityName, |
188
|
1 |
|
implode(', ', $matches) |
189
|
|
|
)); |
190
|
|
|
} |
191
|
|
|
|
192
|
1 |
|
return $entityManager->getClassMetadata(current($matches)); |
|
|
|
|
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* @return string[] |
197
|
|
|
*/ |
198
|
1 |
|
private function formatParentClasses(ComponentMetadata $metadata) |
199
|
|
|
{ |
200
|
1 |
|
$output = []; |
201
|
1 |
|
$parentClass = $metadata; |
202
|
|
|
|
203
|
1 |
|
while (($parentClass = $parentClass->getParent()) !== null) { |
204
|
|
|
/** @var ClassMetadata $parentClass */ |
205
|
|
|
$attributes = []; |
206
|
|
|
|
207
|
|
|
if ($parentClass->isEmbeddedClass) { |
208
|
|
|
$attributes[] = 'Embedded'; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
if ($parentClass->isMappedSuperclass) { |
212
|
|
|
$attributes[] = 'Mapped superclass'; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
if ($parentClass->inheritanceType) { |
216
|
|
|
$attributes[] = ucfirst(strtolower($parentClass->inheritanceType)); |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
if ($parentClass->isReadOnly()) { |
220
|
|
|
$attributes[] = 'Read-only'; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
if ($parentClass->isVersioned()) { |
224
|
|
|
$attributes[] = 'Versioned'; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
$output[] = $this->formatField( |
228
|
|
|
sprintf(' %s', $parentClass->getParent()), |
|
|
|
|
229
|
|
|
($parentClass->isRootEntity() ? '(Root) ' : '') . $this->formatValue($attributes) |
230
|
|
|
); |
231
|
|
|
} |
232
|
|
|
|
233
|
1 |
|
return $output; |
|
|
|
|
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* Format the given value for console output |
238
|
|
|
* |
239
|
|
|
* @param mixed $value |
240
|
|
|
* |
241
|
|
|
* @return string |
242
|
|
|
*/ |
243
|
1 |
|
private function formatValue($value) |
244
|
|
|
{ |
245
|
1 |
|
if ($value === '') { |
246
|
1 |
|
return ''; |
247
|
|
|
} |
248
|
|
|
|
249
|
1 |
|
if ($value === null) { |
250
|
1 |
|
return '<comment>Null</comment>'; |
251
|
|
|
} |
252
|
|
|
|
253
|
1 |
|
if (is_bool($value)) { |
254
|
1 |
|
return '<comment>' . ($value ? 'True' : 'False') . '</comment>'; |
255
|
|
|
} |
256
|
|
|
|
257
|
1 |
|
if (empty($value)) { |
258
|
1 |
|
return '<comment>Empty</comment>'; |
259
|
|
|
} |
260
|
|
|
|
261
|
1 |
|
if (is_array($value)) { |
262
|
1 |
|
return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); |
263
|
|
|
} |
264
|
|
|
|
265
|
1 |
|
if (is_object($value)) { |
266
|
|
|
return sprintf('<%s>', get_class($value)); |
267
|
|
|
} |
268
|
|
|
|
269
|
1 |
|
if (is_scalar($value)) { |
270
|
1 |
|
return $value; |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
throw new InvalidArgumentException(sprintf('Do not know how to format value "%s"', print_r($value, true))); |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* Add the given label and value to the two column table output |
278
|
|
|
* |
279
|
|
|
* @param string $label Label for the value |
280
|
|
|
* @param mixed $value A Value to show |
281
|
|
|
* |
282
|
|
|
* @return string[] |
283
|
|
|
*/ |
284
|
1 |
|
private function formatField($label, $value) |
285
|
|
|
{ |
286
|
1 |
|
if ($value === null) { |
287
|
1 |
|
$value = '<comment>None</comment>'; |
288
|
|
|
} |
289
|
|
|
|
290
|
1 |
|
return [sprintf('<info>%s</info>', $label), $this->formatValue($value)]; |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
/** |
294
|
|
|
* Format the property mappings |
295
|
|
|
* |
296
|
|
|
* @param iterable|Property[] $propertyMappings |
297
|
|
|
* |
298
|
|
|
* @return string[] |
299
|
|
|
*/ |
300
|
1 |
|
private function formatPropertyMappings(iterable $propertyMappings) |
301
|
|
|
{ |
302
|
1 |
|
$output = []; |
303
|
|
|
|
304
|
1 |
|
foreach ($propertyMappings as $propertyName => $property) { |
305
|
1 |
|
$output[] = $this->formatField(sprintf(' %s', $propertyName), ''); |
306
|
|
|
|
307
|
1 |
|
if ($property instanceof FieldMetadata) { |
308
|
1 |
|
$output = array_merge($output, $this->formatColumn($property)); |
309
|
1 |
|
} elseif ($property instanceof AssociationMetadata) { |
310
|
|
|
// @todo guilhermeblanco Fix me! We are trying to iterate through an AssociationMetadata instance |
311
|
1 |
|
foreach ($property as $field => $value) { |
312
|
|
|
$output[] = $this->formatField(sprintf(' %s', $field), $this->formatValue($value)); |
313
|
|
|
} |
314
|
|
|
} |
315
|
|
|
} |
316
|
|
|
|
317
|
1 |
|
return $output; |
|
|
|
|
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
/** |
321
|
|
|
* @return string[] |
322
|
|
|
*/ |
323
|
1 |
|
private function formatColumn(?ColumnMetadata $columnMetadata = null) |
324
|
|
|
{ |
325
|
1 |
|
$output = []; |
326
|
|
|
|
327
|
1 |
|
if ($columnMetadata === null) { |
328
|
|
|
$output[] = '<comment>Null</comment>'; |
329
|
|
|
|
330
|
|
|
return $output; |
331
|
|
|
} |
332
|
|
|
|
333
|
1 |
|
$output[] = $this->formatField(' type', $this->formatValue($columnMetadata->getTypeName())); |
334
|
1 |
|
$output[] = $this->formatField(' tableName', $this->formatValue($columnMetadata->getTableName())); |
335
|
1 |
|
$output[] = $this->formatField(' columnName', $this->formatValue($columnMetadata->getColumnName())); |
336
|
1 |
|
$output[] = $this->formatField(' columnDefinition', $this->formatValue($columnMetadata->getColumnDefinition())); |
337
|
1 |
|
$output[] = $this->formatField(' isPrimaryKey', $this->formatValue($columnMetadata->isPrimaryKey())); |
338
|
1 |
|
$output[] = $this->formatField(' isNullable', $this->formatValue($columnMetadata->isNullable())); |
339
|
1 |
|
$output[] = $this->formatField(' isUnique', $this->formatValue($columnMetadata->isUnique())); |
340
|
1 |
|
$output[] = $this->formatField(' options', $this->formatValue($columnMetadata->getOptions())); |
341
|
|
|
|
342
|
1 |
|
if ($columnMetadata instanceof FieldMetadata) { |
343
|
1 |
|
$output[] = $this->formatField(' Generator type', $this->formatValue($columnMetadata->getValueGenerator()->getType())); |
344
|
1 |
|
$output[] = $this->formatField(' Generator definition', $this->formatValue($columnMetadata->getValueGenerator()->getDefinition())); |
345
|
|
|
} |
346
|
|
|
|
347
|
1 |
|
return $output; |
|
|
|
|
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
/** |
351
|
|
|
* Format the entity listeners |
352
|
|
|
* |
353
|
|
|
* @param object[] $entityListeners |
354
|
|
|
* |
355
|
|
|
* @return string |
356
|
|
|
*/ |
357
|
1 |
|
private function formatEntityListeners(array $entityListeners) |
358
|
|
|
{ |
359
|
1 |
|
return $this->formatField('Entity listeners', array_map('get_class', $entityListeners)); |
|
|
|
|
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
/** |
363
|
|
|
* @return string[] |
364
|
|
|
*/ |
365
|
1 |
|
private function formatTable(?TableMetadata $tableMetadata = null) |
366
|
|
|
{ |
367
|
1 |
|
$output = []; |
368
|
|
|
|
369
|
1 |
|
if ($tableMetadata === null) { |
370
|
|
|
$output[] = '<comment>Null</comment>'; |
371
|
|
|
|
372
|
|
|
return $output; |
373
|
|
|
} |
374
|
|
|
|
375
|
1 |
|
$output[] = $this->formatField(' schema', $this->formatValue($tableMetadata->getSchema())); |
376
|
1 |
|
$output[] = $this->formatField(' name', $this->formatValue($tableMetadata->getName())); |
377
|
1 |
|
$output[] = $this->formatField(' indexes', $this->formatValue($tableMetadata->getIndexes())); |
378
|
1 |
|
$output[] = $this->formatField(' uniqueConstaints', $this->formatValue($tableMetadata->getUniqueConstraints())); |
379
|
1 |
|
$output[] = $this->formatField(' options', $this->formatValue($tableMetadata->getOptions())); |
380
|
|
|
|
381
|
1 |
|
return $output; |
|
|
|
|
382
|
|
|
} |
383
|
|
|
} |
384
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.