Failed Conditions
Push — master ( 8be1e3...e3936d )
by Marco
14s
created

formatAssociationMappings()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 3
nop 1
dl 0
loc 13
ccs 0
cts 7
cp 0
crap 12
rs 9.4285
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
22
/**
23
 * Show information about mapped entities.
24
 */
25
final class MappingDescribeCommand extends Command
26
{
27
    /**
28
     * {@inheritdoc}
29
     */
30 3
    protected function configure()
31
    {
32 3
        $this->setName('orm:mapping:describe')
33 3
             ->addArgument('entityName', InputArgument::REQUIRED, 'Full or partial name of entity')
34 3
             ->setDescription('Display information about mapped objects')
35 3
             ->setHelp(<<<'EOT'
36 3
The %command.full_name% command describes the metadata for the given full or partial entity class name.
37
38
    <info>%command.full_name%</info> My\Namespace\Entity\MyEntity
39
40
Or:
41
42
    <info>%command.full_name%</info> MyEntity
43
EOT
44
             );
45 3
    }
46
47
    /**
48
     * {@inheritdoc}
49
     */
50 3
    protected function execute(InputInterface $input, OutputInterface $output)
51
    {
52 3
        $ui = new SymfonyStyle($input, $output);
53
54
        /* @var $entityManager \Doctrine\ORM\EntityManagerInterface */
55 3
        $entityManager = $this->getHelper('em')->getEntityManager();
56
57 3
        $this->displayEntity($input->getArgument('entityName'), $entityManager, $ui);
58
59 1
        return 0;
60
    }
61
62
    /**
63
     * Display all the mapping information for a single Entity.
64
     *
65
     * @param string $entityName Full or partial entity class name
66
     */
67 3
    private function displayEntity($entityName, EntityManagerInterface $entityManager, SymfonyStyle $ui)
68
    {
69 3
        $metadata    = $this->getClassMetadata($entityName, $entityManager);
70 1
        $parentValue = $metadata->getParent() === null ? '<comment>None</comment>' : '';
71
72 1
        $ui->table(
73 1
            ['Field', 'Value'],
74 1
            array_merge(
75
                [
76 1
                    $this->formatField('Name', $metadata->getClassName()),
77 1
                    $this->formatField('Root entity name', $metadata->getRootClassName()),
78 1
                    $this->formatField('Custom repository class', $metadata->getCustomRepositoryClassName()),
79 1
                    $this->formatField('Mapped super class?', $metadata->isMappedSuperclass),
80 1
                    $this->formatField('Embedded class?', $metadata->isEmbeddedClass),
81 1
                    $this->formatField('Parent classes', $parentValue),
82
                ],
83 1
                $this->formatParentClasses($metadata),
84
                [
85 1
                    $this->formatField('Sub classes', $metadata->getSubClasses()),
86 1
                    $this->formatField('Embedded classes', $metadata->getSubClasses()),
87 1
                    $this->formatField('Named queries', $metadata->getNamedQueries()),
88 1
                    $this->formatField('Named native queries', $metadata->getNamedNativeQueries()),
89 1
                    $this->formatField('SQL result set mappings', $metadata->getSqlResultSetMappings()),
90 1
                    $this->formatField('Identifier', $metadata->getIdentifier()),
91 1
                    $this->formatField('Inheritance type', $metadata->inheritanceType),
92 1
                    $this->formatField('Discriminator column', ''),
93
                ],
94 1
                $this->formatColumn($metadata->discriminatorColumn),
95
                [
96 1
                    $this->formatField('Discriminator value', $metadata->discriminatorValue),
97 1
                    $this->formatField('Discriminator map', $metadata->discriminatorMap),
98 1
                    $this->formatField('Table', ''),
99
                ],
100 1
                $this->formatTable($metadata->table),
101
                [
102 1
                    $this->formatField('Composite identifier?', $metadata->isIdentifierComposite()),
103 1
                    $this->formatField('Change tracking policy', $metadata->changeTrackingPolicy),
104 1
                    $this->formatField('Versioned?', $metadata->isVersioned()),
105 1
                    $this->formatField('Version field', ($metadata->isVersioned() ? $metadata->versionProperty->getName() : '')),
106 1
                    $this->formatField('Read only?', $metadata->isReadOnly()),
107
108 1
                    $this->formatEntityListeners($metadata->entityListeners),
109
                ],
110 1
                [$this->formatField('Property mappings:', '')],
111 1
                $this->formatPropertyMappings($metadata->getDeclaredPropertiesIterator())
0 ignored issues
show
Bug introduced by
$metadata->getDeclaredPropertiesIterator() of type Generator is incompatible with the type iterable expected by parameter $propertyMappings of Doctrine\ORM\Tools\Conso...ormatPropertyMappings(). ( Ignorable by Annotation )

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

111
                $this->formatPropertyMappings(/** @scrutinizer ignore-type */ $metadata->getDeclaredPropertiesIterator())
Loading history...
112
            )
113
        );
114 1
    }
115
116
    /**
117
     * Return all mapped entity class names
118
     *
119
     * @return string[]
120
     */
121 3
    private function getMappedEntities(EntityManagerInterface $entityManager)
122
    {
123 3
        $entityClassNames = $entityManager->getConfiguration()
124 3
                                          ->getMetadataDriverImpl()
125 3
                                          ->getAllClassNames();
126
127 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...
128
            throw new \InvalidArgumentException(
129
                'You do not have any mapped Doctrine ORM entities according to the current configuration. ' .
130
                'If you have entities or mapping files you should check your mapping configuration for errors.'
131
            );
132
        }
133
134 3
        return $entityClassNames;
135
    }
136
137
    /**
138
     * Return the class metadata for the given entity
139
     * name
140
     *
141
     * @param string $entityName Full or partial entity name
142
     *
143
     * @return \Doctrine\ORM\Mapping\ClassMetadata
144
     */
145 3
    private function getClassMetadata($entityName, EntityManagerInterface $entityManager)
146
    {
147
        try {
148 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...
149 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...
150
        }
151
152 3
        $matches = array_filter(
153 3
            $this->getMappedEntities($entityManager),
154 3
            function ($mappedEntity) use ($entityName) {
155 3
                return preg_match('{' . preg_quote($entityName) . '}', $mappedEntity);
156 3
            }
157
        );
158
159 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...
160 1
            throw new \InvalidArgumentException(sprintf(
161 1
                'Could not find any mapped Entity classes matching "%s"',
162 1
                $entityName
163
            ));
164
        }
165
166 2
        if (count($matches) > 1) {
167 1
            throw new \InvalidArgumentException(sprintf(
168 1
                'Entity name "%s" is ambiguous, possible matches: "%s"',
169 1
                $entityName,
170 1
                implode(', ', $matches)
171
            ));
172
        }
173
174 1
        return $entityManager->getClassMetadata(current($matches));
175
    }
176
177
    /**
178
     * @return string[]
179
     */
180 1
    private function formatParentClasses(ComponentMetadata $metadata)
181
    {
182 1
        $output      = [];
183 1
        $parentClass = $metadata;
184
185 1
        while (($parentClass = $parentClass->getParent()) !== null) {
186
            /** @var ClassMetadata $parentClass */
187
            $attributes = [];
188
189
            if ($parentClass->isEmbeddedClass) {
190
                $attributes[] = 'Embedded';
191
            }
192
193
            if ($parentClass->isMappedSuperclass) {
194
                $attributes[] = 'Mapped superclass';
195
            }
196
197
            if ($parentClass->inheritanceType) {
198
                $attributes[] = ucfirst(strtolower($parentClass->inheritanceType));
199
            }
200
201
            if ($parentClass->isReadOnly()) {
202
                $attributes[] = 'Read-only';
203
            }
204
205
            if ($parentClass->isVersioned()) {
206
                $attributes[] = 'Versioned';
207
            }
208
209
            $output[] = $this->formatField(
210
                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

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