Failed Conditions
Pull Request — master (#6735)
by Matthias
09:54
created

MappingDescribeCommand::getMappedEntities()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2.0625

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 1
dl 0
loc 14
ccs 6
cts 8
cp 0.75
crap 2.0625
rs 10
c 0
b 0
f 0
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $entityClassNames of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
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);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $entityManager->g...ssMetadata($entityName) returns the type Doctrine\Common\Persistence\Mapping\ClassMetadata which is incompatible with the documented return type Doctrine\ORM\Mapping\ClassMetadata.
Loading history...
167 3
        } catch (MappingException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
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));
0 ignored issues
show
Bug Best Practice introduced by
The expression return $entityManager->g...data(current($matches)) returns the type Doctrine\Common\Persistence\Mapping\ClassMetadata which is incompatible with the documented return type Doctrine\ORM\Mapping\ClassMetadata.
Loading history...
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()),
0 ignored issues
show
Bug introduced by
It seems like $parentClass->getParent() can also be of type Doctrine\ORM\Mapping\ComponentMetadata; however, parameter $args of sprintf() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

228
                sprintf('  %s', /** @scrutinizer ignore-type */ $parentClass->getParent()),
Loading history...
229
                ($parentClass->isRootEntity() ? '(Root) ' : '') . $this->formatValue($attributes)
230
            );
231
        }
232
233 1
        return $output;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $output returns an array which contains values of type array<integer,string> which are incompatible with the documented value type string.
Loading history...
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;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $output returns an array which contains values of type array<integer,string> which are incompatible with the documented value type string.
Loading history...
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;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $output returns the type array<mixed,array<integer,string>> which is incompatible with the documented return type string[].
Loading history...
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));
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->formatFiel...ss', $entityListeners)) returns the type array<integer,string> which is incompatible with the documented return type string.
Loading history...
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;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $output returns the type array<mixed,array<integer,string>> which is incompatible with the documented return type string[].
Loading history...
382
    }
383
}
384