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.
Passed
Pull Request — master (#192)
by joseph
40:19
created

getRelativePathRelationsGenerator()   A

Complexity

Conditions 2
Paths 3

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 2

Importance

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