GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Builder   A
last analyzed

Complexity

Total Complexity 41

Size/Duplication

Total Lines 436
Duplicated Lines 0 %

Test Coverage

Coverage 21.71%

Importance

Changes 8
Bugs 0 Features 1
Metric Value
eloc 180
dl 0
loc 436
ccs 61
cts 281
cp 0.2171
rs 9.1199
c 8
b 0
f 1
wmc 41

24 Methods

Rating   Name   Duplication   Size   Complexity  
A removeUnusedRelations() 0 3 1
A getRelationsGenerator() 0 3 1
A finaliseBuild() 0 7 1
A getFieldSetter() 0 3 1
A getArchetypeEmbeddableGenerator() 0 3 1
A getFieldGenerator() 0 3 1
A setPathToProjectRoot() 0 14 1
A generateEntities() 0 12 2
A __construct() 0 29 1
A getEntityGenerator() 0 3 1
A getEmbeddableSetter() 0 3 1
A removeIdTraitFromClass() 0 4 1
A setEmbeddablesToEntity() 0 7 2
A getFileName() 0 5 1
A setProjectRootNamespace() 0 11 1
A generateFields() 0 16 3
A setEnumOptionsOnInterface() 0 44 4
A injectTraitInToClass() 0 12 2
A extendInterfaceWithInterface() 0 11 2
A setFieldsToEntity() 0 7 2
A setEntityRelations() 0 7 2
A removeTraitFromClass() 0 23 4
A generateKeyedFields() 0 31 3
A generateEmbeddables() 0 12 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
declare(strict_types=1);
4
5
namespace EdmondsCommerce\DoctrineStaticMeta\Builder;
6
7
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Action\CreateDtosForAllEntitiesAction;
8
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\CodeHelper;
9
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Embeddable\ArchetypeEmbeddableGenerator;
10
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Embeddable\EntityEmbeddableSetter;
11
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\EntityGenerator;
12
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Field\EntityFieldSetter;
13
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Field\FieldGenerator;
14
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\RelationsGenerator;
15
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\NamespaceHelper;
16
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\PostProcessor\CopyPhpstormMeta;
17
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\PostProcessor\EntityFormatter;
18
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\UnusedRelationsRemover;
19
use EdmondsCommerce\DoctrineStaticMeta\Config;
20
use EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException;
21
use Exception;
22
use gossi\codegen\model\PhpClass;
23
use gossi\codegen\model\PhpConstant;
24
use gossi\codegen\model\PhpInterface;
25
use gossi\codegen\model\PhpTrait;
26
use ReflectionException;
27
use RuntimeException;
28
use ts\Reflection\ReflectionClass;
29
30
use function preg_replace;
31
32
/**
33
 * Class Builder
34
 *
35
 * @package EdmondsCommerce\DoctrineStaticMeta\Builder
36
 * @SuppressWarnings(PHPMD)
37
 */
38
class Builder
39
{
40
41
    /**
42
     * @var EntityGenerator
43
     */
44
    protected $entityGenerator;
45
    /**
46
     * @var FieldGenerator
47
     */
48
    protected $fieldGenerator;
49
    /**
50
     * @var EntityFieldSetter
51
     */
52
    protected $fieldSetter;
53
    /**
54
     * @var RelationsGenerator
55
     */
56
    protected $relationsGenerator;
57
    /**
58
     * @var ArchetypeEmbeddableGenerator
59
     */
60
    protected $archetypeEmbeddableGenerator;
61
    /**
62
     * @var EntityEmbeddableSetter
63
     */
64
    protected $embeddableSetter;
65
    /**
66
     * @var CodeHelper
67
     */
68
    protected $codeHelper;
69
    /**
70
     * @var UnusedRelationsRemover
71
     */
72
    protected $unusedRelationsRemover;
73
    /**
74
     * @var CreateDtosForAllEntitiesAction
75
     */
76
    private $dataTransferObjectsForAllEntitiesAction;
77
    /**
78
     * @var EntityFormatter
79
     */
80
    private $entityFormatter;
81
    /**
82
     * @var CopyPhpstormMeta
83
     */
84
    private $copyPhpstormMeta;
85
    /**
86
     * @var NamespaceHelper
87
     */
88
    private $namespaceHelper;
89
90 1
    public function __construct(
91
        EntityGenerator $entityGenerator,
92
        FieldGenerator $fieldGenerator,
93
        EntityFieldSetter $fieldSetter,
94
        RelationsGenerator $relationsGenerator,
95
        ArchetypeEmbeddableGenerator $archetypeEmbeddableGenerator,
96
        EntityEmbeddableSetter $embeddableSetter,
97
        CodeHelper $codeHelper,
98
        UnusedRelationsRemover $unusedRelationsRemover,
99
        CreateDtosForAllEntitiesAction $dataTransferObjectsForAllEntitiesAction,
100
        EntityFormatter $entityFormatter,
101
        Config $config,
102
        CopyPhpstormMeta $copyPhpstormMeta,
103
        NamespaceHelper $namespaceHelper
104
    ) {
105 1
        $this->entityGenerator                         = $entityGenerator;
106 1
        $this->fieldGenerator                          = $fieldGenerator;
107 1
        $this->fieldSetter                             = $fieldSetter;
108 1
        $this->relationsGenerator                      = $relationsGenerator;
109 1
        $this->archetypeEmbeddableGenerator            = $archetypeEmbeddableGenerator;
110 1
        $this->embeddableSetter                        = $embeddableSetter;
111 1
        $this->codeHelper                              = $codeHelper;
112 1
        $this->unusedRelationsRemover                  = $unusedRelationsRemover;
113 1
        $this->dataTransferObjectsForAllEntitiesAction = $dataTransferObjectsForAllEntitiesAction;
114 1
        $this->entityFormatter                         = $entityFormatter;
115 1
        $this->copyPhpstormMeta                        = $copyPhpstormMeta;
116 1
        $this->namespaceHelper                         = $namespaceHelper;
117
118 1
        $this->setPathToProjectRoot($config::getProjectRootDirectory());
119 1
    }
120
121 1
    public function setPathToProjectRoot(string $pathToProjectRoot): self
122
    {
123 1
        $this->entityGenerator->setPathToProjectRoot($pathToProjectRoot);
124 1
        $this->fieldGenerator->setPathToProjectRoot($pathToProjectRoot);
125 1
        $this->fieldSetter->setPathToProjectRoot($pathToProjectRoot);
126 1
        $this->relationsGenerator->setPathToProjectRoot($pathToProjectRoot);
127 1
        $this->archetypeEmbeddableGenerator->setPathToProjectRoot($pathToProjectRoot);
128 1
        $this->unusedRelationsRemover->setPathToProjectRoot($pathToProjectRoot);
129 1
        $this->dataTransferObjectsForAllEntitiesAction->setProjectRootDirectory($pathToProjectRoot);
130 1
        $this->embeddableSetter->setPathToProjectRoot($pathToProjectRoot);
131 1
        $this->entityFormatter->setPathToProjectRoot($pathToProjectRoot);
132 1
        $this->copyPhpstormMeta->setPathToProjectRoot($pathToProjectRoot);
133
134 1
        return $this;
135
    }
136
137
    /**
138
     * @return EntityGenerator
139
     */
140
    public function getEntityGenerator(): EntityGenerator
141
    {
142
        return $this->entityGenerator;
143
    }
144
145
    /**
146
     * @return FieldGenerator
147
     */
148
    public function getFieldGenerator(): FieldGenerator
149
    {
150
        return $this->fieldGenerator;
151
    }
152
153
    /**
154
     * @return EntityFieldSetter
155
     */
156
    public function getFieldSetter(): EntityFieldSetter
157
    {
158
        return $this->fieldSetter;
159
    }
160
161
    /**
162
     * @return RelationsGenerator
163
     */
164
    public function getRelationsGenerator(): RelationsGenerator
165
    {
166
        return $this->relationsGenerator;
167
    }
168
169
    /**
170
     * @return ArchetypeEmbeddableGenerator
171
     */
172
    public function getArchetypeEmbeddableGenerator(): ArchetypeEmbeddableGenerator
173
    {
174
        return $this->archetypeEmbeddableGenerator;
175
    }
176
177
    /**
178
     * @return EntityEmbeddableSetter
179
     */
180
    public function getEmbeddableSetter(): EntityEmbeddableSetter
181
    {
182
        return $this->embeddableSetter;
183
    }
184
185
    /**
186
     * Finalise build - run various steps to wrap up the build and tidy up the codebase
187
     *
188
     * @return Builder
189
     */
190
    public function finaliseBuild(): self
191
    {
192
        $this->dataTransferObjectsForAllEntitiesAction->run();
193
        $this->entityFormatter->run();
194
        $this->copyPhpstormMeta->run();
195
196
        return $this;
197
    }
198
199
    /**
200
     * This step will remove any relations code that is not being used
201
     *
202
     * Generally it needs to be run in a separate PHP process to ensure PHP loads the final versions of code
203
     */
204
    public function removeUnusedRelations(): void
205
    {
206
        $this->unusedRelationsRemover->run();
207
    }
208
209
    /**
210
     * @param array $entityFqns
211
     *
212
     * @return Builder
213
     * @throws DoctrineStaticMetaException
214
     */
215
    public function generateEntities(array $entityFqns): self
216
    {
217
        $this->setProjectRootNamespace(
218
            $this->namespaceHelper->getProjectNamespaceRootFromEntityFqn(
219
                current($entityFqns)
220
            )
221
        );
222
        foreach ($entityFqns as $entityFqn) {
223
            $this->entityGenerator->generateEntity($entityFqn);
224
        }
225
226
        return $this;
227
    }
228
229
    public function setProjectRootNamespace(string $projectRootNamespace): self
230
    {
231
        $this->entityGenerator->setProjectRootNamespace($projectRootNamespace);
232
        $this->fieldGenerator->setProjectRootNamespace($projectRootNamespace);
233
        $this->fieldSetter->setProjectRootNamespace($projectRootNamespace);
234
        $this->relationsGenerator->setProjectRootNamespace($projectRootNamespace);
235
        $this->archetypeEmbeddableGenerator->setProjectRootNamespace($projectRootNamespace);
236
        $this->dataTransferObjectsForAllEntitiesAction->setProjectRootNamespace($projectRootNamespace);
237
        $this->embeddableSetter->setProjectRootNamespace($projectRootNamespace);
238
        $this->unusedRelationsRemover->setProjectRootNamespace($projectRootNamespace);
239
        return $this;
240
    }
241
242
    /**
243
     * @param array $entityRelationEntity
244
     *
245
     * @return Builder
246
     * @throws DoctrineStaticMetaException
247
     */
248
    public function setEntityRelations(array $entityRelationEntity): self
249
    {
250
        foreach ($entityRelationEntity as list($owningEntityFqn, $hasType, $ownedEntityFqn)) {
251
            $this->relationsGenerator->setEntityHasRelationToEntity($owningEntityFqn, $hasType, $ownedEntityFqn);
252
        }
253
254
        return $this;
255
    }
256
257
    /**
258
     * @param array $fields
259
     *
260
     * @return array $traitFqns
261
     */
262
    public function generateFields(array $fields): array
263
    {
264
        $traitFqns = [];
265
        foreach ($fields as list($fieldFqn, $fieldType)) {
266
            try {
267
                $traitFqns[] = $this->fieldGenerator->generateField($fieldFqn, $fieldType);
268
            } catch (Exception $e) {
269
                throw new RuntimeException(
270
                    'Failed building field with $fieldFqn: ' . $fieldFqn . ' and $fieldType ' . $fieldType,
271
                    $e->getCode(),
272
                    $e
273
                );
274
            }
275
        }
276
277
        return $traitFqns;
278
    }
279
280
    public function generateKeyedFields(array $fields): array
281
    {
282
        $traitFqns = [];
283
284
        $defaults = [
285
            FieldGenerator::FIELD_PHP_TYPE_KEY      => null,
286
            FieldGenerator::FIELD_DEFAULT_VAULE_KEY => null,
287
            FieldGenerator::FIELD_IS_UNIQUE_KEY     => false,
288
        ];
289
290
        foreach ($fields as $field) {
291
            /* Can not use list here as it breaks PHPMD */
292
            $combinedDefaults = $field + $defaults;
293
            $fieldFqn         = $combinedDefaults[FieldGenerator::FIELD_FQN_KEY];
294
            $fieldType        = $combinedDefaults[FieldGenerator::FIELD_TYPE_KEY];
295
            $phpType          = $combinedDefaults[FieldGenerator::FIELD_PHP_TYPE_KEY];
296
            $defaultValue     = $combinedDefaults[FieldGenerator::FIELD_DEFAULT_VAULE_KEY];
297
            $isUnique         = $combinedDefaults[FieldGenerator::FIELD_IS_UNIQUE_KEY];
298
            try {
299
                $traitFqns[] =
300
                    $this->fieldGenerator->generateField($fieldFqn, $fieldType, $phpType, $defaultValue, $isUnique);
301
            } catch (Exception $e) {
302
                throw new RuntimeException(
303
                    'Failed building field with $fieldFqn: ' . $fieldFqn . ' and $fieldType ' . $fieldType,
304
                    $e->getCode(),
305
                    $e
306
                );
307
            }
308
        }
309
310
        return $traitFqns;
311
    }
312
313
    /**
314
     * @param string $entityFqn
315
     * @param array  $fieldFqns
316
     *
317
     * @return Builder
318
     * @throws DoctrineStaticMetaException
319
     */
320
    public function setFieldsToEntity(string $entityFqn, array $fieldFqns): self
321
    {
322
        foreach ($fieldFqns as $fieldFqn) {
323
            $this->fieldSetter->setEntityHasField($entityFqn, $fieldFqn);
324
        }
325
326
        return $this;
327
    }
328
329
    /**
330
     * @param array $embeddables
331
     *
332
     * @return array $traitFqns
333
     * @throws DoctrineStaticMetaException
334
     * @throws ReflectionException
335
     */
336
    public function generateEmbeddables(array $embeddables): array
337
    {
338
        $traitFqns = [];
339
        foreach ($embeddables as $embeddable) {
340
            [$archetypeEmbeddableObjectFqn, $newEmbeddableObjectClassName] = array_values($embeddable);
341
            $traitFqns[] = $this->archetypeEmbeddableGenerator->createFromArchetype(
342
                $archetypeEmbeddableObjectFqn,
343
                $newEmbeddableObjectClassName
344
            );
345
        }
346
347
        return $traitFqns;
348
    }
349
350
    /**
351
     * @param string $entityFqn
352
     * @param array  $embeddableTraitFqns
353
     *
354
     * @return Builder
355
     */
356
    public function setEmbeddablesToEntity(string $entityFqn, array $embeddableTraitFqns): self
357
    {
358
        foreach ($embeddableTraitFqns as $embeddableTraitFqn) {
359
            $this->embeddableSetter->setEntityHasEmbeddable($entityFqn, $embeddableTraitFqn);
360
        }
361
362
        return $this;
363
    }
364
365 1
    public function setEnumOptionsOnInterface(string $interfaceFqn, array $options): void
366
    {
367 1
        $pathToInterface = (new ReflectionClass($interfaceFqn))->getFileName();
368 1
        $basename        = basename($pathToInterface);
369 1
        $classy          = substr($basename, 0, strpos($basename, 'FieldInterface'));
370 1
        $consty          = $this->codeHelper->consty($classy);
371 1
        $interface       = PhpInterface::fromFile($pathToInterface);
372 1
        $constants       = $interface->getConstants();
373
        $constants->map(static function (PhpConstant $constant) use ($interface, $consty) {
374 1
            if (0 === strpos($constant->getName(), $consty . '_OPTION')) {
375 1
                $interface->removeConstant($constant);
376
            }
377 1
            if (0 === strpos($constant->getName(), 'DEFAULT')) {
378 1
                $interface->removeConstant($constant);
379
            }
380 1
        });
381 1
        $optionConsts = [];
382 1
        foreach ($options as $option) {
383 1
            $name           = preg_replace(
384 1
                '%_{2,}%',
385 1
                '_',
386 1
                $consty . '_OPTION_' . $this->codeHelper->consty(
387 1
                    preg_replace('%[^a-z0-9]%i', '_', $option)
388
                )
389
            );
390 1
            $optionConsts[] = 'self::' . $name;
391 1
            $constant       = new PhpConstant($name, $option);
392 1
            $interface->setConstant($constant);
393
        }
394 1
        $interface->setConstant(
395 1
            new PhpConstant(
396 1
                $consty . '_OPTIONS',
397 1
                '[' . implode(",\n", $optionConsts) . ']',
398 1
                true
399
            )
400
        );
401 1
        $interface->setConstant(
402 1
            new PhpConstant(
403 1
                'DEFAULT_' . $consty,
404 1
                current($optionConsts),
405 1
                true
406
            )
407
        );
408 1
        $this->codeHelper->generate($interface, $pathToInterface);
409 1
    }
410
411
    public function injectTraitInToClass(string $traitFqn, string $classFqn): void
412
    {
413
        $classFilePath = $this->getFileName($classFqn);
414
        $class         = PhpClass::fromFile($classFilePath);
415
        $trait         = PhpTrait::fromFile($this->getFileName($traitFqn));
416
        $traits        = $class->getTraits();
417
        $exists        = array_search($traitFqn, $traits, true);
418
        if ($exists !== false) {
419
            return;
420
        }
421
        $class->addTrait($trait);
422
        $this->codeHelper->generate($class, $classFilePath);
423
    }
424
425
    private function getFileName(string $typeFqn): string
426
    {
427
        $reflectionClass = new ReflectionClass($typeFqn);
428
429
        return $reflectionClass->getFileName();
430
    }
431
432
    public function extendInterfaceWithInterface(string $interfaceToExtendFqn, string $interfaceToAddFqn): void
433
    {
434
        $toExtendFilePath = $this->getFileName($interfaceToExtendFqn);
435
        $toExtend         = PhpInterface::fromFile($toExtendFilePath);
436
        $toAdd            = PhpInterface::fromFile($this->getFileName($interfaceToAddFqn));
437
        $exists           = $toExtend->getInterfaces()->contains($interfaceToAddFqn);
438
        if ($exists !== false) {
439
            return;
440
        }
441
        $toExtend->addInterface($toAdd);
442
        $this->codeHelper->generate($toExtend, $toExtendFilePath);
443
    }
444
445
    public function removeIdTraitFromClass(string $classFqn): void
446
    {
447
        $traitFqn = "DSM\\Fields\\Traits\\PrimaryKey\\IdFieldTrait";
448
        $this->removeTraitFromClass($classFqn, $traitFqn);
449
    }
450
451
    public function removeTraitFromClass(string $classFqn, string $traitFqn): void
452
    {
453
        $classPath = $this->getFileName($classFqn);
454
        $class     = PhpClass::fromFile($classPath);
455
        $traits    = $class->getTraits();
456
        if ($class->getUseStatements()->contains($traitFqn) === true) {
457
            $class->removeUseStatement($traitFqn);
458
        }
459
        $index = array_search($traitFqn, $traits, true);
460
        if ($index === false) {
461
            $shortNameParts = explode('\\', $traitFqn);
462
            $shortName      = (string)array_pop($shortNameParts);
463
            $index          = array_search($shortName, $traits, true);
464
        }
465
        if ($index === false) {
466
            return;
467
        }
468
        unset($traits[$index]);
469
        $reflectionClass = new ReflectionClass(PhpClass::class);
470
        $property        = $reflectionClass->getProperty('traits');
471
        $property->setAccessible(true);
472
        $property->setValue($class, $traits);
473
        $this->codeHelper->generate($class, $classPath);
474
    }
475
}
476