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 ( a48bd8...83d092 )
by joseph
16:18 queued 18s
created

RelationsGeneratorTest::getCopiedNamespaceRoot()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace EdmondsCommerce\DoctrineStaticMeta\Tests\Large\F\CodeGeneration\Generator;
4
5
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Command\AbstractCommand;
6
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\AbstractGenerator;
7
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\EntityGenerator;
8
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\RelationsGenerator;
9
use EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException;
10
use EdmondsCommerce\DoctrineStaticMeta\Tests\Assets\AbstractTest;
11
12
/**
13
 * @covers \EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\RelationsGenerator
14
 * @covers \EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Relations\GenerateRelationCodeForEntity
15
 * @large
16
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
17
 */
18
class RelationsGeneratorTest extends AbstractTest
19
{
20
    public const WORK_DIR = AbstractTest::VAR_PATH . '/' . self::TEST_TYPE_LARGE . '/RelationsGeneratorTest/';
21
22
    public const TEST_ENTITY_BASKET = self::TEST_PROJECT_ROOT_NAMESPACE . '\\'
23
                                      . AbstractGenerator::ENTITIES_FOLDER_NAME . '\\Basket';
24
25
    public const TEST_ENTITY_BASKET_ITEM = self::TEST_PROJECT_ROOT_NAMESPACE . '\\'
26
                                           . AbstractGenerator::ENTITIES_FOLDER_NAME . '\\Basket\\Item';
27
28
    public const TEST_ENTITY_BASKET_ITEM_OFFER = self::TEST_PROJECT_ROOT_NAMESPACE . '\\'
29
                                                 . AbstractGenerator::ENTITIES_FOLDER_NAME . '\\Basket\\Item\\Offer';
30
31
    public const TEST_ENTITY_NESTED_THING = self::TEST_PROJECT_ROOT_NAMESPACE . '\\'
32
                                            . AbstractGenerator::ENTITIES_FOLDER_NAME
33
                                            . '\\GeneratedRelations\\Testing\\RelationsTestEntity';
34
35
    public const TEST_ENTITY_NESTED_THING2 = self::TEST_PROJECT_ROOT_NAMESPACE . '\\'
36
                                             . AbstractGenerator::ENTITIES_FOLDER_NAME
37
                                             . '\\GeneratedRelations\\ExtraTesting\\Test\\AnotherRelationsTestEntity';
38
39
    public const TEST_ENTITY_NAMESPACING_COMPANY = self::TEST_PROJECT_ROOT_NAMESPACE . '\\'
40
                                                   . AbstractGenerator::ENTITIES_FOLDER_NAME . '\\Company';
41
42
    public const TEST_ENTITY_NAMESPACING_SOME_CLIENT = self::TEST_PROJECT_ROOT_NAMESPACE . '\\'
43
                                                       . AbstractGenerator::ENTITIES_FOLDER_NAME . '\\Some\\Client';
44
45
    public const TEST_ENTITY_NAMESPACING_ANOTHER_CLIENT = self::TEST_PROJECT_ROOT_NAMESPACE .
46
                                                          '\\'
47
                                                          .
48
                                                          AbstractGenerator::ENTITIES_FOLDER_NAME .
49
                                                          '\\Another\\Client';
50
51
    public const TEST_ENTITIES = [
52
        self::TEST_ENTITY_BASKET,
53
        self::TEST_ENTITY_BASKET_ITEM,
54
        self::TEST_ENTITY_BASKET_ITEM_OFFER,
55
        self::TEST_ENTITY_NESTED_THING,
56
        self::TEST_ENTITY_NESTED_THING2,
57
    ];
58
59
    public const TEST_ENTITIES_NAMESPACING = [
60
        self::TEST_ENTITY_NAMESPACING_COMPANY,
61
        self::TEST_ENTITY_NAMESPACING_SOME_CLIENT,
62
        self::TEST_ENTITY_NAMESPACING_ANOTHER_CLIENT,
63
    ];
64
    protected static $buildOnce = true;
65
    protected static $built     = false;
66
    /**
67
     * @var EntityGenerator
68
     */
69
    private $entityGenerator;
70
    /**
71
     * @var RelationsGenerator
72
     */
73
    private $relationsGenerator;
74
    /**
75
     * @var  \ts\Reflection\ReflectionClass
76
     */
77
    private $reflection;
78
    /**
79
     * @var string
80
     */
81
    private $copiedExtraSuffix = '';
82
83
    /**
84
     * @test
85
     * @large
86
     * @coversNothing
87
     */
88
    public function allHasTypesInConstantArrays(): void
89
    {
90
        $hasTypes  = [];
91
        $constants = $this->getReflection()->getConstants();
92
        foreach ($constants as $constantName => $constantValue) {
93
            if (0 === strpos($constantName, 'HAS') && false === strpos($constantName, 'HAS_TYPES')) {
94
                $hasTypes[$constantName] = $constantValue;
95
            }
96
        }
97
        $hasTypesCounted                = count($hasTypes);
98
        $hasTypesDefinedInConstantArray = count(RelationsGenerator::HAS_TYPES);
99
        $fullDiff                       = function (array $arrayX, array $arrayY): array {
100
            $intersect = array_intersect($arrayX, $arrayY);
101
102
            return array_merge(array_diff($arrayX, $intersect), array_diff($arrayY, $intersect));
103
        };
104
        self::assertSame(
105
            $hasTypesCounted,
106
            $hasTypesDefinedInConstantArray,
107
            'The number of defined in the constant array RelationsGenerator::HAS_TYPES is not correct:'
108
            . " \n\nfull diff:\n "
109
            . print_r($fullDiff($hasTypes, RelationsGenerator::HAS_TYPES), true)
110
        );
111
    }
112
113
    /**
114
     * @return  \ts\Reflection\ReflectionClass
115
     * @throws \ReflectionException
116
     */
117
    private function getReflection(): \ts\Reflection\ReflectionClass
118
    {
119
        if (null === $this->reflection) {
120
            $this->reflection = new  \ts\Reflection\ReflectionClass(RelationsGenerator::class);
121
        }
122
123
        return $this->reflection;
124
    }
125
126
    /**
127
     * @test
128
     * @throws DoctrineStaticMetaException
129
     * @throws \ReflectionException
130
     */
131
    public function generateRelations(): void
132
    {
133
        /**
134
         * @var \SplFileInfo $i
135
         */
136
        foreach (self::TEST_ENTITIES as $entityFqn) {
137
            $entityFqn = $this->getCopiedFqn($entityFqn);
138
            foreach ($this->relationsGenerator->getRelativePathRelationsGenerator() as $relativePath => $i) {
139
                if ($i->isDir()) {
140
                    continue;
141
                }
142
                $entityRefl          = new  \ts\Reflection\ReflectionClass($entityFqn);
143
                $namespace           = $entityRefl->getNamespaceName();
144
                $className           = $entityRefl->getShortName();
145
                $namespaceNoEntities = substr(
146
                    $namespace,
147
                    strpos(
148
                        $namespace,
149
                        AbstractGenerator::ENTITIES_FOLDER_NAME
150
                    ) + \strlen(AbstractGenerator::ENTITIES_FOLDER_NAME)
151
                );
152
                $subPathNoEntites    = str_replace('\\', '/', $namespaceNoEntities);
153
                $plural              = ucfirst($entityFqn::getDoctrineStaticMeta()->getPlural());
154
                $singular            = ucfirst($entityFqn::getDoctrineStaticMeta()->getSingular());
155
                $relativePath        = str_replace(
156
                    ['TemplateEntity', 'TemplateEntities'],
157
                    [$singular, $plural],
158
                    $relativePath
159
                );
160
                $createdFile         = realpath($this->copiedWorkDir)
161
                                       . '/' . AbstractCommand::DEFAULT_SRC_SUBFOLDER
162
                                       . '/' . AbstractGenerator::ENTITY_RELATIONS_FOLDER_NAME
163
                                       . '/' . $subPathNoEntites . '/'
164
                                       . $className . '/' . $relativePath;
165
                $this->assertNoMissedReplacements($createdFile);
166
            }
167
        }
168
        $this->qaGeneratedCode();
169
    }
170
171
    /**
172
     * @test
173
     * @large
174
     *      * @throws \ReflectionException
175
     * @throws DoctrineStaticMetaException
176
     */
177
    public function setRelationsBetweenEntities(): void
178
    {
179
        $errors = [];
180
        foreach (RelationsGenerator::HAS_TYPES as $hasType) {
181
            foreach ([true, false] as $requiredReciprocation) {
182
                try {
183
                    if (false !== strpos($hasType, RelationsGenerator::PREFIX_INVERSE)) {
184
                        //inverse types are tested implicitly
185
                        continue;
186
                    }
187
                    $this->copiedExtraSuffix =
188
                        $hasType . ($requiredReciprocation ? 'RecipRequired' : 'RecipNotRequired');
189
                    $this->setUp();
190
191
                    $this->relationsGenerator->setEntityHasRelationToEntity(
192
                        $this->getCopiedFqn(self::TEST_ENTITY_BASKET),
193
                        $hasType,
194
                        $this->getCopiedFqn(self::TEST_ENTITY_BASKET_ITEM),
195
                        $requiredReciprocation
196
                    );
197
                    $this->assertCorrectInterfacesSet(
198
                        $this->getCopiedFqn(self::TEST_ENTITY_BASKET),
199
                        $hasType,
200
                        $this->getCopiedFqn(self::TEST_ENTITY_BASKET_ITEM),
201
                        $requiredReciprocation
202
                    );
203
204
                    $this->relationsGenerator->setEntityHasRelationToEntity(
205
                        $this->getCopiedFqn(self::TEST_ENTITY_BASKET),
206
                        $hasType,
207
                        $this->getCopiedFqn(self::TEST_ENTITY_BASKET_ITEM_OFFER),
208
                        $requiredReciprocation
209
                    );
210
                    $this->assertCorrectInterfacesSet(
211
                        $this->getCopiedFqn(self::TEST_ENTITY_BASKET),
212
                        $hasType,
213
                        $this->getCopiedFqn(self::TEST_ENTITY_BASKET_ITEM_OFFER),
214
                        $requiredReciprocation
215
                    );
216
217
                    $this->relationsGenerator->setEntityHasRelationToEntity(
218
                        $this->getCopiedFqn(self::TEST_ENTITY_NESTED_THING),
219
                        $hasType,
220
                        $this->getCopiedFqn(self::TEST_ENTITY_NESTED_THING2),
221
                        $requiredReciprocation
222
                    );
223
                    $this->assertCorrectInterfacesSet(
224
                        $this->getCopiedFqn(self::TEST_ENTITY_NESTED_THING),
225
                        $hasType,
226
                        $this->getCopiedFqn(self::TEST_ENTITY_NESTED_THING2),
227
                        $requiredReciprocation
228
                    );
229
                    $this->qaGeneratedCode();
230
                } catch (DoctrineStaticMetaException $e) {
231
                    $errors[] = [
232
                        'Failed setting relations using' =>
233
                            [
234
                                $this->getCopiedFqn(self::TEST_ENTITIES[0]),
235
                                $hasType,
236
                                $this->getCopiedFqn(self::TEST_ENTITIES[1]),
237
                            ],
238
                        'Exception message'              => $e->getMessage(),
239
                        'Exception trace'                => $e->getTraceAsStringRelativePath(),
240
                    ];
241
                }
242
            }
243
        }
244
        self::assertEmpty(
245
            $errors,
246
            'Found ' . count($errors) . ' errors: '
247
            . print_r($errors, true)
248
        );
249
        $this->copiedExtraSuffix   = null;
250
        $this->copiedRootNamespace = null;
251
    }
252
253
    public function setUp()
254
    {
255
        parent::setUp();
256
        $this->entityGenerator    = $this->getEntityGenerator();
257
        $this->relationsGenerator = $this->getRelationsGenerator();
258
        if (false === self::$built) {
259
            foreach (self::TEST_ENTITIES as $fqn) {
260
                $this->entityGenerator->generateEntity($fqn);
261
                $this->relationsGenerator->generateRelationCodeForEntity($fqn);
262
            }
263
            self::$built = true;
264
        }
265
        $this->setupCopiedWorkDir();
266
        $this->relationsGenerator->setPathToProjectRoot($this->copiedWorkDir)
267
                                 ->setProjectRootNamespace($this->copiedRootNamespace);
268
    }
269
270
    /**
271
     * Inspect the generated class and ensure that all required interfaces have been implemented
272
     *
273
     * @param string $owningEntityFqn
274
     * @param string $hasType
275
     * @param string $ownedEntityFqn
276
     * @param bool   $requiredReciprocation
277
     * @param bool   $assertInverse
278
     *
279
     * @return void
280
     * @throws \ReflectionException
281
     * @SuppressWarnings(PHPMD)
282
     */
283
    private function assertCorrectInterfacesSet(
284
        string $owningEntityFqn,
285
        string $hasType,
286
        string $ownedEntityFqn,
287
        bool $requiredReciprocation,
288
        bool $assertInverse = true
289
    ): void {
290
        $owningInterfaces   = $this->getOwningEntityInterfaces($owningEntityFqn);
291
        $expectedInterfaces = $this->getExpectedInterfacesForEntityFqn($ownedEntityFqn, $hasType);
292
293
        $missingOwningInterfaces = [];
294
        foreach ($expectedInterfaces as $expectedInterface) {
295
            if (!\in_array($expectedInterface, $owningInterfaces, true)) {
296
                $missingOwningInterfaces[] = $expectedInterface;
297
            }
298
        }
299
        self::assertEmpty(
300
            $missingOwningInterfaces,
301
            'Entity ' . $owningEntityFqn . ' has some expected owning interfaces missing for hasType: '
302
            . $hasType . "\n\n"
303
            . print_r($missingOwningInterfaces, true)
304
        );
305
306
        if ($assertInverse) {
307
            $inverseHasType = $this->getInverseHasType($hasType, $requiredReciprocation);
308
            if (null === $inverseHasType) {
309
                return;
310
            }
311
            $this->assertCorrectInterfacesSet(
312
                $ownedEntityFqn,
313
                $inverseHasType,
314
                $owningEntityFqn,
315
                false,
316
                false
317
            );
318
        }
319
    }
320
321
    /**
322
     * Get an array of all the interfaces for a class
323
     *
324
     * @param string $classFqn
325
     *
326
     * @return array
327
     * @throws \ReflectionException
328
     */
329
    private function getOwningEntityInterfaces(string $classFqn): array
330
    {
331
        $owningReflection = new  \ts\Reflection\ReflectionClass($classFqn);
332
333
        return $this->getImplementedInterfacesFromClassFile($owningReflection->getFileName());
334
    }
335
336
    /**
337
     * Get a an array of interfaces (short names) by reading the PHP file itself
338
     *
339
     * @param string $classFilePath
340
     *
341
     * @return array
342
     */
343
    private function getImplementedInterfacesFromClassFile(string $classFilePath): array
344
    {
345
        $interfaceFilePath = \str_replace(
346
            [
347
                '/Entities/',
348
                '.php',
349
            ],
350
            [
351
                '/Entity/Interfaces/',
352
                'Interface.php',
353
            ],
354
            $classFilePath
355
        );
356
        $contents          = file_get_contents($interfaceFilePath);
357
        preg_match('%extends([^{]+){%', $contents, $matches);
358
        if (isset($matches[1])) {
359
            return array_map('trim', explode(',', $matches[1]));
360
        }
361
362
        return [];
363
    }
364
365
    /**
366
     * Get an array of the interfaces we expect an entity to implement based on the has type
367
     *
368
     * @param string $entityFqn
369
     * @param string $hasType
370
     *
371
     * @return array
372
     */
373
    private function getExpectedInterfacesForEntityFqn(string $entityFqn, string $hasType): array
374
    {
375
        $expectedInterfaces   = [];
376
        $required             = (
377
        0 === strpos($hasType, RelationsGenerator::PREFIX_REQUIRED)
378
            ? RelationsGenerator::PREFIX_REQUIRED
379
            : ''
380
        );
381
        $expectedInterfaces[] = \in_array($hasType, RelationsGenerator::HAS_TYPES_PLURAL, true)
382
            ? 'Has' . $required . \ucwords($entityFqn::getDoctrineStaticMeta()->getPlural()) . 'Interface'
383
            : 'Has' . $required . \ucwords($entityFqn::getDoctrineStaticMeta()->getSingular()) . 'Interface';
384
        if (!\in_array($hasType, RelationsGenerator::HAS_TYPES_UNIDIRECTIONAL, true)
385
            || \in_array($hasType, RelationsGenerator::HAS_TYPES_RECIPROCATED, true)
386
        ) {
387
            $expectedInterfaces[] = 'Reciprocates' . \ucwords($entityFqn::getDoctrineStaticMeta()->getSingular())
388
                                    . 'Interface';
389
        }
390
391
        return $expectedInterfaces;
392
    }
393
394
    /**
395
     * Get the inverse has type name, or false if there is no inverse has type
396
     *
397
     * @param string $hasType
398
     *
399
     * @return string|false
400
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
401
     * @SuppressWarnings(PHPMD.NPathComplexity)
402
     */
403
    private function getInverseHasType(string $hasType, bool $requiredReciprocation): ?string
404
    {
405
        $inverseHasType = null;
406
        switch ($hasType) {
407
            case RelationsGenerator::HAS_ONE_TO_ONE:
408
            case RelationsGenerator::HAS_MANY_TO_MANY:
409
            case RelationsGenerator::HAS_REQUIRED_ONE_TO_ONE:
410
            case RelationsGenerator::HAS_REQUIRED_MANY_TO_MANY:
411
                $inverseHasType = \str_replace(
412
                    RelationsGenerator::PREFIX_OWNING,
413
                    RelationsGenerator::PREFIX_INVERSE,
414
                    $hasType
415
                );
416
                break;
417
418
            case RelationsGenerator::HAS_INVERSE_ONE_TO_ONE:
419
            case RelationsGenerator::HAS_INVERSE_MANY_TO_MANY:
420
            case RelationsGenerator::HAS_REQUIRED_INVERSE_ONE_TO_ONE:
421
            case RelationsGenerator::HAS_REQUIRED_INVERSE_MANY_TO_MANY:
422
                $inverseHasType = \str_replace(
423
                    RelationsGenerator::PREFIX_INVERSE,
424
                    RelationsGenerator::PREFIX_OWNING,
425
                    $hasType
426
                );
427
                break;
428
429
            case RelationsGenerator::HAS_MANY_TO_ONE:
430
            case RelationsGenerator::HAS_REQUIRED_MANY_TO_ONE:
431
                $inverseHasType = RelationsGenerator::HAS_ONE_TO_MANY;
432
                break;
433
            case RelationsGenerator::HAS_ONE_TO_MANY:
434
            case RelationsGenerator::HAS_REQUIRED_ONE_TO_MANY:
435
                $inverseHasType = RelationsGenerator::HAS_MANY_TO_ONE;
436
                break;
437
            case RelationsGenerator::HAS_UNIDIRECTIONAL_ONE_TO_ONE:
438
            case RelationsGenerator::HAS_UNIDIRECTIONAL_ONE_TO_MANY:
439
            case RelationsGenerator::HAS_UNIDIRECTIONAL_MANY_TO_ONE:
440
            case RelationsGenerator::HAS_REQUIRED_UNIDIRECTIONAL_ONE_TO_ONE:
441
            case RelationsGenerator::HAS_REQUIRED_UNIDIRECTIONAL_ONE_TO_MANY:
442
            case RelationsGenerator::HAS_REQUIRED_UNIDIRECTIONAL_MANY_TO_ONE:
443
                return null;
444
            default:
445
                $this->fail('Failed getting $inverseHasType for $hasType ' . $hasType);
446
        }
447
        if (true === $requiredReciprocation && 0 === strpos($inverseHasType, RelationsGenerator::PREFIX_REQUIRED)) {
448
            return $inverseHasType;
449
        }
450
        if (true === $requiredReciprocation && 0 !== strpos($inverseHasType, RelationsGenerator::PREFIX_REQUIRED)) {
451
            return RelationsGenerator::PREFIX_REQUIRED . $inverseHasType;
452
        }
453
        if (false === $requiredReciprocation && 0 !== strpos($inverseHasType, RelationsGenerator::PREFIX_REQUIRED)) {
454
            return $inverseHasType;
455
        }
456
        if (false === $requiredReciprocation && 0 === strpos($inverseHasType, RelationsGenerator::PREFIX_REQUIRED)) {
457
            return substr($inverseHasType, 8);
458
        }
459
    }
460
461
    protected function getCopiedNamespaceRoot(): string
462
    {
463
        return parent::getCopiedNamespaceRoot() . $this->copiedExtraSuffix;
464
    }
465
}
466