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 (#57)
by joseph
16:59
created

RelationsGenerator::getOwningInterfaceFqn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 6
cts 6
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 2
crap 1
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
     * Generator that yields relative paths of all the files in the relations template path and the SplFileInfo objects
141
     *
142
     * Use a PHP Generator to iterate over a recursive iterator iterator and then yield:
143
     * - key: string $relativePath
144
     * - value: \SplFileInfo $fileInfo
145
     *
146
     * The `finally` step unsets the recursiveIterator once everything is done
147
     *
148
     * @return \Generator
149
     */
150 26
    public function getRelativePathRelationsGenerator(): \Generator
151
    {
152
        try {
153 26
            $recursiveIterator = new \RecursiveIteratorIterator(
154 26
                new \RecursiveDirectoryIterator(
155 26
                    \realpath(AbstractGenerator::RELATIONS_TEMPLATE_PATH),
156 26
                    \RecursiveDirectoryIterator::SKIP_DOTS
157
                ),
158 26
                \RecursiveIteratorIterator::SELF_FIRST
159
            );
160 26
            foreach ($recursiveIterator as $path => $fileInfo) {
161 26
                $relativePath = rtrim(
162 26
                    $this->getFilesystem()->makePathRelative(
163 26
                        $path,
164 26
                        \realpath(AbstractGenerator::RELATIONS_TEMPLATE_PATH)
165
                    ),
166 26
                    '/'
167
                );
168 26
                yield $relativePath => $fileInfo;
169
            }
170 26
        } finally {
171 26
            $recursiveIterator = null;
172 26
            unset($recursiveIterator);
173
        }
174 26
    }
175
176
177
    /**
178
     * Generate the relation traits for specified Entity
179
     *
180
     * This works by copying the template traits folder over and then updating the file contents, name and path
181
     *
182
     * @param string $entityFqn Fully Qualified Name of Entity
183
     *
184
     * @throws DoctrineStaticMetaException
185
     * @SuppressWarnings(PHPMD.StaticAccess)
186
     */
187 26
    public function generateRelationCodeForEntity(string $entityFqn)
188
    {
189 26
        $invokable = new GenerateRelationCodeForEntity(
190 26
            $entityFqn,
191 26
            $this->pathToProjectRoot,
192 26
            $this->projectRootNamespace,
193 26
            $this->srcSubFolderName,
194 26
            $this->codeHelper,
195 26
            $this->namespaceHelper,
196 26
            $this->pathHelper,
197 26
            $this->findAndReplaceHelper
198
        );
199 26
        $invokable($this->getRelativePathRelationsGenerator());
200 26
    }
201
202
    /**
203
     * Add the specified interface to the specified class
204
     *
205
     * @param string $classPath
206
     * @param string $interfacePath
207
     * @SuppressWarnings(PHPMD.StaticAccess)
208
     */
209 23
    protected function useRelationInterfaceInClass(string $classPath, string $interfacePath)
210
    {
211 23
        $class     = PhpClass::fromFile($classPath);
212 23
        $interface = PhpInterface::fromFile($interfacePath);
213 23
        $class->addInterface($interface);
214 23
        $this->codeHelper->generate($class, $classPath);
215 23
    }
216
217
    /**
218
     * Add the specified trait to the specified class
219
     *
220
     * @param string $classPath
221
     * @param string $traitPath
222
     * @SuppressWarnings(PHPMD.StaticAccess)
223
     */
224 23
    protected function useRelationTraitInClass(string $classPath, string $traitPath)
225
    {
226
        try {
227 23
            $class = PhpClass::fromFile($classPath);
228
        } catch (Error $e) {
229
            throw new DoctrineStaticMetaException(
230
                'PHP parsing error when loading class '.$classPath.': '.$e->getMessage(),
231
                $e->getCode(),
232
                $e
233
            );
234
        }
235
        try {
236 23
            $trait = PhpTrait::fromFile($traitPath);
237
        } catch (Error $e) {
238
            throw new DoctrineStaticMetaException(
239
                'PHP parsing error when loading class '.$classPath.': '.$e->getMessage(),
240
                $e->getCode(),
241
                $e
242
            );
243
        }
244 23
        $class->addTrait($trait);
245 23
        $this->codeHelper->generate($class, $classPath);
246 23
    }
247
248
    /**
249
     * Get the absolute paths for the owning traits and interfaces for the specified relation type
250
     * Will ensure that the files exists
251
     *
252
     * @param string $hasType
253
     * @param string $ownedEntityFqn
254
     *
255
     * @return array [
256
     *  $owningTraitPath,
257
     *  $owningInterfacePath,
258
     *  $reciprocatingInterfacePath
259
     * ]
260
     * @throws DoctrineStaticMetaException
261
     * @SuppressWarnings(PHPMD.StaticAccess)
262
     */
263 23
    protected function getPathsForOwningTraitsAndInterfaces(string $hasType, string $ownedEntityFqn): array
264
    {
265
        try {
266 23
            $ownedHasName        = $this->namespaceHelper->getOwnedHasName(
267 23
                $hasType,
268 23
                $ownedEntityFqn,
269 23
                $this->srcSubFolderName,
270 23
                $this->projectRootNamespace
271
            );
272 23
            $reciprocatedHasName = $this->namespaceHelper->getReciprocatedHasName(
273 23
                $ownedEntityFqn,
274 23
                $this->srcSubFolderName,
275 23
                $this->projectRootNamespace
276
            );
277 23
            $owningTraitFqn      = $this->getOwningTraitFqn($hasType, $ownedEntityFqn);
278 23
            list($traitName, , $traitSubDirsNoEntities) = $this->parseFullyQualifiedName($owningTraitFqn);
279 23
            $owningTraitPath = $this->pathHelper->getPathFromNameAndSubDirs(
280 23
                $this->pathToProjectRoot,
281 23
                $traitName,
282 23
                $traitSubDirsNoEntities
283
            );
284 23
            if (!\file_exists($owningTraitPath)) {
285 5
                $this->generateRelationCodeForEntity($ownedEntityFqn);
286
            }
287 23
            $owningInterfaceFqn = $this->getOwningInterfaceFqn($hasType, $ownedEntityFqn);
288 23
            list($interfaceName, , $interfaceSubDirsNoEntities) = $this->parseFullyQualifiedName($owningInterfaceFqn);
289 23
            $owningInterfacePath        = $this->pathHelper->getPathFromNameAndSubDirs(
290 23
                $this->pathToProjectRoot,
291 23
                $interfaceName,
292 23
                $interfaceSubDirsNoEntities
293
            );
294 23
            $reciprocatingInterfacePath = \str_replace(
295 23
                'Has'.$ownedHasName,
296 23
                'Reciprocates'.$reciprocatedHasName,
297 23
                $owningInterfacePath
298
            );
299
300
            return [
301 23
                $owningTraitPath,
302 23
                $owningInterfacePath,
303 23
                $reciprocatingInterfacePath,
304
            ];
305
        } catch (\Exception $e) {
306
            throw new DoctrineStaticMetaException('Exception in '.__METHOD__.': '.$e->getMessage(), $e->getCode(), $e);
307
        }
308
    }
309
310
    /**
311
     * @param string $hasType
312
     * @param string $ownedEntityFqn
313
     *
314
     * @return string
315
     * @throws DoctrineStaticMetaException
316
     */
317 23
    public function getOwningTraitFqn(string $hasType, string $ownedEntityFqn): string
318
    {
319 23
        return $this->namespaceHelper->getOwningTraitFqn(
320 23
            $hasType,
321 23
            $ownedEntityFqn,
322 23
            $this->projectRootNamespace,
323 23
            $this->srcSubFolderName
324
        );
325
    }
326
327
    /**
328
     * @param string $hasType
329
     * @param string $ownedEntityFqn
330
     *
331
     * @return string
332
     * @throws DoctrineStaticMetaException
333
     */
334 23
    public function getOwningInterfaceFqn(string $hasType, string $ownedEntityFqn): string
335
    {
336 23
        return $this->namespaceHelper->getOwningInterfaceFqn(
337 23
            $hasType,
338 23
            $ownedEntityFqn,
339 23
            $this->projectRootNamespace,
340 23
            $this->srcSubFolderName
341
        );
342
    }
343
344
    /**
345
     * @param string $hasType
346
     *
347
     * @throws \InvalidArgumentException
348
     */
349 23
    protected function validateHasType(string $hasType)
350
    {
351 23
        if (!\in_array($hasType, static::HAS_TYPES, true)) {
352
            throw new \InvalidArgumentException(
353
                'Invalid $hasType '.$hasType.', must be one of: '
354
                .\print_r(static::HAS_TYPES, true)
355
            );
356
        }
357 23
    }
358
359
    /**
360
     * Set a relationship from one Entity to Another Entity.
361
     *
362
     * Also used internally to set the reciprocal side. Uses an undocumented 4th bool parameter to kill recursion.
363
     *
364
     * @param string $owningEntityFqn
365
     * @param string $hasType
366
     * @param string $ownedEntityFqn
367
     * @param bool   $reciprocate
368
     *
369
     * @throws DoctrineStaticMetaException
370
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
371
     */
372 23
    public function setEntityHasRelationToEntity(
373
        string $owningEntityFqn,
374
        string $hasType,
375
        string $ownedEntityFqn,
376
        bool $reciprocate = true
377
    ) {
378
        try {
379 23
            $this->validateHasType($hasType);
380
            list(
381
                $owningTraitPath,
382
                $owningInterfacePath,
383
                $reciprocatingInterfacePath,
384 23
                ) = $this->getPathsForOwningTraitsAndInterfaces(
385 23
                $hasType,
386 23
                $ownedEntityFqn
387
            );
388 23
            list($owningClass, , $owningClassSubDirs) = $this->parseFullyQualifiedName($owningEntityFqn);
389 23
            $owningClassPath = $this->pathHelper->getPathFromNameAndSubDirs(
390 23
                $this->pathToProjectRoot,
391 23
                $owningClass,
392 23
                $owningClassSubDirs
393
            );
394 23
            $this->useRelationTraitInClass($owningClassPath, $owningTraitPath);
395 23
            $this->useRelationInterfaceInClass($owningClassPath, $owningInterfacePath);
396 23
            if (\in_array($hasType, self::HAS_TYPES_RECIPROCATED, true)) {
397 23
                $this->useRelationInterfaceInClass($owningClassPath, $reciprocatingInterfacePath);
398 23
                if (true === $reciprocate) {
399 23
                    $inverseType = $this->getInverseHasType($hasType);
400 23
                    $this->setEntityHasRelationToEntity(
401 23
                        $ownedEntityFqn,
402 23
                        $inverseType,
403 23
                        $owningEntityFqn,
404 23
                        false
405
                    );
406
                }
407
            }
408
        } catch (\Exception $e) {
409
            throw new DoctrineStaticMetaException('Exception in '.__METHOD__.': '.$e->getMessage(), $e->getCode(), $e);
410
        }
411 23
    }
412
413
    /**
414
     * Get the inverse of a hasType
415
     *
416
     * @param string $hasType
417
     *
418
     * @return string
419
     * @throws DoctrineStaticMetaException
420
     */
421 23
    protected function getInverseHasType(string $hasType): string
422
    {
423
        switch ($hasType) {
424 23
            case self::HAS_ONE_TO_ONE:
425 23
            case self::HAS_MANY_TO_MANY:
426 22
                return \str_replace(
427 22
                    self::PREFIX_OWNING,
428 22
                    self::PREFIX_INVERSE,
429 22
                    $hasType
430
                );
431
432 4
            case self::HAS_INVERSE_ONE_TO_ONE:
433 4
            case self::HAS_INVERSE_MANY_TO_MANY:
434
                return \str_replace(
435
                    self::PREFIX_INVERSE,
436
                    self::PREFIX_OWNING,
437
                    $hasType
438
                );
439
440 4
            case self::HAS_MANY_TO_ONE:
441 3
                return self::HAS_ONE_TO_MANY;
442
443 4
            case self::HAS_ONE_TO_MANY:
444 4
                return self::HAS_MANY_TO_ONE;
445
446
            default:
447
                throw new DoctrineStaticMetaException(
448
                    'invalid $hasType '.$hasType.' when trying to set the inverted relation'
449
                );
450
        }
451
    }
452
}
453