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
Push — master ( 75bdf9...8faa57 )
by joseph
83:56 queued 81:04
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
24
25
    /*******************************************************************************************************************
26
     * OneToOne - One instance of the current Entity refers to One instance of the referred Entity.
27
     */
28
    public const INTERNAL_TYPE_ONE_TO_ONE = 'OneToOne';
29
30
    /**
31
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntity/HasTemplateEntityOwningOneToOne.php
32
     */
33
    public const HAS_ONE_TO_ONE = self::PREFIX_OWNING . self::INTERNAL_TYPE_ONE_TO_ONE;
34
35
    /**
36
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntity/HasTemplateEntityInverseOneToOne.php
37
     */
38
    public const HAS_INVERSE_ONE_TO_ONE = self::PREFIX_INVERSE . self::INTERNAL_TYPE_ONE_TO_ONE;
39
40
    /**
41
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntity/HasTemplateEntityUnidrectionalOneToOne.php
42
     */
43
    public const HAS_UNIDIRECTIONAL_ONE_TO_ONE = self::PREFIX_UNIDIRECTIONAL . self::INTERNAL_TYPE_ONE_TO_ONE;
44
45
46
    /*******************************************************************************************************************
47
     * OneToMany - One instance of the current Entity has Many instances (references) to the referred Entity.
48
     */
49
    public const INTERNAL_TYPE_ONE_TO_MANY = 'OneToMany';
50
51
    /**
52
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntities/HasTemplateEntitiesOneToMany.php
53
     */
54
    public const HAS_ONE_TO_MANY = self::INTERNAL_TYPE_ONE_TO_MANY;
55
56
    /**
57
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntities/HasTemplateEntitiesOneToMany.php
58
     */
59
    public const HAS_UNIDIRECTIONAL_ONE_TO_MANY = self::PREFIX_UNIDIRECTIONAL . self::INTERNAL_TYPE_ONE_TO_MANY;
60
61
62
    /*******************************************************************************************************************
63
     * ManyToOne - Many instances of the current Entity refer to One instance of the referred Entity.
64
     */
65
    public const INTERNAL_TYPE_MANY_TO_ONE = 'ManyToOne';
66
    /**
67
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntity/HasTemplateEntityManyToOne.php
68
     */
69
    public const HAS_MANY_TO_ONE = self::INTERNAL_TYPE_MANY_TO_ONE;
70
71
    /**
72
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntity/HasTemplateEntityManyToOne.php
73
     */
74
    public const HAS_UNIDIRECTIONAL_MANY_TO_ONE = self::PREFIX_UNIDIRECTIONAL . self::INTERNAL_TYPE_MANY_TO_ONE;
75
76
77
    /*******************************************************************************************************************
78
     * ManyToMany - Many instances of the current Entity refer to Many instance of the referred Entity.
79
     */
80
    public const INTERNAL_TYPE_MANY_TO_MANY = 'ManyToMany';
81
82
    /**
83
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntities/HasTemplateEntitiesOwningManyToMany.php
84
     */
85
    public const HAS_MANY_TO_MANY = self::PREFIX_OWNING . self::INTERNAL_TYPE_MANY_TO_MANY;
86
87
    /**
88
     * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntities/HasTemplateEntitiesInverseManyToMany.php
89
     */
90
    public const HAS_INVERSE_MANY_TO_MANY = self::PREFIX_INVERSE . self::INTERNAL_TYPE_MANY_TO_MANY;
91
92
93
    /**
94
     * The full list of possible relation types
95
     */
96
    public const HAS_TYPES = [
97
        self::HAS_ONE_TO_ONE,
98
        self::HAS_INVERSE_ONE_TO_ONE,
99
        self::HAS_UNIDIRECTIONAL_ONE_TO_ONE,
100
        self::HAS_ONE_TO_MANY,
101
        self::HAS_UNIDIRECTIONAL_ONE_TO_MANY,
102
        self::HAS_MANY_TO_ONE,
103
        self::HAS_UNIDIRECTIONAL_MANY_TO_ONE,
104
        self::HAS_MANY_TO_MANY,
105
        self::HAS_INVERSE_MANY_TO_MANY,
106
    ];
107
108
    /**
109
     * Of the full list, which ones will be automatically reciprocated in the generated code
110
     */
111
    public const HAS_TYPES_RECIPROCATED = [
112
        self::HAS_ONE_TO_ONE,
113
        self::HAS_INVERSE_ONE_TO_ONE,
114
        self::HAS_ONE_TO_MANY,
115
        self::HAS_MANY_TO_ONE,
116
        self::HAS_MANY_TO_MANY,
117
        self::HAS_INVERSE_MANY_TO_MANY,
118
    ];
119
120
    /**
121
     *Of the full list, which ones are unidirectional (i.e not reciprocated)
122
     */
123
    public const HAS_TYPES_UNIDIRECTIONAL = [
124
        self::HAS_UNIDIRECTIONAL_MANY_TO_ONE,
125
        self::HAS_UNIDIRECTIONAL_ONE_TO_MANY,
126
        self::HAS_UNIDIRECTIONAL_ONE_TO_ONE,
127
    ];
128
129
    /**
130
     * Of the full list, which ones are a plural relationship, i.e they have multiple of the related entity
131
     */
132
    public const HAS_TYPES_PLURAL = [
133
        self::HAS_MANY_TO_MANY,
134
        self::HAS_INVERSE_MANY_TO_MANY,
135
        self::HAS_ONE_TO_MANY,
136
        self::HAS_UNIDIRECTIONAL_ONE_TO_MANY,
137
    ];
138
139
    /**
140
     * Set a relationship from one Entity to Another Entity.
141
     *
142
     * Also used internally to set the reciprocal side. Uses an undocumented 4th bool parameter to kill recursion.
143
     *
144
     * @param string $owningEntityFqn
145
     * @param string $hasType
146
     * @param string $ownedEntityFqn
147
     * @param bool   $reciprocate
148
     *
149
     * @throws DoctrineStaticMetaException
150
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
151
     */
152 1
    public function setEntityHasRelationToEntity(
153
        string $owningEntityFqn,
154
        string $hasType,
155
        string $ownedEntityFqn,
156
        bool $reciprocate = true
157
    ): void {
158
        try {
159 1
            $this->validateHasType($hasType);
160
            list(
161
                $owningTraitPath,
162
                $owningInterfacePath,
163
                $reciprocatingInterfacePath,
164 1
                ) = $this->getPathsForOwningTraitsAndInterfaces(
165 1
                    $hasType,
166 1
                    $ownedEntityFqn
167
                );
168 1
            list($owningClass, , $owningClassSubDirs) = $this->parseFullyQualifiedName($owningEntityFqn);
169 1
            $owningClassPath = $this->pathHelper->getPathFromNameAndSubDirs(
170 1
                $this->pathToProjectRoot,
171 1
                $owningClass,
172 1
                $owningClassSubDirs
173
            );
174 1
            $this->useRelationTraitInClass($owningClassPath, $owningTraitPath);
175 1
            $this->useRelationInterfaceInEntityInterface($owningClassPath, $owningInterfacePath);
176 1
            if (\in_array($hasType, self::HAS_TYPES_RECIPROCATED, true)) {
177 1
                $this->useRelationInterfaceInEntityInterface($owningClassPath, $reciprocatingInterfacePath);
178 1
                if (true === $reciprocate) {
179 1
                    $inverseType = $this->getInverseHasType($hasType);
180 1
                    $this->setEntityHasRelationToEntity(
181 1
                        $ownedEntityFqn,
182 1
                        $inverseType,
183 1
                        $owningEntityFqn,
184 1
                        false
185
                    );
186
                }
187
            }
188
        } catch (\Exception $e) {
189
            throw new DoctrineStaticMetaException(
190
                'Exception in ' . __METHOD__ . ': ' . $e->getMessage(),
191
                $e->getCode(),
192
                $e
193
            );
194
        }
195 1
    }
196
197
    /**
198
     * @param string $hasType
199
     *
200
     * @throws \InvalidArgumentException
201
     */
202 1
    protected function validateHasType(string $hasType): void
203
    {
204 1
        if (!\in_array($hasType, static::HAS_TYPES, true)) {
205
            throw new \InvalidArgumentException(
206
                'Invalid $hasType ' . $hasType . ', must be one of: '
207
                . \print_r(static::HAS_TYPES, true)
208
            );
209
        }
210 1
    }
211
212
    /**
213
     * Get the absolute paths for the owning traits and interfaces for the specified relation type
214
     * Will ensure that the files exists
215
     *
216
     * @param string $hasType
217
     * @param string $ownedEntityFqn
218
     *
219
     * @return array [
220
     *  $owningTraitPath,
221
     *  $owningInterfacePath,
222
     *  $reciprocatingInterfacePath
223
     * ]
224
     * @throws DoctrineStaticMetaException
225
     * @SuppressWarnings(PHPMD.StaticAccess)
226
     */
227 1
    protected function getPathsForOwningTraitsAndInterfaces(string $hasType, string $ownedEntityFqn): array
228
    {
229
        try {
230 1
            $ownedHasName        = $this->namespaceHelper->getOwnedHasName(
231 1
                $hasType,
232 1
                $ownedEntityFqn,
233 1
                $this->srcSubFolderName,
234 1
                $this->projectRootNamespace
235
            );
236 1
            $reciprocatedHasName = $this->namespaceHelper->getReciprocatedHasName(
237 1
                $ownedEntityFqn,
238 1
                $this->srcSubFolderName,
239 1
                $this->projectRootNamespace
240
            );
241 1
            $owningTraitFqn      = $this->getOwningTraitFqn($hasType, $ownedEntityFqn);
242 1
            list($traitName, , $traitSubDirsNoEntities) = $this->parseFullyQualifiedName($owningTraitFqn);
243 1
            $owningTraitPath = $this->pathHelper->getPathFromNameAndSubDirs(
244 1
                $this->pathToProjectRoot,
245 1
                $traitName,
246 1
                $traitSubDirsNoEntities
247
            );
248 1
            if (!\file_exists($owningTraitPath)) {
249
                $this->generateRelationCodeForEntity($ownedEntityFqn);
250
            }
251 1
            $owningInterfaceFqn = $this->getOwningInterfaceFqn($hasType, $ownedEntityFqn);
252 1
            list($interfaceName, , $interfaceSubDirsNoEntities) = $this->parseFullyQualifiedName($owningInterfaceFqn);
253 1
            $owningInterfacePath        = $this->pathHelper->getPathFromNameAndSubDirs(
254 1
                $this->pathToProjectRoot,
255 1
                $interfaceName,
256 1
                $interfaceSubDirsNoEntities
257
            );
258 1
            $reciprocatingInterfacePath = \str_replace(
259 1
                'Has' . $ownedHasName,
260 1
                'Reciprocates' . $reciprocatedHasName,
261 1
                $owningInterfacePath
262
            );
263
264
            return [
265 1
                $owningTraitPath,
266 1
                $owningInterfacePath,
267 1
                $reciprocatingInterfacePath,
268
            ];
269
        } catch (\Exception $e) {
270
            throw new DoctrineStaticMetaException(
271
                'Exception in ' . __METHOD__ . ': ' . $e->getMessage(),
272
                $e->getCode(),
273
                $e
274
            );
275
        }
276
    }
277
278
    /**
279
     * @param string $hasType
280
     * @param string $ownedEntityFqn
281
     *
282
     * @return string
283
     * @throws DoctrineStaticMetaException
284
     */
285 1
    public function getOwningTraitFqn(string $hasType, string $ownedEntityFqn): string
286
    {
287 1
        return $this->namespaceHelper->getOwningTraitFqn(
288 1
            $hasType,
289 1
            $ownedEntityFqn,
290 1
            $this->projectRootNamespace,
291 1
            $this->srcSubFolderName
292
        );
293
    }
294
295
    /**
296
     * Generate the relation traits for specified Entity
297
     *
298
     * This works by copying the template traits folder over and then updating the file contents, name and path
299
     *
300
     * @param string $entityFqn Fully Qualified Name of Entity
301
     *
302
     * @throws DoctrineStaticMetaException
303
     * @SuppressWarnings(PHPMD.StaticAccess)
304
     */
305
    public function generateRelationCodeForEntity(string $entityFqn): void
306
    {
307
        $invokable = new GenerateRelationCodeForEntity(
308
            $entityFqn,
309
            $this->pathToProjectRoot,
310
            $this->projectRootNamespace,
311
            $this->srcSubFolderName,
312
            $this->namespaceHelper,
313
            $this->pathHelper,
314
            $this->findAndReplaceHelper
315
        );
316
        $invokable($this->getRelativePathRelationsGenerator());
317
    }
318
319
    /**
320
     * Generator that yields relative paths of all the files in the relations template path and the SplFileInfo objects
321
     *
322
     * Use a PHP Generator to iterate over a recursive iterator iterator and then yield:
323
     * - key: string $relativePath
324
     * - value: \SplFileInfo $fileInfo
325
     *
326
     * The `finally` step unsets the recursiveIterator once everything is done
327
     *
328
     * @return \Generator
329
     */
330 1
    public function getRelativePathRelationsGenerator(): \Generator
331
    {
332
        try {
333 1
            $recursiveIterator = new \RecursiveIteratorIterator(
334 1
                new \RecursiveDirectoryIterator(
335 1
                    \realpath(AbstractGenerator::RELATIONS_TEMPLATE_PATH),
336 1
                    \RecursiveDirectoryIterator::SKIP_DOTS
337
                ),
338 1
                \RecursiveIteratorIterator::SELF_FIRST
339
            );
340 1
            foreach ($recursiveIterator as $path => $fileInfo) {
341 1
                $relativePath = rtrim(
342 1
                    $this->getFilesystem()->makePathRelative(
343 1
                        $path,
344 1
                        \realpath(AbstractGenerator::RELATIONS_TEMPLATE_PATH)
345
                    ),
346 1
                    '/'
347
                );
348 1
                yield $relativePath => $fileInfo;
349
            }
350 1
        } finally {
351 1
            $recursiveIterator = null;
352 1
            unset($recursiveIterator);
353
        }
354 1
    }
355
356
    /**
357
     * @param string $hasType
358
     * @param string $ownedEntityFqn
359
     *
360
     * @return string
361
     * @throws DoctrineStaticMetaException
362
     */
363 1
    public function getOwningInterfaceFqn(string $hasType, string $ownedEntityFqn): string
364
    {
365 1
        return $this->namespaceHelper->getOwningInterfaceFqn(
366 1
            $hasType,
367 1
            $ownedEntityFqn,
368 1
            $this->projectRootNamespace,
369 1
            $this->srcSubFolderName
370
        );
371
    }
372
373
    /**
374
     * Add the specified trait to the specified class
375
     *
376
     * @param string $classPath
377
     * @param string $traitPath
378
     *
379
     * @throws DoctrineStaticMetaException
380
     * @SuppressWarnings(PHPMD.StaticAccess)
381
     */
382 1
    protected function useRelationTraitInClass(string $classPath, string $traitPath): void
383
    {
384
        try {
385 1
            $class = PhpClass::fromFile($classPath);
386
        } catch (Error $e) {
387
            throw new DoctrineStaticMetaException(
388
                'PHP parsing error when loading class ' . $classPath . ': ' . $e->getMessage(),
389
                $e->getCode(),
390
                $e
391
            );
392
        }
393
        try {
394 1
            $trait = PhpTrait::fromFile($traitPath);
395
        } catch (Error $e) {
396
            throw new DoctrineStaticMetaException(
397
                'PHP parsing error when loading class ' . $classPath . ': ' . $e->getMessage(),
398
                $e->getCode(),
399
                $e
400
            );
401
        }
402 1
        $class->addTrait($trait);
403 1
        $this->codeHelper->generate($class, $classPath);
404 1
    }
405
406
    /**
407
     * Add the specified interface to the specified entity interface
408
     *
409
     * @param string $classPath
410
     * @param string $interfacePath
411
     *
412
     * @throws \ReflectionException
413
     * @SuppressWarnings(PHPMD.StaticAccess)
414
     */
415 1
    protected function useRelationInterfaceInEntityInterface(string $classPath, string $interfacePath): void
416
    {
417 1
        $entityFqn           = PhpClass::fromFile($classPath)->getQualifiedName();
418 1
        $entityInterfaceFqn  = $this->namespaceHelper->getEntityInterfaceFromEntityFqn($entityFqn);
419 1
        $entityInterfacePath = (new \ts\Reflection\ReflectionClass($entityInterfaceFqn))->getFileName();
420 1
        $entityInterface     = PhpInterface::fromFile($entityInterfacePath);
421 1
        $relationInterface   = PhpInterface::fromFile($interfacePath);
422 1
        $entityInterface->addInterface($relationInterface);
423 1
        $this->codeHelper->generate($entityInterface, $entityInterfacePath);
424 1
    }
425
426
    /**
427
     * Get the inverse of a hasType
428
     *
429
     * @param string $hasType
430
     *
431
     * @return string
432
     * @throws DoctrineStaticMetaException
433
     */
434 1
    protected function getInverseHasType(string $hasType): string
435
    {
436
        switch ($hasType) {
437 1
            case self::HAS_ONE_TO_ONE:
438 1
            case self::HAS_MANY_TO_MANY:
439 1
                return \str_replace(
440 1
                    self::PREFIX_OWNING,
441 1
                    self::PREFIX_INVERSE,
442 1
                    $hasType
443
                );
444
445 1
            case self::HAS_INVERSE_ONE_TO_ONE:
446 1
            case self::HAS_INVERSE_MANY_TO_MANY:
447
                return \str_replace(
448
                    self::PREFIX_INVERSE,
449
                    self::PREFIX_OWNING,
450
                    $hasType
451
                );
452
453 1
            case self::HAS_MANY_TO_ONE:
454 1
                return self::HAS_ONE_TO_MANY;
455
456 1
            case self::HAS_ONE_TO_MANY:
457 1
                return self::HAS_MANY_TO_ONE;
458
459
            default:
460
                throw new DoctrineStaticMetaException(
461
                    'invalid $hasType ' . $hasType . ' when trying to set the inverted relation'
462
                );
463
        }
464
    }
465
}
466