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.
Completed
Pull Request — master (#199)
by joseph
18:28
created

RelationsGenerator::addRequiredToRelation()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 7
ccs 0
cts 6
cp 0
crap 6
rs 10
c 0
b 0
f 0
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
    public function setEntityHasRelationToEntity(
238
        string $owningEntityFqn,
239
        string $hasType,
240
        string $ownedEntityFqn,
241
        bool $requiredReciprocation = false,
242
        bool $internalUseOnly = true
243
    ): void {
244
        $reciprocate = $internalUseOnly;
245
        try {
246
            $this->validateHasType($hasType);
247
            list(
248
                $owningTraitPath,
249
                $owningInterfacePath,
250
                $reciprocatingInterfacePath,
251
                ) = $this->getPathsForOwningTraitsAndInterfaces(
252
                    $hasType,
253
                    $ownedEntityFqn
254
                );
255
            list($owningClass, , $owningClassSubDirs) = $this->parseFullyQualifiedName($owningEntityFqn);
256
            $owningClassPath = $this->pathHelper->getPathFromNameAndSubDirs(
257
                $this->pathToProjectRoot,
258
                $owningClass,
259
                $owningClassSubDirs
260
            );
261
            $this->useRelationTraitInClass($owningClassPath, $owningTraitPath);
262
            $this->useRelationInterfaceInEntityInterface($owningClassPath, $owningInterfacePath);
263
            if (\in_array($hasType, self::HAS_TYPES_RECIPROCATED, true)) {
264
                $this->useRelationInterfaceInEntityInterface($owningClassPath, $reciprocatingInterfacePath);
265
            }
266
            if (true === $reciprocate && \in_array($hasType, self::HAS_TYPES_RECIPROCATED, true)) {
267
                $inverseType = $this->getInverseHasType($hasType);
268
                $inverseType = $this->updateHasTypeForPossibleRequired($inverseType, $requiredReciprocation);
269
                $this->setEntityHasRelationToEntity(
270
                    $ownedEntityFqn,
271
                    $inverseType,
272
                    $owningEntityFqn,
273
                    /**
274
                     * Setting required reciprocation to false,
275
                     * actually it is irrelevant because reciprocation is disabled
276
                     */
277
                    false,
278
                    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
    }
289
290
    /**
291
     * @param string $hasType
292
     *
293
     * @throws \InvalidArgumentException
294
     */
295
    protected function validateHasType(string $hasType): void
296
    {
297
        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
    }
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
    protected function getPathsForOwningTraitsAndInterfaces(
322
        string $hasType,
323
        string $ownedEntityFqn
324
    ): array {
325
        try {
326
            $ownedHasName        = $this->namespaceHelper->getOwnedHasName(
327
                $hasType,
328
                $ownedEntityFqn,
329
                $this->srcSubFolderName,
330
                $this->projectRootNamespace
331
            );
332
            $reciprocatedHasName = $this->namespaceHelper->getReciprocatedHasName(
333
                $ownedEntityFqn,
334
                $this->srcSubFolderName,
335
                $this->projectRootNamespace
336
            );
337
            $owningTraitFqn      = $this->getOwningTraitFqn($hasType, $ownedEntityFqn);
338
            list($traitName, , $traitSubDirsNoEntities) = $this->parseFullyQualifiedName($owningTraitFqn);
339
            $owningTraitPath = $this->pathHelper->getPathFromNameAndSubDirs(
340
                $this->pathToProjectRoot,
341
                $traitName,
342
                $traitSubDirsNoEntities
343
            );
344
            if (!\file_exists($owningTraitPath)) {
345
                $this->generateRelationCodeForEntity($ownedEntityFqn);
346
            }
347
            $owningInterfaceFqn = $this->getOwningInterfaceFqn($hasType, $ownedEntityFqn);
348
            list($interfaceName, , $interfaceSubDirsNoEntities) = $this->parseFullyQualifiedName($owningInterfaceFqn);
349
            $owningInterfacePath        = $this->pathHelper->getPathFromNameAndSubDirs(
350
                $this->pathToProjectRoot,
351
                $interfaceName,
352
                $interfaceSubDirsNoEntities
353
            );
354
            $reciprocatingInterfacePath = \preg_replace(
355
                '%Has(Required|)' . $ownedHasName . '%',
356
                'Reciprocates' . $reciprocatedHasName,
357
                $owningInterfacePath
358
            );
359
360
            return [
361
                $owningTraitPath,
362
                $owningInterfacePath,
363
                $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
    public function getOwningTraitFqn(string $hasType, string $ownedEntityFqn): string
382
    {
383
        return $this->namespaceHelper->getOwningTraitFqn(
384
            $hasType,
385
            $ownedEntityFqn,
386
            $this->projectRootNamespace,
387
            $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
    public function getRelativePathRelationsGenerator(): \Generator
427
    {
428
        try {
429
            $recursiveIterator = new \RecursiveIteratorIterator(
430
                new \RecursiveDirectoryIterator(
431
                    \realpath(AbstractGenerator::RELATIONS_TEMPLATE_PATH),
432
                    \RecursiveDirectoryIterator::SKIP_DOTS
433
                ),
434
                \RecursiveIteratorIterator::SELF_FIRST
435
            );
436
            foreach ($recursiveIterator as $path => $fileInfo) {
437
                $relativePath = rtrim(
438
                    $this->getFilesystem()->makePathRelative(
439
                        $path,
440
                        \realpath(AbstractGenerator::RELATIONS_TEMPLATE_PATH)
441
                    ),
442
                    '/'
443
                );
444
                yield $relativePath => $fileInfo;
445
            }
446
        } finally {
447
            $recursiveIterator = null;
448
            unset($recursiveIterator);
449
        }
450
    }
451
452
    /**
453
     * @param string $hasType
454
     * @param string $ownedEntityFqn
455
     *
456
     * @return string
457
     * @throws DoctrineStaticMetaException
458
     */
459
    public function getOwningInterfaceFqn(string $hasType, string $ownedEntityFqn): string
460
    {
461
        return $this->namespaceHelper->getOwningInterfaceFqn(
462
            $hasType,
463
            $ownedEntityFqn,
464
            $this->projectRootNamespace,
465
            $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
    protected function useRelationTraitInClass(string $classPath, string $traitPath): void
479
    {
480
        try {
481
            $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
            $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
        $class->addTrait($trait);
499
        $this->codeHelper->generate($class, $classPath);
500
    }
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
    protected function useRelationInterfaceInEntityInterface(string $classPath, string $interfacePath): void
512
    {
513
        $entityFqn           = PhpClass::fromFile($classPath)->getQualifiedName();
514
        $entityInterfaceFqn  = $this->namespaceHelper->getEntityInterfaceFromEntityFqn($entityFqn);
515
        $entityInterfacePath = (new \ts\Reflection\ReflectionClass($entityInterfaceFqn))->getFileName();
516
        $entityInterface     = PhpInterface::fromFile($entityInterfacePath);
517
        $relationInterface   = PhpInterface::fromFile($interfacePath);
518
        $entityInterface->addInterface($relationInterface);
519
        $this->codeHelper->generate($entityInterface, $entityInterfacePath);
520
    }
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
    protected function getInverseHasType(string $hasType): string
532
    {
533
        switch ($hasType) {
534
            case self::HAS_ONE_TO_ONE:
535
            case self::HAS_REQUIRED_ONE_TO_ONE:
536
            case self::HAS_MANY_TO_MANY:
537
            case self::HAS_REQUIRED_MANY_TO_MANY:
538
                return \str_replace(
539
                    self::PREFIX_OWNING,
540
                    self::PREFIX_INVERSE,
541
                    $hasType
542
                );
543
544
            case self::HAS_INVERSE_ONE_TO_ONE:
545
            case self::HAS_REQUIRED_INVERSE_ONE_TO_ONE:
546
            case self::HAS_INVERSE_MANY_TO_MANY:
547
            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
            case self::HAS_MANY_TO_ONE:
555
                return self::HAS_ONE_TO_MANY;
556
557
            case self::HAS_REQUIRED_MANY_TO_ONE:
558
                return self::HAS_REQUIRED_ONE_TO_MANY;
559
560
            case self::HAS_ONE_TO_MANY:
561
                return self::HAS_MANY_TO_ONE;
562
563
            case self::HAS_REQUIRED_ONE_TO_MANY:
564
                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
    private function updateHasTypeForPossibleRequired(string $relation, bool $required): string
582
    {
583
        $inverseIsRequired = \ts\stringContains($relation, self::PREFIX_REQUIRED);
584
        if (false === $required) {
585
            if (false === $inverseIsRequired) {
0 ignored issues
show
introduced by
The condition false === $inverseIsRequired is always false.
Loading history...
586
                return $relation;
587
            }
588
589
            return $this->removeRequiredToRelation($relation);
590
        }
591
        if (true === $required) {
0 ignored issues
show
introduced by
The condition true === $required is always true.
Loading history...
592
            if (true === $inverseIsRequired) {
0 ignored issues
show
introduced by
The condition true === $inverseIsRequired is always true.
Loading history...
593
                return $relation;
594
            }
595
596
            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
    private function removeRequiredToRelation(string $relation): string
601
    {
602
        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
        return substr($relation, 8);
607
    }
608
609
    private function addRequiredToRelation(string $relation): string
610
    {
611
        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
        return self::PREFIX_REQUIRED . $relation;
616
    }
617
}
618