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.

RelationsGenerator   A
last analyzed

Complexity

Total Complexity 41

Size/Duplication

Total Lines 598
Duplicated Lines 0 %

Test Coverage

Coverage 53.26%

Importance

Changes 0
Metric Value
eloc 260
c 0
b 0
f 0
dl 0
loc 598
rs 9.1199
ccs 139
cts 261
cp 0.5326
wmc 41

13 Methods

Rating   Name   Duplication   Size   Complexity  
A validateHasType() 0 6 2
A removeRequiredToRelation() 0 7 2
A getRelativePathRelationsGenerator() 0 23 2
A getPathsForOwningTraitsAndInterfaces() 0 49 3
A useRelationInterfaceInEntityInterface() 0 9 1
A useRelationTraitInClass() 0 22 3
A setEntityHasRelationToEntity() 0 49 5
A updateHasTypeForPossibleRequired() 0 16 5
A getOwningInterfaceFqn() 0 7 1
C getInverseHasType() 0 38 13
A getOwningTraitFqn() 0 7 1
A generateRelationCodeForEntity() 0 12 1
A addRequiredToRelation() 0 7 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
declare(strict_types=1);
4
5
namespace EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator;
6
7
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Relations\GenerateRelationCodeForEntity;
8
use EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException;
9
use Exception;
10
use Generator;
11
use gossi\codegen\model\PhpClass;
12
use gossi\codegen\model\PhpInterface;
13
use gossi\codegen\model\PhpTrait;
14
use InvalidArgumentException;
15
use PhpParser\Error;
16
use RecursiveDirectoryIterator;
17
use RecursiveIteratorIterator;
18
use ReflectionException;
19
use RuntimeException;
20
use ts\Reflection\ReflectionClass;
21
22
use function file_exists;
23
use function in_array;
24
use function preg_replace;
25
use function print_r;
26
use function realpath;
27
use function str_replace;
28
29
/**
30
 * Class RelationsGenerator
31
 *
32
 * @package EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator
33
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
34
 */
35
class RelationsGenerator extends AbstractGenerator
36
{
37
    public const PREFIX_OWNING         = 'Owning';
38
    public const PREFIX_INVERSE        = 'Inverse';
39
    public const PREFIX_UNIDIRECTIONAL = 'Unidirectional';
40
    public const PREFIX_REQUIRED       = 'Required';
41
42
43
    /*******************************************************************************************************************
44
     * OneToOne - One instance of the current Entity refers to One instance of the referred Entity.
45
     */
46
    public const INTERNAL_TYPE_ONE_TO_ONE = 'OneToOne';
47
48
    /**
49
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntity/HasTemplateEntityOwningOneToOne.php
50
     */
51
    public const HAS_ONE_TO_ONE = self::PREFIX_OWNING . self::INTERNAL_TYPE_ONE_TO_ONE;
52
53
    /**
54
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasRequiredTemplateEntity/HasRequiredTemplateEntityOwningOneToOne.php
55
     */
56
    public const HAS_REQUIRED_ONE_TO_ONE = self::PREFIX_REQUIRED . self::PREFIX_OWNING . self::INTERNAL_TYPE_ONE_TO_ONE;
57
58
    /**
59
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntity/HasTemplateEntityInverseOneToOne.php
60
     */
61
    public const HAS_INVERSE_ONE_TO_ONE = self::PREFIX_INVERSE . self::INTERNAL_TYPE_ONE_TO_ONE;
62
63
    /**
64
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasRequiredTemplateEntity/HasRequiredTemplateEntityInverseOneToOne.php
65
     */
66
    public const HAS_REQUIRED_INVERSE_ONE_TO_ONE = self::PREFIX_REQUIRED .
67
                                                   self::PREFIX_INVERSE .
68
                                                   self::INTERNAL_TYPE_ONE_TO_ONE;
69
70
    /**
71
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntity/HasTemplateEntityUnidrectionalOneToOne.php
72
     */
73
    public const HAS_UNIDIRECTIONAL_ONE_TO_ONE = self::PREFIX_UNIDIRECTIONAL . self::INTERNAL_TYPE_ONE_TO_ONE;
74
75
    /**
76
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasRequiredTemplateEntity/HasRequiredTemplateEntityUnidrectionalOneToOne.php
77
     */
78
    public const HAS_REQUIRED_UNIDIRECTIONAL_ONE_TO_ONE = self::PREFIX_REQUIRED .
79
                                                          self::PREFIX_UNIDIRECTIONAL .
80
                                                          self::INTERNAL_TYPE_ONE_TO_ONE;
81
82
    /*******************************************************************************************************************
83
     * OneToMany - One instance of the current Entity has Many instances (references) to the referred Entity.
84
     */
85
    public const INTERNAL_TYPE_ONE_TO_MANY = 'OneToMany';
86
87
    /**
88
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntities/HasTemplateEntitiesOneToMany.php
89
     */
90
    public const HAS_ONE_TO_MANY = self::INTERNAL_TYPE_ONE_TO_MANY;
91
92
    /**
93
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasRequiredTemplateEntities/HasRequiredTemplateEntitiesOneToMany.php
94
     */
95
    public const HAS_REQUIRED_ONE_TO_MANY = self::PREFIX_REQUIRED . self::INTERNAL_TYPE_ONE_TO_MANY;
96
97
    /**
98
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntities/HasTemplateEntitiesOneToMany.php
99
     */
100
    public const HAS_UNIDIRECTIONAL_ONE_TO_MANY = self::PREFIX_UNIDIRECTIONAL . self::INTERNAL_TYPE_ONE_TO_MANY;
101
102
    /**
103
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasRequiredTemplateEntities/HasRequiredTemplateEntitiesOneToMany.php
104
     */
105
    public const HAS_REQUIRED_UNIDIRECTIONAL_ONE_TO_MANY = self::PREFIX_REQUIRED .
106
                                                           self::PREFIX_UNIDIRECTIONAL .
107
                                                           self::INTERNAL_TYPE_ONE_TO_MANY;
108
109
    /*******************************************************************************************************************
110
     * ManyToOne - Many instances of the current Entity refer to One instance of the referred Entity.
111
     */
112
    public const INTERNAL_TYPE_MANY_TO_ONE = 'ManyToOne';
113
114
    /**
115
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntity/HasTemplateEntityManyToOne.php
116
     */
117
    public const HAS_MANY_TO_ONE = self::INTERNAL_TYPE_MANY_TO_ONE;
118
119
    /**
120
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasRequiredTemplateEntity/HasRequiredTemplateEntityManyToOne.php
121
     */
122
    public const HAS_REQUIRED_MANY_TO_ONE = self::PREFIX_REQUIRED . self::INTERNAL_TYPE_MANY_TO_ONE;
123
124
    /**
125
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntity/HasTemplateEntityManyToOne.php
126
     */
127
    public const HAS_UNIDIRECTIONAL_MANY_TO_ONE = self::PREFIX_UNIDIRECTIONAL . self::INTERNAL_TYPE_MANY_TO_ONE;
128
129
    /**
130
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasRequiredTemplateEntity/HasRequiredTemplateEntityManyToOne.php
131
     */
132
    public const HAS_REQUIRED_UNIDIRECTIONAL_MANY_TO_ONE = self::PREFIX_REQUIRED .
133
                                                           self::PREFIX_UNIDIRECTIONAL .
134
                                                           self::INTERNAL_TYPE_MANY_TO_ONE;
135
136
137
    /*******************************************************************************************************************
138
     * ManyToMany - Many instances of the current Entity refer to Many instance of the referred Entity.
139
     */
140
    public const INTERNAL_TYPE_MANY_TO_MANY = 'ManyToMany';
141
142
    /**
143
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntities/HasTemplateEntitiesOwningManyToMany.php
144
     */
145
    public const HAS_MANY_TO_MANY = self::PREFIX_OWNING . self::INTERNAL_TYPE_MANY_TO_MANY;
146
147
    /**
148
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasRequiredTemplateEntities/HasRequiredTemplateEntitiesOwningManyToMany.php
149
     */
150
    public const HAS_REQUIRED_MANY_TO_MANY = self::PREFIX_REQUIRED .
151
                                             self::PREFIX_OWNING .
152
                                             self::INTERNAL_TYPE_MANY_TO_MANY;
153
    /**
154
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntities/HasTemplateEntitiesInverseManyToMany.php
155
     */
156
    public const HAS_INVERSE_MANY_TO_MANY = self::PREFIX_INVERSE . self::INTERNAL_TYPE_MANY_TO_MANY;
157
158
    /**
159
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasRequiredTemplateEntities/HasRequiredTemplateEntitiesInverseManyToMany.php
160
     */
161
    public const HAS_REQUIRED_INVERSE_MANY_TO_MANY = self::PREFIX_REQUIRED .
162
                                                     self::PREFIX_INVERSE .
163
                                                     self::INTERNAL_TYPE_MANY_TO_MANY;
164
165
166
    /**
167
     * The full list of possible relation types
168
     */
169
    public const HAS_TYPES = [
170
        self::HAS_ONE_TO_ONE,
171
        self::HAS_INVERSE_ONE_TO_ONE,
172
        self::HAS_UNIDIRECTIONAL_ONE_TO_ONE,
173
        self::HAS_ONE_TO_MANY,
174
        self::HAS_UNIDIRECTIONAL_ONE_TO_MANY,
175
        self::HAS_MANY_TO_ONE,
176
        self::HAS_UNIDIRECTIONAL_MANY_TO_ONE,
177
        self::HAS_MANY_TO_MANY,
178
        self::HAS_INVERSE_MANY_TO_MANY,
179
180
        self::HAS_REQUIRED_ONE_TO_ONE,
181
        self::HAS_REQUIRED_INVERSE_ONE_TO_ONE,
182
        self::HAS_REQUIRED_UNIDIRECTIONAL_ONE_TO_ONE,
183
        self::HAS_REQUIRED_ONE_TO_MANY,
184
        self::HAS_REQUIRED_UNIDIRECTIONAL_ONE_TO_MANY,
185
        self::HAS_REQUIRED_MANY_TO_ONE,
186
        self::HAS_REQUIRED_UNIDIRECTIONAL_MANY_TO_ONE,
187
        self::HAS_REQUIRED_MANY_TO_MANY,
188
        self::HAS_REQUIRED_INVERSE_MANY_TO_MANY,
189
    ];
190
191
    /**
192
     * Of the full list, which ones will be automatically reciprocated in the generated code
193
     */
194
    public const HAS_TYPES_RECIPROCATED = [
195
        self::HAS_ONE_TO_ONE,
196
        self::HAS_INVERSE_ONE_TO_ONE,
197
        self::HAS_ONE_TO_MANY,
198
        self::HAS_MANY_TO_ONE,
199
        self::HAS_MANY_TO_MANY,
200
        self::HAS_INVERSE_MANY_TO_MANY,
201
202
        self::HAS_REQUIRED_ONE_TO_ONE,
203
        self::HAS_REQUIRED_INVERSE_ONE_TO_ONE,
204
        self::HAS_REQUIRED_ONE_TO_MANY,
205
        self::HAS_REQUIRED_MANY_TO_ONE,
206
        self::HAS_REQUIRED_MANY_TO_MANY,
207
        self::HAS_REQUIRED_INVERSE_MANY_TO_MANY,
208
    ];
209
210
    /**
211
     *Of the full list, which ones are unidirectional (i.e not reciprocated)
212
     */
213
    public const HAS_TYPES_UNIDIRECTIONAL = [
214
        self::HAS_UNIDIRECTIONAL_MANY_TO_ONE,
215
        self::HAS_UNIDIRECTIONAL_ONE_TO_MANY,
216
        self::HAS_UNIDIRECTIONAL_ONE_TO_ONE,
217
218
        self::HAS_REQUIRED_UNIDIRECTIONAL_MANY_TO_ONE,
219
        self::HAS_REQUIRED_UNIDIRECTIONAL_ONE_TO_MANY,
220
        self::HAS_REQUIRED_UNIDIRECTIONAL_ONE_TO_ONE,
221
    ];
222
223
    /**
224
     * Of the full list, which ones are a plural relationship, i.e they have multiple of the related entity
225
     */
226
    public const HAS_TYPES_PLURAL = [
227
        self::HAS_MANY_TO_MANY,
228
        self::HAS_INVERSE_MANY_TO_MANY,
229
        self::HAS_ONE_TO_MANY,
230
        self::HAS_UNIDIRECTIONAL_ONE_TO_MANY,
231
232
        self::HAS_REQUIRED_MANY_TO_MANY,
233
        self::HAS_REQUIRED_INVERSE_MANY_TO_MANY,
234
        self::HAS_REQUIRED_ONE_TO_MANY,
235
        self::HAS_REQUIRED_UNIDIRECTIONAL_ONE_TO_MANY,
236
    ];
237
238
    /**
239
     * Set a relationship from one Entity to Another Entity.
240
     *
241
     * Also used internally to set the reciprocal side. Uses an undocumented 4th bool parameter to kill recursion.
242
     *
243
     * @param string $owningEntityFqn
244
     * @param string $hasType
245
     * @param string $ownedEntityFqn
246
     * @param bool   $requiredReciprocation
247
     *
248
     * You should never pass in this parameter, it is only used internally
249
     * @param bool   $internalUseOnly
250
     *
251
     * @throws DoctrineStaticMetaException
252
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
253
     */
254 1
    public function setEntityHasRelationToEntity(
255
        string $owningEntityFqn,
256
        string $hasType,
257
        string $ownedEntityFqn,
258
        bool $requiredReciprocation = false,
259
        bool $internalUseOnly = true
260
    ): void {
261 1
        $reciprocate = $internalUseOnly;
262
        try {
263 1
            $this->validateHasType($hasType);
264
            [
265
                $owningTraitPath,
266
                $owningInterfacePath,
267
                $reciprocatingInterfacePath,
268 1
            ] = $this->getPathsForOwningTraitsAndInterfaces(
269 1
                $hasType,
270 1
                $ownedEntityFqn
271
            );
272 1
            list($owningClass, , $owningClassSubDirs) = $this->parseFullyQualifiedName($owningEntityFqn);
273 1
            $owningClassPath = $this->pathHelper->getPathFromNameAndSubDirs(
274 1
                $this->pathToProjectRoot,
275 1
                $owningClass,
276 1
                $owningClassSubDirs
277
            );
278 1
            $this->useRelationTraitInClass($owningClassPath, $owningTraitPath);
279 1
            $this->useRelationInterfaceInEntityInterface($owningClassPath, $owningInterfacePath);
280 1
            if (in_array($hasType, self::HAS_TYPES_RECIPROCATED, true)) {
281 1
                $this->useRelationInterfaceInEntityInterface($owningClassPath, $reciprocatingInterfacePath);
282
            }
283 1
            if (true === $reciprocate && in_array($hasType, self::HAS_TYPES_RECIPROCATED, true)) {
284 1
                $inverseType = $this->getInverseHasType($hasType);
285 1
                $inverseType = $this->updateHasTypeForPossibleRequired($inverseType, $requiredReciprocation);
286 1
                $this->setEntityHasRelationToEntity(
287 1
                    $ownedEntityFqn,
288 1
                    $inverseType,
289 1
                    $owningEntityFqn,
290
                    /**
291
                     * Setting required reciprocation to false,
292
                     * actually it is irrelevant because reciprocation is disabled
293
                     */
294 1
                    false,
295 1
                    false
296
                );
297
            }
298
        } catch (Exception $e) {
299
            throw new DoctrineStaticMetaException(
300
                'Exception in ' . __METHOD__ . ': ' . $e->getMessage(),
301
                $e->getCode(),
302
                $e
303
            );
304
        }
305 1
    }
306
307
    /**
308
     * @param string $hasType
309
     *
310
     * @throws InvalidArgumentException
311
     */
312 1
    protected function validateHasType(string $hasType): void
313
    {
314 1
        if (!in_array($hasType, static::HAS_TYPES, true)) {
315
            throw new InvalidArgumentException(
316
                'Invalid $hasType ' . $hasType . ', must be one of: '
317
                . print_r(static::HAS_TYPES, true)
318
            );
319
        }
320 1
    }
321
322
    /**
323
     * Get the absolute paths for the owning traits and interfaces for the specified relation type
324
     * Will ensure that the files exists
325
     *
326
     * @param string $hasType
327
     * @param string $ownedEntityFqn
328
     *
329
     * @return array [
330
     *  $owningTraitPath,
331
     *  $owningInterfacePath,
332
     *  $reciprocatingInterfacePath
333
     * ]
334
     * @throws DoctrineStaticMetaException
335
     * @SuppressWarnings(PHPMD.StaticAccess)
336
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
337
     */
338 1
    protected function getPathsForOwningTraitsAndInterfaces(
339
        string $hasType,
340
        string $ownedEntityFqn
341
    ): array {
342
        try {
343 1
            $ownedHasName        = $this->namespaceHelper->getOwnedHasName(
344 1
                $hasType,
345 1
                $ownedEntityFqn,
346 1
                $this->srcSubFolderName,
347 1
                $this->projectRootNamespace
348
            );
349 1
            $reciprocatedHasName = $this->namespaceHelper->getReciprocatedHasName(
350 1
                $ownedEntityFqn,
351 1
                $this->srcSubFolderName,
352 1
                $this->projectRootNamespace
353
            );
354 1
            $owningTraitFqn      = $this->getOwningTraitFqn($hasType, $ownedEntityFqn);
355 1
            list($traitName, , $traitSubDirsNoEntities) = $this->parseFullyQualifiedName($owningTraitFqn);
356 1
            $owningTraitPath = $this->pathHelper->getPathFromNameAndSubDirs(
357 1
                $this->pathToProjectRoot,
358 1
                $traitName,
359 1
                $traitSubDirsNoEntities
360
            );
361 1
            if (!file_exists($owningTraitPath)) {
362
                $this->generateRelationCodeForEntity($ownedEntityFqn);
363
            }
364 1
            $owningInterfaceFqn = $this->getOwningInterfaceFqn($hasType, $ownedEntityFqn);
365 1
            list($interfaceName, , $interfaceSubDirsNoEntities) = $this->parseFullyQualifiedName($owningInterfaceFqn);
366 1
            $owningInterfacePath        = $this->pathHelper->getPathFromNameAndSubDirs(
367 1
                $this->pathToProjectRoot,
368 1
                $interfaceName,
369 1
                $interfaceSubDirsNoEntities
370
            );
371 1
            $reciprocatingInterfacePath = preg_replace(
372 1
                '%Has(Required|)' . $ownedHasName . '%',
373 1
                'Reciprocates' . $reciprocatedHasName,
374 1
                $owningInterfacePath
375
            );
376
377
            return [
378 1
                $owningTraitPath,
379 1
                $owningInterfacePath,
380 1
                $reciprocatingInterfacePath,
381
            ];
382
        } catch (Exception $e) {
383
            throw new DoctrineStaticMetaException(
384
                'Exception in ' . __METHOD__ . ': ' . $e->getMessage(),
385
                $e->getCode(),
386
                $e
387
            );
388
        }
389
    }
390
391
    /**
392
     * @param string $hasType
393
     * @param string $ownedEntityFqn
394
     *
395
     * @return string
396
     * @throws DoctrineStaticMetaException
397
     */
398 1
    public function getOwningTraitFqn(string $hasType, string $ownedEntityFqn): string
399
    {
400 1
        return $this->namespaceHelper->getOwningTraitFqn(
401 1
            $hasType,
402 1
            $ownedEntityFqn,
403 1
            $this->projectRootNamespace,
404 1
            $this->srcSubFolderName
405
        );
406
    }
407
408
    /**
409
     * Generate the relation traits for specified Entity
410
     *
411
     * This works by copying the template traits folder over and then updating the file contents, name and path
412
     *
413
     * @param string $entityFqn Fully Qualified Name of Entity
414
     *
415
     * @throws DoctrineStaticMetaException
416
     * @SuppressWarnings(PHPMD.StaticAccess)
417
     */
418
    public function generateRelationCodeForEntity(string $entityFqn): void
419
    {
420
        $invokable = new GenerateRelationCodeForEntity(
421
            $entityFqn,
422
            $this->pathToProjectRoot,
423
            $this->projectRootNamespace,
424
            $this->srcSubFolderName,
425
            $this->namespaceHelper,
426
            $this->pathHelper,
427
            $this->findAndReplaceHelper
428
        );
429
        $invokable($this->getRelativePathRelationsGenerator());
430
    }
431
432
    /**
433
     * Generator that yields relative paths of all the files in the relations template path and the SplFileInfo objects
434
     *
435
     * Use a PHP Generator to iterate over a recursive iterator iterator and then yield:
436
     * - key: string $relativePath
437
     * - value: \SplFileInfo $fileInfo
438
     *
439
     * The `finally` step unsets the recursiveIterator once everything is done
440
     *
441
     * @return Generator
442
     */
443 1
    public function getRelativePathRelationsGenerator(): Generator
444
    {
445
        try {
446 1
            $recursiveIterator = new RecursiveIteratorIterator(
447 1
                new RecursiveDirectoryIterator(
448 1
                    realpath(AbstractGenerator::RELATIONS_TEMPLATE_PATH),
449 1
                    RecursiveDirectoryIterator::SKIP_DOTS
450
                ),
451 1
                RecursiveIteratorIterator::SELF_FIRST
452
            );
453 1
            foreach ($recursiveIterator as $path => $fileInfo) {
454 1
                $relativePath = rtrim(
455 1
                    $this->getFilesystem()->makePathRelative(
456 1
                        $path,
457 1
                        realpath(AbstractGenerator::RELATIONS_TEMPLATE_PATH)
458
                    ),
459 1
                    '/'
460
                );
461 1
                yield $relativePath => $fileInfo;
462
            }
463 1
        } finally {
464 1
            $recursiveIterator = null;
465 1
            unset($recursiveIterator);
466
        }
467 1
    }
468
469
    /**
470
     * @param string $hasType
471
     * @param string $ownedEntityFqn
472
     *
473
     * @return string
474
     * @throws DoctrineStaticMetaException
475
     */
476 1
    public function getOwningInterfaceFqn(string $hasType, string $ownedEntityFqn): string
477
    {
478 1
        return $this->namespaceHelper->getOwningInterfaceFqn(
479 1
            $hasType,
480 1
            $ownedEntityFqn,
481 1
            $this->projectRootNamespace,
482 1
            $this->srcSubFolderName
483
        );
484
    }
485
486
    /**
487
     * Add the specified trait to the specified class
488
     *
489
     * @param string $classPath
490
     * @param string $traitPath
491
     *
492
     * @throws DoctrineStaticMetaException
493
     * @SuppressWarnings(PHPMD.StaticAccess)
494
     */
495 1
    protected function useRelationTraitInClass(string $classPath, string $traitPath): void
496
    {
497
        try {
498 1
            $class = PhpClass::fromFile($classPath);
499
        } catch (Error $e) {
500
            throw new DoctrineStaticMetaException(
501
                'PHP parsing error when loading class ' . $classPath . ': ' . $e->getMessage(),
502
                $e->getCode(),
503
                $e
504
            );
505
        }
506
        try {
507 1
            $trait = PhpTrait::fromFile($traitPath);
508
        } catch (Error $e) {
509
            throw new DoctrineStaticMetaException(
510
                'PHP parsing error when loading class ' . $classPath . ': ' . $e->getMessage(),
511
                $e->getCode(),
512
                $e
513
            );
514
        }
515 1
        $class->addTrait($trait);
516 1
        $this->codeHelper->generate($class, $classPath);
517 1
    }
518
519
    /**
520
     * Add the specified interface to the specified entity interface
521
     *
522
     * @param string $classPath
523
     * @param string $interfacePath
524
     *
525
     * @throws ReflectionException
526
     * @SuppressWarnings(PHPMD.StaticAccess)
527
     */
528 1
    protected function useRelationInterfaceInEntityInterface(string $classPath, string $interfacePath): void
529
    {
530 1
        $entityFqn           = PhpClass::fromFile($classPath)->getQualifiedName();
531 1
        $entityInterfaceFqn  = $this->namespaceHelper->getEntityInterfaceFromEntityFqn($entityFqn);
532 1
        $entityInterfacePath = (new ReflectionClass($entityInterfaceFqn))->getFileName();
533 1
        $entityInterface     = PhpInterface::fromFile($entityInterfacePath);
534 1
        $relationInterface   = PhpInterface::fromFile($interfacePath);
535 1
        $entityInterface->addInterface($relationInterface);
536 1
        $this->codeHelper->generate($entityInterface, $entityInterfacePath);
537 1
    }
538
539
    /**
540
     * Get the inverse of a hasType
541
     *
542
     * @param string $hasType
543
     *
544
     * @return string
545
     * @throws DoctrineStaticMetaException
546
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
547
     */
548 1
    protected function getInverseHasType(string $hasType): string
549
    {
550
        switch ($hasType) {
551 1
            case self::HAS_ONE_TO_ONE:
552 1
            case self::HAS_REQUIRED_ONE_TO_ONE:
553 1
            case self::HAS_MANY_TO_MANY:
554 1
            case self::HAS_REQUIRED_MANY_TO_MANY:
555 1
                return str_replace(
556 1
                    self::PREFIX_OWNING,
557 1
                    self::PREFIX_INVERSE,
558 1
                    $hasType
559
                );
560
561 1
            case self::HAS_INVERSE_ONE_TO_ONE:
562 1
            case self::HAS_REQUIRED_INVERSE_ONE_TO_ONE:
563 1
            case self::HAS_INVERSE_MANY_TO_MANY:
564 1
            case self::HAS_REQUIRED_INVERSE_MANY_TO_MANY:
565
                return str_replace(
566
                    self::PREFIX_INVERSE,
567
                    self::PREFIX_OWNING,
568
                    $hasType
569
                );
570
571 1
            case self::HAS_MANY_TO_ONE:
572 1
                return self::HAS_ONE_TO_MANY;
573
574 1
            case self::HAS_REQUIRED_MANY_TO_ONE:
575 1
                return self::HAS_REQUIRED_ONE_TO_MANY;
576
577 1
            case self::HAS_ONE_TO_MANY:
578 1
                return self::HAS_MANY_TO_ONE;
579
580 1
            case self::HAS_REQUIRED_ONE_TO_MANY:
581 1
                return self::HAS_REQUIRED_MANY_TO_ONE;
582
583
            default:
584
                throw new DoctrineStaticMetaException(
585
                    'invalid $hasType ' . $hasType . ' when trying to get the inverted relation'
586
                );
587
        }
588
    }
589
590
    /**
591
     * Take a relationship and a possibility of being required and ensure it is set as the correct relationship
592
     *
593
     * @param string $relation
594
     * @param bool   $required
595
     *
596
     * @return string
597
     */
598 1
    private function updateHasTypeForPossibleRequired(string $relation, bool $required): string
599
    {
600 1
        $inverseIsRequired = \ts\stringContains($relation, self::PREFIX_REQUIRED);
601 1
        if (false === $required) {
602 1
            if (false === $inverseIsRequired) {
0 ignored issues
show
introduced by
The condition false === $inverseIsRequired is always false.
Loading history...
603 1
                return $relation;
604
            }
605
606 1
            return $this->removeRequiredToRelation($relation);
607
        }
608 1
        if (true === $required) {
0 ignored issues
show
introduced by
The condition true === $required is always true.
Loading history...
609 1
            if (true === $inverseIsRequired) {
0 ignored issues
show
introduced by
The condition true === $inverseIsRequired is always true.
Loading history...
610 1
                return $relation;
611
            }
612
613 1
            return $this->addRequiredToRelation($relation);
614
        }
0 ignored issues
show
Bug Best Practice introduced by
The function implicitly returns null when the if condition on line 608 is false. This is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
615
    }
616
617 1
    private function removeRequiredToRelation(string $relation): string
618
    {
619 1
        if (0 !== strpos($relation, self::PREFIX_REQUIRED)) {
620
            throw new RuntimeException('Trying to remove the Required prefix, but it is not set: ' . $relation);
621
        }
622
623 1
        return substr($relation, 8);
624
    }
625
626 1
    private function addRequiredToRelation(string $relation): string
627
    {
628 1
        if (0 === strpos($relation, self::PREFIX_REQUIRED)) {
629
            throw new RuntimeException('Trying to add the Required prefix, but it is already set: ' . $relation);
630
        }
631
632 1
        return self::PREFIX_REQUIRED . $relation;
633
    }
634
}
635