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 (#51)
by joseph
06:58
created

RelationsGenerator::setEntityHasRelationToEntity()   B

Complexity

Conditions 4
Paths 13

Size

Total Lines 38
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 4.0105

Importance

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