Test Failed
Push — master ( 89cc0d...38b4e7 )
by Curtis
02:08
created

buildAssociationMappings()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 18
rs 9.9332
c 0
b 0
f 0
cc 3
nc 1
nop 1
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
        $fieldMappings = $this->buildFieldMappings($metaData);
142
        $associationMappings = $this->buildAssociationMappings($metaData);
143
144
        return array_merge(
145
            $fieldMappings,
146
            $associationMappings
147
        );
148
    }
149
150
    /**
151
     * @param ClassMetadata $metaData
152
     * @return string
153
     */
154
    private function buildBody(ClassMetadata $metaData): string
155
    {
156
        $fields = $this->buildFactoryData($metaData);
157
158
        $body = sprintf(
159
            "use League\\FactoryMuffin\\Faker\\Facade as Faker;
160
161
\$factory->_define(
162
    %s::class,
163
    [\n",
164
            $metaData->getName()
165
        );
166
167
        foreach ($fields as $field) {
168
            $body .= $field;
169
        }
170
171
        $body .= '
172
    ]
173
);';
174
        return $body;
175
    }
176
177
    /**
178
     * @param ClassMetadata $metaData
179
     * @return array
180
     */
181
    private function buildAssociationMappings(ClassMetadata $metaData): array
182
    {
183
        $data = array_map(
184
            function(array $associationMapping) {
185
                if ($associationMapping['type'] > 0 && $associationMapping['type'] < 4) {
186
                    return sprintf(
187
                        "        '%s' => 'entity|' . \\%s::class,\n",
188
                        $associationMapping['fieldName'],
189
                        $associationMapping['targetEntity']
190
                    );
191
                }
192
193
                return null;
194
            },
195
            $metaData->associationMappings
196
        );
197
198
        return array_filter($data);
199
    }
200
201
    /**
202
     * @param ClassMetadata $metaData
203
     * @return array
204
     */
205
    private function buildFieldMappings(ClassMetadata $metaData): array
206
    {
207
        $data = [];
208
209
        foreach ($metaData->fieldMappings as $fieldMapping) {
210
            switch ($fieldMapping['type']) {
211
                case 'smallint':
212
                case 'integer':
213
                case 'bigint':
214
                    $data[] = sprintf(
215
                        "        '%s' => random_int(0, 65000),\n",
216
                        $fieldMapping['fieldName']
217
                    );
218
                    break;
219
                case 'decimal':
220
                case 'float':
221
                    $data[] = sprintf(
222
                        "        '%s' => Faker::randomFloat(2),\n",
223
                        $fieldMapping['fieldName']
224
                    );
225
                    break;
226
                case 'string':
227
                    $data[] = sprintf(
228
                        "        '%s' => Faker::sentence(),\n",
229
                        $fieldMapping['fieldName']
230
                    );
231
                    break;
232
                case 'text':
233
                    $data[] = sprintf(
234
                        "        '%s' => Faker::paragraph(),\n",
235
                        $fieldMapping['fieldName']
236
                    );
237
                    break;
238
                case 'guid':
239
                    $data[] = sprintf(
240
                        "        '%s' => uniqid('', true)",
241
                        $fieldMapping['fieldName']
242
                    );
243
                    break;
244
                case 'binary':
245
                case 'blob':
246
                    break;
247
                case 'boolean':
248
                    $data[] = sprintf(
249
                        "        '%s' => (bool)random_int(0, 1),\n",
250
                        $fieldMapping['fieldName']
251
                    );
252
                    break;
253
                case 'date':
254
                case 'date_immutable':
255
                case 'datetime':
256
                case 'datetime_immutable':
257
                case 'datetimetz':
258
                case 'datetimetz_immutable':
259
                case 'time':
260
                case 'time_immutable':
261
                    $data[] = sprintf(
262
                        "        '%s' => Faker::dateTime(),\n",
263
                        $fieldMapping['fieldName']
264
                    );
265
                    break;
266
                case 'dateinterval':
267
                case 'array':
268
                case 'simple_array':
269
                case 'json':
270
                case 'json_array':
271
                case 'object':
272
                    break;
273
                default:
274
                    $data[] = sprintf(
275
                        "        '%s' => Faker::word(),\n",
276
                        $fieldMapping['fieldName']
277
                    );
278
                    break;
279
            }
280
        }
281
282
        return $data;
283
    }
284
}