Failed Conditions
Push — master ( fa7802...d60694 )
by Guilherme
09:27
created

MappingDescribeCommand   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 357
Duplicated Lines 0 %

Test Coverage

Coverage 82.91%

Importance

Changes 0
Metric Value
eloc 152
c 0
b 0
f 0
dl 0
loc 357
ccs 131
cts 158
cp 0.8291
rs 8.8798
wmc 44

13 Methods

Rating   Name   Duplication   Size   Complexity  
A formatField() 0 7 2
A getMappedEntities() 0 14 2
A formatEntityListeners() 0 3 1
B formatParentClasses() 0 36 8
A formatTable() 0 17 2
B formatValue() 0 31 9
A formatValueGenerator() 0 20 3
A configure() 0 6 1
A formatColumn() 0 24 3
A execute() 0 10 1
A getClassMetadata() 0 30 4
A formatPropertyMappings() 0 18 5
A displayEntity() 0 42 3

How to fix   Complexity   

Complex Class

Complex classes like MappingDescribeCommand often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MappingDescribeCommand, and based on these observations, apply Extract Interface, too.

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\Annotation\GeneratedValue;
0 ignored issues
show
introduced by
Type Doctrine\ORM\Annotation\GeneratedValue is not used in this file.
Loading history...
9
use Doctrine\ORM\EntityManagerInterface;
10
use Doctrine\ORM\Mapping\AssociationMetadata;
11
use Doctrine\ORM\Mapping\ClassMetadata;
12
use Doctrine\ORM\Mapping\ColumnMetadata;
13
use Doctrine\ORM\Mapping\ComponentMetadata;
14
use Doctrine\ORM\Mapping\FieldMetadata;
15
use Doctrine\ORM\Mapping\Property;
16
use Doctrine\ORM\Mapping\TableMetadata;
17
use Doctrine\ORM\Mapping\ValueGeneratorMetadata;
18
use Doctrine\ORM\Sequencing\Generator;
19
use InvalidArgumentException;
20
use Symfony\Component\Console\Command\Command;
21
use Symfony\Component\Console\Input\InputArgument;
22
use Symfony\Component\Console\Input\InputInterface;
23
use Symfony\Component\Console\Output\OutputInterface;
24
use Symfony\Component\Console\Style\SymfonyStyle;
25
use const JSON_UNESCAPED_SLASHES;
26
use const JSON_UNESCAPED_UNICODE;
27
use function array_filter;
28
use function array_map;
29
use function array_merge;
30
use function count;
31
use function current;
32
use function get_class;
33
use function implode;
34
use function is_array;
35
use function is_bool;
36
use function is_object;
37
use function is_scalar;
38
use function json_encode;
39
use function preg_match;
40
use function preg_quote;
41
use function print_r;
42
use function sprintf;
43
use function strtolower;
44
use function ucfirst;
45
46
/**
47
 * Show information about mapped entities.
48
 */
49
final class MappingDescribeCommand extends Command
50
{
51
    /**
52
     * {@inheritdoc}
53
     */
54 3
    protected function configure()
55
    {
56 3
        $this->setName('orm:mapping:describe')
57 3
             ->addArgument('entityName', InputArgument::REQUIRED, 'Full or partial name of entity')
58 3
             ->setDescription('Display information about mapped objects')
59 3
             ->setHelp(<<<'EOT'
60 3
The %command.full_name% command describes the metadata for the given full or partial entity class name.
61
62
    <info>%command.full_name%</info> My\Namespace\Entity\MyEntity
63
64
Or:
65
66
    <info>%command.full_name%</info> MyEntity
67
EOT
68
             );
69 3
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74 3
    protected function execute(InputInterface $input, OutputInterface $output)
75
    {
76 3
        $ui = new SymfonyStyle($input, $output);
77
78
        /** @var EntityManagerInterface $entityManager */
79 3
        $entityManager = $this->getHelper('em')->getEntityManager();
80
81 3
        $this->displayEntity($input->getArgument('entityName'), $entityManager, $ui);
0 ignored issues
show
Bug introduced by
It seems like $input->getArgument('entityName') can also be of type string[]; however, parameter $entityName of Doctrine\ORM\Tools\Conso...ommand::displayEntity() 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

81
        $this->displayEntity(/** @scrutinizer ignore-type */ $input->getArgument('entityName'), $entityManager, $ui);
Loading history...
82
83 1
        return 0;
84
    }
85
86
    /**
87
     * Display all the mapping information for a single Entity.
88
     *
89
     * @param string $entityName Full or partial entity class name
90
     */
91 3
    private function displayEntity($entityName, EntityManagerInterface $entityManager, SymfonyStyle $ui)
92
    {
93 3
        $metadata    = $this->getClassMetadata($entityName, $entityManager);
94 1
        $parentValue = $metadata->getParent() === null ? '<comment>None</comment>' : '';
95
96 1
        $ui->table(
97 1
            ['Field', 'Value'],
98 1
            array_merge(
99
                [
100 1
                    $this->formatField('Name', $metadata->getClassName()),
101 1
                    $this->formatField('Root entity name', $metadata->getRootClassName()),
102 1
                    $this->formatField('Custom repository class', $metadata->getCustomRepositoryClassName()),
103 1
                    $this->formatField('Mapped super class?', $metadata->isMappedSuperclass),
104 1
                    $this->formatField('Embedded class?', $metadata->isEmbeddedClass),
105 1
                    $this->formatField('Parent classes', $parentValue),
106
                ],
107 1
                $this->formatParentClasses($metadata),
108
                [
109 1
                    $this->formatField('Sub classes', $metadata->getSubClasses()),
110 1
                    $this->formatField('Embedded classes', $metadata->getSubClasses()),
111 1
                    $this->formatField('Identifier', $metadata->getIdentifier()),
112 1
                    $this->formatField('Inheritance type', $metadata->inheritanceType),
113 1
                    $this->formatField('Discriminator column', ''),
114
                ],
115 1
                $this->formatColumn($metadata->discriminatorColumn),
116
                [
117 1
                    $this->formatField('Discriminator value', $metadata->discriminatorValue),
118 1
                    $this->formatField('Discriminator map', $metadata->discriminatorMap),
119 1
                    $this->formatField('Table', ''),
120
                ],
121 1
                $this->formatTable($metadata->table),
122
                [
123 1
                    $this->formatField('Composite identifier?', $metadata->isIdentifierComposite()),
124 1
                    $this->formatField('Change tracking policy', $metadata->changeTrackingPolicy),
125 1
                    $this->formatField('Versioned?', $metadata->isVersioned()),
126 1
                    $this->formatField('Version field', ($metadata->isVersioned() ? $metadata->versionProperty->getName() : '')),
127 1
                    $this->formatField('Read only?', $metadata->isReadOnly()),
128
129 1
                    $this->formatEntityListeners($metadata->entityListeners),
130
                ],
131 1
                [$this->formatField('Property mappings:', '')],
132 1
                $this->formatPropertyMappings($metadata->getPropertiesIterator())
133
            )
134
        );
135 1
    }
136
137
    /**
138
     * Return all mapped entity class names
139
     *
140
     * @return string[]
141
     */
142 3
    private function getMappedEntities(EntityManagerInterface $entityManager)
143
    {
144 3
        $entityClassNames = $entityManager->getConfiguration()
145 3
                                          ->getMetadataDriverImpl()
146 3
                                          ->getAllClassNames();
147
148 3
        if (! $entityClassNames) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $entityClassNames 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...
149
            throw new InvalidArgumentException(
150
                'You do not have any mapped Doctrine ORM entities according to the current configuration. ' .
151
                'If you have entities or mapping files you should check your mapping configuration for errors.'
152
            );
153
        }
154
155 3
        return $entityClassNames;
156
    }
157
158
    /**
159
     * Return the class metadata for the given entity
160
     * name
161
     *
162
     * @param string $entityName Full or partial entity name
163
     *
164
     * @return ClassMetadata
165
     */
166 3
    private function getClassMetadata($entityName, EntityManagerInterface $entityManager)
167
    {
168
        try {
169 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...
170 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...
171
        }
172
173 3
        $matches = array_filter(
174 3
            $this->getMappedEntities($entityManager),
175
            static function ($mappedEntity) use ($entityName) {
176 3
                return preg_match('{' . preg_quote($entityName) . '}', $mappedEntity);
177 3
            }
178
        );
179
180 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...
181 1
            throw new InvalidArgumentException(sprintf(
182 1
                'Could not find any mapped Entity classes matching "%s"',
183 1
                $entityName
184
            ));
185
        }
186
187 2
        if (count($matches) > 1) {
188 1
            throw new InvalidArgumentException(sprintf(
189 1
                'Entity name "%s" is ambiguous, possible matches: "%s"',
190 1
                $entityName,
191 1
                implode(', ', $matches)
192
            ));
193
        }
194
195 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...
196
    }
197
198
    /**
199
     * @return string[]
200
     */
201 1
    private function formatParentClasses(ComponentMetadata $metadata)
202
    {
203 1
        $output      = [];
204 1
        $parentClass = $metadata;
205
206 1
        while (($parentClass = $parentClass->getParent()) !== null) {
207
            /** @var ClassMetadata $parentClass */
208
            $attributes = [];
209
210
            if ($parentClass->isEmbeddedClass) {
211
                $attributes[] = 'Embedded';
212
            }
213
214
            if ($parentClass->isMappedSuperclass) {
215
                $attributes[] = 'Mapped superclass';
216
            }
217
218
            if ($parentClass->inheritanceType) {
219
                $attributes[] = ucfirst(strtolower($parentClass->inheritanceType));
220
            }
221
222
            if ($parentClass->isReadOnly()) {
223
                $attributes[] = 'Read-only';
224
            }
225
226
            if ($parentClass->isVersioned()) {
227
                $attributes[] = 'Versioned';
228
            }
229
230
            $output[] = $this->formatField(
231
                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

231
                sprintf('  %s', /** @scrutinizer ignore-type */ $parentClass->getParent()),
Loading history...
232
                ($parentClass->isRootEntity() ? '(Root) ' : '') . $this->formatValue($attributes)
233
            );
234
        }
235
236 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...
237
    }
238
239
    /**
240
     * Format the given value for console output
241
     *
242
     * @param mixed $value
243
     *
244
     * @return string
245
     */
246 1
    private function formatValue($value)
247
    {
248 1
        if ($value === '') {
249 1
            return '';
250
        }
251
252 1
        if ($value === null) {
253 1
            return '<comment>Null</comment>';
254
        }
255
256 1
        if (is_bool($value)) {
257 1
            return '<comment>' . ($value ? 'True' : 'False') . '</comment>';
258
        }
259
260 1
        if (empty($value)) {
261 1
            return '<comment>Empty</comment>';
262
        }
263
264 1
        if (is_array($value)) {
265 1
            return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
266
        }
267
268 1
        if (is_object($value)) {
269
            return sprintf('<%s>', get_class($value));
270
        }
271
272 1
        if (is_scalar($value)) {
273 1
            return $value;
274
        }
275
276
        throw new InvalidArgumentException(sprintf('Do not know how to format value "%s"', print_r($value, true)));
277
    }
278
279
    /**
280
     * Add the given label and value to the two column table output
281
     *
282
     * @param string $label Label for the value
283
     * @param mixed  $value A Value to show
284
     *
285
     * @return string[]
286
     */
287 1
    private function formatField($label, $value)
288
    {
289 1
        if ($value === null) {
290 1
            $value = '<comment>None</comment>';
291
        }
292
293 1
        return [sprintf('<info>%s</info>', $label), $this->formatValue($value)];
294
    }
295
296
    /**
297
     * Format the property mappings
298
     *
299
     * @param iterable|Property[] $propertyMappings
300
     *
301
     * @return string[]
302
     */
303 1
    private function formatPropertyMappings(iterable $propertyMappings)
304
    {
305 1
        $output = [];
306
307 1
        foreach ($propertyMappings as $propertyName => $property) {
308 1
            $output[] = $this->formatField(sprintf('  %s', $propertyName), '');
309
310 1
            if ($property instanceof FieldMetadata) {
311 1
                $output = array_merge($output, $this->formatColumn($property));
312 1
            } elseif ($property instanceof AssociationMetadata) {
313
                // @todo guilhermeblanco Fix me! We are trying to iterate through an AssociationMetadata instance
314 1
                foreach ($property as $field => $value) {
315
                    $output[] = $this->formatField(sprintf('    %s', $field), $this->formatValue($value));
316
                }
317
            }
318
        }
319
320 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...
321
    }
322
323
    /**
324
     * @return string[]
325
     */
326 1
    private function formatColumn(?ColumnMetadata $columnMetadata = null)
327
    {
328 1
        $output = [];
329
330 1
        if ($columnMetadata === null) {
331
            $output[] = '<comment>Null</comment>';
332
333
            return $output;
334
        }
335
336 1
        $output[] = $this->formatField('    type', $this->formatValue($columnMetadata->getTypeName()));
337 1
        $output[] = $this->formatField('    tableName', $this->formatValue($columnMetadata->getTableName()));
338 1
        $output[] = $this->formatField('    columnName', $this->formatValue($columnMetadata->getColumnName()));
339 1
        $output[] = $this->formatField('    columnDefinition', $this->formatValue($columnMetadata->getColumnDefinition()));
340 1
        $output[] = $this->formatField('    isPrimaryKey', $this->formatValue($columnMetadata->isPrimaryKey()));
341 1
        $output[] = $this->formatField('    isNullable', $this->formatValue($columnMetadata->isNullable()));
342 1
        $output[] = $this->formatField('    isUnique', $this->formatValue($columnMetadata->isUnique()));
343 1
        $output[] = $this->formatField('    options', $this->formatValue($columnMetadata->getOptions()));
344
345 1
        if ($columnMetadata instanceof FieldMetadata) {
346 1
            $output = array_merge($output, $this->formatValueGenerator($columnMetadata->getValueGenerator()));
347
        }
348
349 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...
350
    }
351
352 1
    private function formatValueGenerator(?ValueGeneratorMetadata $valueGeneratorMetadata = null)
353
    {
354 1
        $output = [];
355
356 1
        if ($valueGeneratorMetadata === null) {
357
            $output[] = $this->formatField('    Generator', '<comment>None</comment>');
358
359
            return $output;
360
        }
361
362 1
        $output[] = $this->formatField('    Generator type', $this->formatValue($valueGeneratorMetadata->getType()));
363
364 1
        $generator = $valueGeneratorMetadata->getGenerator();
365
366 1
        if ($generator instanceof Generator\SequenceGenerator) {
367
            $output[] = $this->formatField('    Sequence name', $this->formatValue($generator->getSequenceName()));
368
            $output[] = $this->formatField('    Allocation size', $this->formatValue($generator->getAllocationSize()));
369
        }
370
371 1
        return $output;
372
    }
373
374
    /**
375
     * Format the entity listeners
376
     *
377
     * @param object[] $entityListeners
378
     *
379
     * @return string
380
     */
381 1
    private function formatEntityListeners(array $entityListeners)
382
    {
383 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...
384
    }
385
386
    /**
387
     * @return string[]
388
     */
389 1
    private function formatTable(?TableMetadata $tableMetadata = null)
390
    {
391 1
        $output = [];
392
393 1
        if ($tableMetadata === null) {
394
            $output[] = '<comment>Null</comment>';
395
396
            return $output;
397
        }
398
399 1
        $output[] = $this->formatField('    schema', $this->formatValue($tableMetadata->getSchema()));
400 1
        $output[] = $this->formatField('    name', $this->formatValue($tableMetadata->getName()));
401 1
        $output[] = $this->formatField('    indexes', $this->formatValue($tableMetadata->getIndexes()));
402 1
        $output[] = $this->formatField('    uniqueConstaints', $this->formatValue($tableMetadata->getUniqueConstraints()));
403 1
        $output[] = $this->formatField('    options', $this->formatValue($tableMetadata->getOptions()));
404
405 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...
406
    }
407
}
408