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 (#129)
by joseph
25:29
created

testSetRelationsBetweenEntities()   A

Complexity

Conditions 4
Paths 12

Size

Total Lines 65
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 46
dl 0
loc 65
rs 9.1781
c 0
b 0
f 0
cc 4
nc 12
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
3
namespace EdmondsCommerce\DoctrineStaticMeta\Tests\Large\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
 * @coversDefaultClass \EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\RelationsGenerator
14
 */
15
class RelationsGeneratorTest extends AbstractTest
16
{
17
    public const WORK_DIR = AbstractTest::VAR_PATH . '/' . self::TEST_TYPE_LARGE . '/RelationsGeneratorTest/';
18
19
    public const TEST_PROJECT_ROOT_NAMESPACE = parent::TEST_PROJECT_ROOT_NAMESPACE
20
                                               . '\\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
     * @large
129
     * @covers ::generateRelationCodeForEntity
130
     * @throws \ReflectionException
131
     */
132
    public function generateRelations(): void
133
    {
134
        /**
135
         * @var \SplFileInfo $i
136
         */
137
        foreach (self::TEST_ENTITIES as $entityFqn) {
138
            $entityFqn = $this->getCopiedFqn($entityFqn);
139
            foreach ($this->relationsGenerator->getRelativePathRelationsGenerator() as $relativePath => $i) {
140
                if ($i->isDir()) {
141
                    continue;
142
                }
143
                $entityRefl          = new  \ts\Reflection\ReflectionClass($entityFqn);
144
                $namespace           = $entityRefl->getNamespaceName();
145
                $className           = $entityRefl->getShortName();
146
                $namespaceNoEntities = substr(
147
                    $namespace,
148
                    strpos(
149
                        $namespace,
150
                        AbstractGenerator::ENTITIES_FOLDER_NAME
151
                    ) + \strlen(AbstractGenerator::ENTITIES_FOLDER_NAME)
152
                );
153
                $subPathNoEntites    = str_replace('\\', '/', $namespaceNoEntities);
154
                $plural              = ucfirst($entityFqn::getDoctrineStaticMeta()->getPlural());
155
                $singular            = ucfirst($entityFqn::getDoctrineStaticMeta()->getSingular());
156
                $relativePath        = str_replace(
157
                    ['TemplateEntity', 'TemplateEntities'],
158
                    [$singular, $plural],
159
                    $relativePath
160
                );
161
                $createdFile         = realpath($this->copiedWorkDir)
162
                                       . '/' . AbstractCommand::DEFAULT_SRC_SUBFOLDER
163
                                       . '/' . AbstractGenerator::ENTITY_RELATIONS_FOLDER_NAME
164
                                       . '/' . $subPathNoEntites . '/'
165
                                       . $className . '/' . $relativePath;
166
                $this->assertNoMissedReplacements($createdFile);
167
            }
168
        }
169
        $this->qaGeneratedCode();
170
    }
171
172
    /**
173
     * @test
174
     * @large
175
     * @covers ::setEntityHasRelationToEntity
176
     * @throws \ReflectionException
177
     * @throws DoctrineStaticMetaException
178
     */
179
    public function setRelationsBetweenEntities(): void
180
    {
181
        $errors = [];
182
        foreach (RelationsGenerator::HAS_TYPES as $hasType) {
183
            try {
184
                if (false !== strpos($hasType, RelationsGenerator::PREFIX_INVERSE)) {
185
                    //inverse types are tested implicitly
186
                    continue;
187
                }
188
                $this->copiedExtraSuffix = $hasType;
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
                );
196
                $this->assertCorrectInterfacesSet(
197
                    $this->getCopiedFqn(self::TEST_ENTITY_BASKET),
198
                    $hasType,
199
                    $this->getCopiedFqn(self::TEST_ENTITY_BASKET_ITEM)
200
                );
201
202
                $this->relationsGenerator->setEntityHasRelationToEntity(
203
                    $this->getCopiedFqn(self::TEST_ENTITY_BASKET),
204
                    $hasType,
205
                    $this->getCopiedFqn(self::TEST_ENTITY_BASKET_ITEM_OFFER)
206
                );
207
                $this->assertCorrectInterfacesSet(
208
                    $this->getCopiedFqn(self::TEST_ENTITY_BASKET),
209
                    $hasType,
210
                    $this->getCopiedFqn(self::TEST_ENTITY_BASKET_ITEM_OFFER)
211
                );
212
213
                $this->relationsGenerator->setEntityHasRelationToEntity(
214
                    $this->getCopiedFqn(self::TEST_ENTITY_NESTED_THING),
215
                    $hasType,
216
                    $this->getCopiedFqn(self::TEST_ENTITY_NESTED_THING2)
217
                );
218
                $this->assertCorrectInterfacesSet(
219
                    $this->getCopiedFqn(self::TEST_ENTITY_NESTED_THING),
220
                    $hasType,
221
                    $this->getCopiedFqn(self::TEST_ENTITY_NESTED_THING2)
222
                );
223
                $this->qaGeneratedCode();
224
            } catch (DoctrineStaticMetaException $e) {
225
                $errors[] = [
226
                    'Failed setting relations using' =>
227
                        [
228
                            $this->getCopiedFqn(self::TEST_ENTITIES[0]),
229
                            $hasType,
230
                            $this->getCopiedFqn(self::TEST_ENTITIES[1]),
231
                        ],
232
                    'Exception message'              => $e->getMessage(),
233
                    'Exception trace'                => $e->getTraceAsStringRelativePath(),
234
                ];
235
            }
236
        }
237
        self::assertEmpty(
238
            $errors,
239
            'Found ' . count($errors) . ' errors: '
240
            . print_r($errors, true)
241
        );
242
        $this->copiedExtraSuffix   = null;
243
        $this->copiedRootNamespace = null;
244
    }
245
246
    public function setUp()
247
    {
248
        parent::setUp();
249
        $this->entityGenerator    = $this->getEntityGenerator();
250
        $this->relationsGenerator = $this->getRelationsGenerator();
251
        if (false === self::$built) {
252
            foreach (self::TEST_ENTITIES as $fqn) {
253
                $this->entityGenerator->generateEntity($fqn);
254
                $this->relationsGenerator->generateRelationCodeForEntity($fqn);
255
            }
256
            self::$built = true;
257
        }
258
        $this->setupCopiedWorkDir();
259
        $this->relationsGenerator->setPathToProjectRoot($this->copiedWorkDir)
260
                                 ->setProjectRootNamespace($this->copiedRootNamespace);
261
    }
262
263
    /**
264
     * Inspect the generated class and ensure that all required interfaces have been implemented
265
     *
266
     * @param string $owningEntityFqn
267
     * @param string $hasType
268
     * @param string $ownedEntityFqn
269
     * @param bool   $assertInverse
270
     *
271
     * @return void
272
     * @throws \ReflectionException
273
     * @SuppressWarnings(PHPMD)
274
     */
275
    private function assertCorrectInterfacesSet(
276
        string $owningEntityFqn,
277
        string $hasType,
278
        string $ownedEntityFqn,
279
        bool $assertInverse = true
280
    ): void {
281
        $owningInterfaces   = $this->getOwningEntityInterfaces($owningEntityFqn);
282
        $expectedInterfaces = $this->getExpectedInterfacesForEntityFqn($ownedEntityFqn, $hasType);
283
284
        $missingOwningInterfaces = [];
285
        foreach ($expectedInterfaces as $expectedInterface) {
286
            if (!\in_array($expectedInterface, $owningInterfaces, true)) {
287
                $missingOwningInterfaces[] = $expectedInterface;
288
            }
289
        }
290
        self::assertEmpty(
291
            $missingOwningInterfaces,
292
            'Entity ' . $owningEntityFqn . ' has some expected owning interfaces missing for hasType: '
293
            . $hasType . "\n\n"
294
            . print_r($missingOwningInterfaces, true)
295
        );
296
297
        if ($assertInverse) {
298
            $inverseHasType = $this->getInverseHasType($hasType);
299
            if (false === $inverseHasType) {
300
                return;
301
            }
302
            $this->assertCorrectInterfacesSet(
303
                $ownedEntityFqn,
304
                $inverseHasType,
0 ignored issues
show
Bug introduced by
It seems like $inverseHasType can also be of type true; however, parameter $hasType of EdmondsCommerce\Doctrine...tCorrectInterfacesSet() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

304
                /** @scrutinizer ignore-type */ $inverseHasType,
Loading history...
305
                $owningEntityFqn,
306
                false
307
            );
308
        }
309
    }
310
311
    /**
312
     * Get an array of all the interfaces for a class
313
     *
314
     * @param string $classFqn
315
     *
316
     * @return array
317
     * @throws \ReflectionException
318
     */
319
    private function getOwningEntityInterfaces(string $classFqn): array
320
    {
321
        $owningReflection = new  \ts\Reflection\ReflectionClass($classFqn);
322
323
        return $this->getImplementedInterfacesFromClassFile($owningReflection->getFileName());
324
    }
325
326
    /**
327
     * Get a an array of interfaces (short names) by reading the PHP file itself
328
     *
329
     * @param string $classFilePath
330
     *
331
     * @return array
332
     */
333
    private function getImplementedInterfacesFromClassFile(string $classFilePath): array
334
    {
335
        $interfaceFilePath = \str_replace(
336
            [
337
                '/Entities/',
338
                '.php',
339
            ],
340
            [
341
                '/Entity/Interfaces/',
342
                'Interface.php',
343
            ],
344
            $classFilePath
345
        );
346
        $contents          = file_get_contents($interfaceFilePath);
347
        preg_match('%extends([^{]+){%', $contents, $matches);
348
        if (isset($matches[1])) {
349
            return array_map('trim', explode(',', $matches[1]));
350
        }
351
352
        return [];
353
    }
354
355
    /**
356
     * Get an array of the interfaces we expect an entity to implement based on the has type
357
     *
358
     * @param string $entityFqn
359
     * @param string $hasType
360
     *
361
     * @return array
362
     */
363
    private function getExpectedInterfacesForEntityFqn(string $entityFqn, string $hasType): array
364
    {
365
        $expectedInterfaces   = [];
366
        $expectedInterfaces[] = \in_array($hasType, RelationsGenerator::HAS_TYPES_PLURAL, true)
367
            ? 'Has' . \ucwords($entityFqn::getDoctrineStaticMeta()->getPlural()) . 'Interface'
368
            : 'Has' . \ucwords($entityFqn::getDoctrineStaticMeta()->getSingular()) . 'Interface';
369
        if (!\in_array($hasType, RelationsGenerator::HAS_TYPES_UNIDIRECTIONAL, true)
370
            || \in_array($hasType, RelationsGenerator::HAS_TYPES_RECIPROCATED, true)
371
        ) {
372
            $expectedInterfaces[] = 'Reciprocates' . \ucwords($entityFqn::getDoctrineStaticMeta()->getSingular())
373
                                    . 'Interface';
374
        }
375
376
        return $expectedInterfaces;
377
    }
378
379
    /**
380
     * Get the inverse has type name, or false if there is no inverse has type
381
     *
382
     * @param string $hasType
383
     *
384
     * @return bool|mixed|null|string
385
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
386
     */
387
    private function getInverseHasType(string $hasType)
388
    {
389
        $inverseHasType = null;
390
        switch ($hasType) {
391
            case RelationsGenerator::HAS_ONE_TO_ONE:
392
            case RelationsGenerator::HAS_MANY_TO_MANY:
393
                return \str_replace(
394
                    RelationsGenerator::PREFIX_OWNING,
395
                    RelationsGenerator::PREFIX_INVERSE,
396
                    $hasType
397
                );
398
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
399
400
            case RelationsGenerator::HAS_INVERSE_ONE_TO_ONE:
401
            case RelationsGenerator::HAS_INVERSE_MANY_TO_MANY:
402
                $inverseHasType = \str_replace(
403
                    RelationsGenerator::PREFIX_INVERSE,
404
                    RelationsGenerator::PREFIX_OWNING,
405
                    $hasType
406
                );
407
                break;
408
409
            case RelationsGenerator::HAS_MANY_TO_ONE:
410
                $inverseHasType = RelationsGenerator::HAS_ONE_TO_MANY;
411
                break;
412
            case RelationsGenerator::HAS_ONE_TO_MANY:
413
                $inverseHasType = RelationsGenerator::HAS_MANY_TO_ONE;
414
                break;
415
            case RelationsGenerator::HAS_UNIDIRECTIONAL_ONE_TO_ONE:
416
            case RelationsGenerator::HAS_UNIDIRECTIONAL_ONE_TO_MANY:
417
            case RelationsGenerator::HAS_UNIDIRECTIONAL_MANY_TO_ONE:
418
                $inverseHasType = false;
419
                break;
420
            default:
421
                $this->fail('Failed getting $inverseHasType for $hasType ' . $hasType);
422
        }
423
424
        return $inverseHasType;
425
    }
426
427
    protected function getCopiedNamespaceRoot(): string
428
    {
429
        return parent::getCopiedNamespaceRoot() . $this->copiedExtraSuffix;
430
    }
431
}
432