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 (#69)
by joseph
14:54
created

RelationsGenerator::useRelationInterfaceInClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

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