Failed Conditions
Pull Request — master (#7210)
by Michael
12:25
created

MappingDescribeCommand::formatColumn()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 3.0146

Importance

Changes 0
Metric Value
cc 3
eloc 16
nc 3
nop 1
dl 0
loc 25
ccs 15
cts 17
cp 0.8824
crap 3.0146
rs 8.8571
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 Symfony\Component\Console\Command\Command;
17
use Symfony\Component\Console\Input\InputArgument;
18
use Symfony\Component\Console\Input\InputInterface;
19
use Symfony\Component\Console\Output\OutputInterface;
20
use Symfony\Component\Console\Style\SymfonyStyle;
21
use const JSON_UNESCAPED_SLASHES;
22
use const JSON_UNESCAPED_UNICODE;
23
use function array_filter;
24
use function array_map;
25
use function array_merge;
26
use function count;
27
use function current;
28
use function get_class;
29
use function implode;
30
use function is_array;
31
use function is_bool;
32
use function is_object;
33
use function is_scalar;
34
use function json_encode;
35
use function preg_match;
36
use function preg_quote;
37
use function print_r;
38
use function sprintf;
39
use function strtolower;
40
use function ucfirst;
41
42
/**
43
 * Show information about mapped entities.
44
 */
45
final class MappingDescribeCommand extends Command
46
{
47
    /**
48
     * {@inheritdoc}
49
     */
50 3
    protected function configure()
51
    {
52 3
        $this->setName('orm:mapping:describe')
53 3
             ->addArgument('entityName', InputArgument::REQUIRED, 'Full or partial name of entity')
54 3
             ->setDescription('Display information about mapped objects')
55 3
             ->setHelp(<<<'EOT'
56 3
The %command.full_name% command describes the metadata for the given full or partial entity class name.
57
58
    <info>%command.full_name%</info> My\Namespace\Entity\MyEntity
59
60
Or:
61
62
    <info>%command.full_name%</info> MyEntity
63
EOT
64
             );
65 3
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70 3
    protected function execute(InputInterface $input, OutputInterface $output)
71
    {
72 3
        $ui = new SymfonyStyle($input, $output);
73
74
        /** @var EntityManagerInterface $entityManager */
75 3
        $entityManager = $this->getHelper('em')->getEntityManager();
76
77 3
        $this->displayEntity($input->getArgument('entityName'), $entityManager, $ui);
78
79 1
        return 0;
80
    }
81
82
    /**
83
     * Display all the mapping information for a single Entity.
84
     *
85
     * @param string $entityName Full or partial entity class name
86
     */
87 3
    private function displayEntity($entityName, EntityManagerInterface $entityManager, SymfonyStyle $ui)
88
    {
89 3
        $metadata    = $this->getClassMetadata($entityName, $entityManager);
90 1
        $parentValue = $metadata->getParent() === null ? '<comment>None</comment>' : '';
91
92 1
        $ui->table(
93 1
            ['Field', 'Value'],
94 1
            array_merge(
95
                [
96 1
                    $this->formatField('Name', $metadata->getClassName()),
97 1
                    $this->formatField('Root entity name', $metadata->getRootClassName()),
98 1
                    $this->formatField('Custom repository class', $metadata->getCustomRepositoryClassName()),
99 1
                    $this->formatField('Mapped super class?', $metadata->isMappedSuperclass),
100 1
                    $this->formatField('Embedded class?', $metadata->isEmbeddedClass),
101 1
                    $this->formatField('Parent classes', $parentValue),
102
                ],
103 1
                $this->formatParentClasses($metadata),
104
                [
105 1
                    $this->formatField('Sub classes', $metadata->getSubClasses()),
106 1
                    $this->formatField('Embedded classes', $metadata->getSubClasses()),
107 1
                    $this->formatField('Identifier', $metadata->getIdentifier()),
108 1
                    $this->formatField('Inheritance type', $metadata->inheritanceType),
109 1
                    $this->formatField('Discriminator column', ''),
110
                ],
111 1
                $this->formatColumn($metadata->discriminatorColumn),
112
                [
113 1
                    $this->formatField('Discriminator value', $metadata->discriminatorValue),
114 1
                    $this->formatField('Discriminator map', $metadata->discriminatorMap),
115 1
                    $this->formatField('Table', ''),
116
                ],
117 1
                $this->formatTable($metadata->table),
118
                [
119 1
                    $this->formatField('Composite identifier?', $metadata->isIdentifierComposite()),
120 1
                    $this->formatField('Change tracking policy', $metadata->changeTrackingPolicy),
121 1
                    $this->formatField('Versioned?', $metadata->isVersioned()),
122 1
                    $this->formatField('Version field', ($metadata->isVersioned() ? $metadata->versionProperty->getName() : '')),
123 1
                    $this->formatField('Read only?', $metadata->isReadOnly()),
124
125 1
                    $this->formatEntityListeners($metadata->entityListeners),
126
                ],
127 1
                [$this->formatField('Property mappings:', '')],
128 1
                $this->formatPropertyMappings($metadata->getDeclaredPropertiesIterator())
129
            )
130
        );
131 1
    }
132
133
    /**
134
     * Return all mapped entity class names
135
     *
136
     * @return string[]
137
     */
138 3
    private function getMappedEntities(EntityManagerInterface $entityManager)
139
    {
140 3
        $entityClassNames = $entityManager->getConfiguration()
141 3
                                          ->getMetadataDriverImpl()
142 3
                                          ->getAllClassNames();
143
144 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...
145
            throw new \InvalidArgumentException(
146
                'You do not have any mapped Doctrine ORM entities according to the current configuration. ' .
147
                'If you have entities or mapping files you should check your mapping configuration for errors.'
148
            );
149
        }
150
151 3
        return $entityClassNames;
152
    }
153
154
    /**
155
     * Return the class metadata for the given entity
156
     * name
157
     *
158
     * @param string $entityName Full or partial entity name
159
     *
160
     * @return ClassMetadata
161
     */
162 3
    private function getClassMetadata($entityName, EntityManagerInterface $entityManager)
163
    {
164
        try {
165 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...
166 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...
167
        }
168
169 3
        $matches = array_filter(
170 3
            $this->getMappedEntities($entityManager),
171
            function ($mappedEntity) use ($entityName) {
172 3
                return preg_match('{' . preg_quote($entityName) . '}', $mappedEntity);
173 3
            }
174
        );
175
176 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...
177 1
            throw new \InvalidArgumentException(sprintf(
178 1
                'Could not find any mapped Entity classes matching "%s"',
179 1
                $entityName
180
            ));
181
        }
182
183 2
        if (count($matches) > 1) {
184 1
            throw new \InvalidArgumentException(sprintf(
185 1
                'Entity name "%s" is ambiguous, possible matches: "%s"',
186 1
                $entityName,
187 1
                implode(', ', $matches)
188
            ));
189
        }
190
191 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...
192
    }
193
194
    /**
195
     * @return string[]
196
     */
197 1
    private function formatParentClasses(ComponentMetadata $metadata)
198
    {
199 1
        $output      = [];
200 1
        $parentClass = $metadata;
201
202 1
        while (($parentClass = $parentClass->getParent()) !== null) {
203
            /** @var ClassMetadata $parentClass */
204
            $attributes = [];
205
206
            if ($parentClass->isEmbeddedClass) {
207
                $attributes[] = 'Embedded';
208
            }
209
210
            if ($parentClass->isMappedSuperclass) {
211
                $attributes[] = 'Mapped superclass';
212
            }
213
214
            if ($parentClass->inheritanceType) {
215
                $attributes[] = ucfirst(strtolower($parentClass->inheritanceType));
216
            }
217
218
            if ($parentClass->isReadOnly()) {
219
                $attributes[] = 'Read-only';
220
            }
221
222
            if ($parentClass->isVersioned()) {
223
                $attributes[] = 'Versioned';
224
            }
225
226
            $output[] = $this->formatField(
227
                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

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