Test Failed
Push — master ( 8d0fdb...c2bd3c )
by Curtis
02:26
created

GenerateDataFactoryCommand   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 247
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 43
eloc 153
dl 0
loc 247
rs 8.96
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A configure() 0 26 1
B execute() 0 68 6
A buildBody() 0 21 2
F buildFactoryData() 0 100 33

How to fix   Complexity   

Complex Class

Complex classes like GenerateDataFactoryCommand 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 GenerateDataFactoryCommand, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace DoctrineRepoHelper\Command;
4
5
6
use Doctrine\ORM\Mapping\ClassMetadata;
7
use Doctrine\ORM\EntityManagerInterface;
8
use Symfony\Component\Console\Command\Command;
9
use Symfony\Component\Console\Input\InputInterface;
10
use Symfony\Component\Console\Input\InputOption;
11
use Symfony\Component\Console\Output\OutputInterface;
12
use Zend\Code\Generator\DocBlockGenerator;
13
use Zend\Code\Generator\FileGenerator;
14
15
/**
16
 * Class GenerateDataFactoryCommand
17
 * @package DoctrineRepoHelper\Command
18
 */
19
class GenerateDataFactoryCommand extends Command
20
{
21
    /** @var EntityManagerInterface */
22
    protected $entityManager;
23
24
    /**
25
     * GenerateTraitCommand constructor.
26
     * @param EntityManagerInterface $entityManager
27
     */
28
    public function __construct(EntityManagerInterface $entityManager)
29
    {
30
        $this->entityManager = $entityManager;
31
        parent::__construct();
32
    }
33
34
    protected function configure()
35
    {
36
        parent::configure();
37
38
        $this
39
            ->setName('orm:generate-data-factories')
40
            ->setDescription('Generate data factories for use with Codeception')
41
            ->setHelp('Generates data factories for use with the Codeception DataFactory module')
42
            ->addOption(
43
                'output',
44
                'o',
45
                InputOption::VALUE_OPTIONAL,
46
                'Output path',
47
                getcwd() . '/data/factories'
48
            )
49
            ->addOption(
50
                'force',
51
                'f',
52
                InputOption::VALUE_OPTIONAL,
53
                'Overwrite existing data factories'
54
            )
55
            ->addOption(
56
                'filter',
57
                null,
58
                InputOption::VALUE_OPTIONAL,
59
                'filter the list of entities data factories are created for'
60
            );
61
    }
62
63
    public function execute(InputInterface $input, OutputInterface $output)
64
    {
65
        $destination = $input->getOption('output');
66
        $metaDataEntries = $this->entityManager->getMetadataFactory()->getAllMetadata();
67
68
        if (!is_dir($destination)) {
0 ignored issues
show
Bug introduced by
It seems like $destination can also be of type string[]; however, parameter $filename of is_dir() 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

68
        if (!is_dir(/** @scrutinizer ignore-type */ $destination)) {
Loading history...
69
            mkdir($destination, 0777, true);
0 ignored issues
show
Bug introduced by
It seems like $destination can also be of type string[]; however, parameter $pathname of mkdir() 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

69
            mkdir(/** @scrutinizer ignore-type */ $destination, 0777, true);
Loading history...
70
        }
71
72
        /** @var ClassMetadata $metaData */
73
        foreach ($metaDataEntries as $metaData) {
74
            if ($filter = $input->getOption('filter')) {
75
                if (strpos($metaData->getName(), $filter) === false) {
76
                    $output->writeln(
77
                        sprintf(
78
                            'Filtering out %s...',
79
                            $metaData->getName()
80
                        ),
81
                        OutputInterface::VERBOSITY_VERY_VERBOSE
82
                    );
83
                    continue;
84
                }
85
            }
86
87
            $fileName = $destination . '/' . $metaData->reflClass->getShortName() . 'DataFactory.php';
0 ignored issues
show
Bug introduced by
Accessing reflClass on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
88
            // TODO handle duplicate names
89
90
            $file = FileGenerator::fromArray(
91
                [
92
                    'docblock' => DocBlockGenerator::fromArray(
93
                        [
94
                            'shortDescription' => '',
95
                            'longDescription' => '',
96
                            'tags' => [
97
                                [
98
                                    'name' => 'var',
99
                                    'description' => '\\Codeception\\Module\\DataFactory $factory'
100
                                ],
101
                                [
102
                                    'name' => 'var',
103
                                    'description' => '\\Doctrine\\ORM\\EntityManager $em'
104
                                ]
105
                            ]
106
                        ]
107
                    ),
108
                    'body' => $this->buildBody($metaData),
109
                ]
110
            );
111
112
            if (file_exists($fileName)) {
113
                $output->writeln(
114
                    sprintf(
115
                        '%s already exists. Skipping...',
116
                        $fileName
117
                    ),
118
                    OutputInterface::VERBOSITY_VERBOSE
119
                );
120
121
                continue;
122
            }
123
124
            file_put_contents($fileName, $file->generate());
125
        }
126
127
        $output->writeln(
128
            sprintf(
129
                'Data factories written to "%s"',
130
                $destination
0 ignored issues
show
Bug introduced by
It seems like $destination can also be of type string[]; 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

130
                /** @scrutinizer ignore-type */ $destination
Loading history...
131
            )
132
        );
133
    }
134
135
    /**
136
     * @param ClassMetadata $metaData
137
     * @return string[]
138
     */
139
    private function buildFactoryData(ClassMetadata $metaData): array
140
    {
141
        $data = [];
142
143
        foreach ($metaData->fieldMappings as $fieldMapping) {
144
            switch ($fieldMapping['type']) {
145
                case 'smallint':
146
                case 'integer':
147
                case 'bigint':
148
                    $data[] = sprintf(
149
                        "        '%s' => random_int(0, 65000),\n",
150
                        $fieldMapping['fieldName']
151
                    );
152
                    break;
153
                case 'decimal':
154
                case 'float':
155
                    $data[] = sprintf(
156
                        "        '%s' => Faker::randomFloat(2),\n",
157
                        $fieldMapping['fieldName']
158
                    );
159
                    break;
160
                case 'string':
161
                    $data[] = sprintf(
162
                        "        '%s' => Faker::sentence(),\n",
163
                        $fieldMapping['fieldName']
164
                    );
165
                    break;
166
                case 'text':
167
                    $data[] = sprintf(
168
                        "        '%s' => Faker::paragraph(),\n",
169
                        $fieldMapping['fieldName']
170
                    );
171
                    break;
172
                case 'guid':
173
                    $data[] = sprintf(
174
                        "        '%s' => uniqid('', true)",
175
                        $fieldMapping['fieldName']
176
                    );
177
                    break;
178
                case 'binary':
179
                case 'blob':
180
                    break;
181
                case 'boolean':
182
                    $data[] = sprintf(
183
                        "        '%s' => (bool)random_int(0, 1),\n",
184
                        $fieldMapping['fieldName']
185
                    );
186
                    break;
187
                case 'date':
188
                case 'date_immutable':
189
                case 'datetime':
190
                case 'datetime_immutable':
191
                case 'datetimetz':
192
                case 'datetimetz_immutable':
193
                case 'time':
194
                case 'time_immutable':
195
                    $data[] = sprintf(
196
                        "        '%s' => Faker::dateTime(),\n",
197
                        $fieldMapping['fieldName']
198
                    );
199
                    break;
200
                case 'dateinterval':
201
                case 'array':
202
                case 'simple_array':
203
                case 'json':
204
                case 'json_array':
205
                case 'object':
206
                    break;
207
                default:
208
                    $data[] = sprintf(
209
                        "        '%s' => Faker::word(),\n",
210
                        $fieldMapping['fieldName']
211
                    );
212
                    break;
213
            }
214
        }
215
216
        foreach ($metaData->associationMappings as $associationMapping) {
217
            switch ($associationMapping['type']) {
218
                case 1:
219
                case 2:
220
                case 3:
221
                    $data[] = sprintf(
222
                        "        '%s' => 'entity|' . \\%s::class,\n",
223
                        $associationMapping['fieldName'],
224
                        $associationMapping['targetEntity']
225
                    );
226
                    break;
227
                case 4:
228
                    // TODO one to many
229
                    break;
230
                case 8:
231
                    // TODO many to many
232
                    break;
233
                default:
234
                    break;
235
            }
236
        }
237
238
        return $data;
239
    }
240
241
    /**
242
     * @param ClassMetadata $metaData
243
     * @return string
244
     */
245
    private function buildBody(ClassMetadata $metaData): string
246
    {
247
        $fields = $this->buildFactoryData($metaData);
248
249
        $body = sprintf(
250
            "use League\\FactoryMuffin\\Faker\\Facade as Faker;
251
252
\$factory->_define(
253
    %s::class,
254
    [\n",
255
            $metaData->getName()
256
        );
257
258
        foreach ($fields as $field) {
259
            $body .= $field;
260
        }
261
262
        $body .= '
263
    ]
264
);';
265
        return $body;
266
    }
267
}