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
102:38 queued 99:24
created

testSetRelationsBetweenEntities()   B

Complexity

Conditions 4
Paths 11

Size

Total Lines 62
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 43
nc 11
nop 0
dl 0
loc 62
rs 8.9167
c 0
b 0
f 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\CodeGeneration\Generator;
4
5
use EdmondsCommerce\DoctrineStaticMeta\AbstractTest;
6
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Command\AbstractCommand;
7
use EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException;
8
use EdmondsCommerce\DoctrineStaticMeta\Schema\Schema;
9
10
class RelationsGeneratorTest extends AbstractTest
11
{
12
    public const WORK_DIR = AbstractTest::VAR_PATH.'/RelationsGeneratorTest/';
13
14
    public const TEST_ENTITY_BASKET = self::TEST_PROJECT_ROOT_NAMESPACE.'\\'
15
                                      .AbstractGenerator::ENTITIES_FOLDER_NAME.'\\Basket';
16
17
    public const TEST_ENTITY_BASKET_ITEM = self::TEST_PROJECT_ROOT_NAMESPACE.'\\'
18
                                           .AbstractGenerator::ENTITIES_FOLDER_NAME.'\\Basket\\Item';
19
20
    public const TEST_ENTITY_BASKET_ITEM_OFFER = self::TEST_PROJECT_ROOT_NAMESPACE.'\\'
21
                                                 .AbstractGenerator::ENTITIES_FOLDER_NAME.'\\Basket\\Item\\Offer';
22
23
    public const TEST_ENTITY_NESTED_THING = self::TEST_PROJECT_ROOT_NAMESPACE.'\\'
24
                                            .AbstractGenerator::ENTITIES_FOLDER_NAME
25
                                            .'\\GeneratedRelations\\Testing\\RelationsTestEntity';
26
27
    public const TEST_ENTITY_NESTED_THING2 = self::TEST_PROJECT_ROOT_NAMESPACE.'\\'
28
                                             .AbstractGenerator::ENTITIES_FOLDER_NAME
29
                                             .'\\GeneratedRelations\\ExtraTesting\\Test\\AnotherRelationsTestEntity';
30
31
    public const TEST_ENTITY_NAMESPACING_COMPANY = self::TEST_PROJECT_ROOT_NAMESPACE.'\\'
32
                                                   .AbstractGenerator::ENTITIES_FOLDER_NAME.'\\Company';
33
34
    public const TEST_ENTITY_NAMESPACING_SOME_CLIENT = self::TEST_PROJECT_ROOT_NAMESPACE.'\\'
35
                                                       .AbstractGenerator::ENTITIES_FOLDER_NAME.'\\Some\\Client';
36
37
    public const TEST_ENTITY_NAMESPACING_ANOTHER_CLIENT = self::TEST_PROJECT_ROOT_NAMESPACE.'\\'
38
                                                          .AbstractGenerator::ENTITIES_FOLDER_NAME.'\\Another\\Client';
39
40
    public const TEST_ENTITIES = [
41
        self::TEST_ENTITY_BASKET,
42
        self::TEST_ENTITY_BASKET_ITEM,
43
        self::TEST_ENTITY_BASKET_ITEM_OFFER,
44
        self::TEST_ENTITY_NESTED_THING,
45
        self::TEST_ENTITY_NESTED_THING2,
46
    ];
47
48
    public const TEST_ENTITIES_NAMESPACING = [
49
        self::TEST_ENTITY_NAMESPACING_COMPANY,
50
        self::TEST_ENTITY_NAMESPACING_SOME_CLIENT,
51
        self::TEST_ENTITY_NAMESPACING_ANOTHER_CLIENT,
52
    ];
53
54
    /**
55
     * @var EntityGenerator
56
     */
57
    protected $entityGenerator;
58
59
    /**
60
     * @var RelationsGenerator
61
     */
62
    protected $relationsGenerator;
63
64
    /**
65
     * @var \ReflectionClass
66
     */
67
    protected $reflection;
68
69
    /**
70
     * @var Schema
71
     */
72
    protected $schema;
73
74
    /**
75
     */
76
    public function testAllHasTypesInConstantArrays()
77
    {
78
        $hasTypes  = [];
79
        $constants = $this->getReflection()->getConstants();
80
        foreach ($constants as $constantName => $constantValue) {
81
            if (0 === strpos($constantName, 'HAS') && false === strpos($constantName, 'HAS_TYPES')) {
82
                $hasTypes[$constantName] = $constantValue;
83
            }
84
        }
85
        $hasTypesCounted                = count($hasTypes);
86
        $hasTypesDefinedInConstantArray = count(RelationsGenerator::HAS_TYPES);
87
        $fullDiff                       = function (array $arrayX, array $arrayY): array {
88
            $intersect = array_intersect($arrayX, $arrayY);
89
90
            return array_merge(array_diff($arrayX, $intersect), array_diff($arrayY, $intersect));
91
        };
92
        $this->assertSame(
93
            $hasTypesCounted,
94
            $hasTypesDefinedInConstantArray,
95
            'The number of defined in the constant array RelationsGenerator::HAS_TYPES is not correct:'
96
            ." \n\nfull diff:\n "
97
            .print_r($fullDiff($hasTypes, RelationsGenerator::HAS_TYPES), true)
98
        );
99
        $this->qaGeneratedCode();
100
    }
101
102
    /**
103
     * @return \ReflectionClass
104
     * @throws \ReflectionException
105
     */
106
    protected function getReflection(): \ReflectionClass
107
    {
108
        if (null === $this->reflection) {
109
            $this->reflection = new \ReflectionClass(RelationsGenerator::class);
110
        }
111
112
        return $this->reflection;
113
    }
114
115
    /**
116
     * @throws \ReflectionException
117
     */
118
    public function testGenerateRelations()
119
    {
120
        /**
121
         * @var \SplFileInfo $i
122
         */
123
        foreach (self::TEST_ENTITIES as $entityFqn) {
124
            foreach ($this->relationsGenerator->getRelativePathRelationsGenerator() as $relativePath => $i) {
125
                if ($i->isDir()) {
126
                    continue;
127
                }
128
                $entityRefl          = new \ReflectionClass($entityFqn);
129
                $namespace           = $entityRefl->getNamespaceName();
130
                $className           = $entityRefl->getShortName();
131
                $namespaceNoEntities = substr($namespace, strpos(
132
                    $namespace,
133
                    AbstractGenerator::ENTITIES_FOLDER_NAME
134
                ) + \strlen(AbstractGenerator::ENTITIES_FOLDER_NAME));
135
                $subPathNoEntites    = str_replace('\\', '/', $namespaceNoEntities);
136
                $plural              = ucfirst($entityFqn::getPlural());
137
                $singular            = ucfirst($entityFqn::getSingular());
138
                $relativePath        = str_replace(
139
                    ['TemplateEntity', 'TemplateEntities'],
140
                    [$singular, $plural],
141
                    $relativePath
142
                );
143
                $createdFile         = realpath(static::WORK_DIR)
144
                                       .'/'.AbstractCommand::DEFAULT_SRC_SUBFOLDER
145
                                       .'/'.AbstractGenerator::ENTITY_RELATIONS_FOLDER_NAME
146
                                       .'/'.$subPathNoEntites.'/'
147
                                       .$className.'/'.$relativePath;
148
                $this->assertNoMissedReplacements($createdFile);
149
            }
150
        }
151
        $this->qaGeneratedCode();
152
    }
153
154
    /**
155
     * Get a an array of interfaces (short names) by reading the PHP file itself
156
     *
157
     * @param string $classFilePath
158
     *
159
     * @return array
160
     */
161
    protected function getImplementedInterfacesFromClassFile(string $classFilePath): array
162
    {
163
        $contents = file_get_contents($classFilePath);
164
        preg_match('%implements([^{]+){%', $contents, $matches);
165
        if (isset($matches[1])) {
166
            return array_map('trim', explode(',', $matches[1]));
167
        }
168
169
        return [];
170
    }
171
172
    /**
173
     * Get an array of all the interfaces for a class
174
     *
175
     * @param string $classFqn
176
     *
177
     * @return array
178
     * @throws \ReflectionException
179
     */
180
    protected function getOwningEntityInterfaces(string $classFqn): array
181
    {
182
        $owningReflection = new \ReflectionClass($classFqn);
183
184
        return $this->getImplementedInterfacesFromClassFile($owningReflection->getFileName());
185
    }
186
187
    /**
188
     * Get an array of the interfaces we expect an entity to implement based on the has type
189
     *
190
     * @param string $entityFqn
191
     * @param string $hasType
192
     *
193
     * @return array
194
     */
195
    protected function getExpectedInterfacesForEntityFqn(string $entityFqn, string $hasType): array
196
    {
197
        $expectedInterfaces   = [];
198
        $expectedInterfaces[] = \in_array($hasType, RelationsGenerator::HAS_TYPES_PLURAL, true)
199
            ? 'Has'.\ucwords($entityFqn::getPlural()).'Interface'
200
            : 'Has'.\ucwords($entityFqn::getSingular()).'Interface';
201
        if (!\in_array($hasType, RelationsGenerator::HAS_TYPES_UNIDIRECTIONAL, true)
202
            || \in_array($hasType, RelationsGenerator::HAS_TYPES_RECIPROCATED, true)
203
        ) {
204
            $expectedInterfaces[] = 'Reciprocates'.\ucwords($entityFqn::getSingular()).'Interface';
205
        }
206
207
        return $expectedInterfaces;
208
    }
209
210
    /**
211
     * Get the inverse has type name, or false if there is no inverse has type
212
     *
213
     * @param string $hasType
214
     *
215
     * @return bool|mixed|null|string
216
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
217
     */
218
    protected function getInverseHasType(string $hasType)
219
    {
220
        $inverseHasType = null;
221
        switch ($hasType) {
222
            case RelationsGenerator::HAS_ONE_TO_ONE:
223
            case RelationsGenerator::HAS_MANY_TO_MANY:
224
                return \str_replace(
225
                    RelationsGenerator::PREFIX_OWNING,
226
                    RelationsGenerator::PREFIX_INVERSE,
227
                    $hasType
228
                );
229
                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...
230
231
            case RelationsGenerator::HAS_INVERSE_ONE_TO_ONE:
232
            case RelationsGenerator::HAS_INVERSE_MANY_TO_MANY:
233
                $inverseHasType = \str_replace(
234
                    RelationsGenerator::PREFIX_INVERSE,
235
                    RelationsGenerator::PREFIX_OWNING,
236
                    $hasType
237
                );
238
                break;
239
240
            case RelationsGenerator::HAS_MANY_TO_ONE:
241
                $inverseHasType = RelationsGenerator::HAS_ONE_TO_MANY;
242
                break;
243
            case RelationsGenerator::HAS_ONE_TO_MANY:
244
                $inverseHasType = RelationsGenerator::HAS_MANY_TO_ONE;
245
                break;
246
            case RelationsGenerator::HAS_UNIDIRECTIONAL_ONE_TO_ONE:
247
            case RelationsGenerator::HAS_UNIDIRECTIONAL_ONE_TO_MANY:
248
            case RelationsGenerator::HAS_UNIDIRECTIONAL_MANY_TO_ONE:
249
                $inverseHasType = false;
250
                break;
251
            default:
252
                $this->fail('Failed getting $inverseHasType for $hasType '.$hasType);
253
        }
254
255
        return $inverseHasType;
256
    }
257
258
    /**
259
     * Inspect the generated class and ensure that all required interfaces have been implemented
260
     *
261
     * @param string $owningEntityFqn
262
     * @param string $hasType
263
     * @param string $ownedEntityFqn
264
     * @param bool   $assertInverse
265
     *
266
     * @return mixed
267
     * @throws \ReflectionException
268
     * @SuppressWarnings(PHPMD)
269
     */
270
    public function assertCorrectInterfacesSet(
271
        string $owningEntityFqn,
272
        string $hasType,
273
        string $ownedEntityFqn,
274
        bool $assertInverse = true
275
    ) {
276
        $owningInterfaces   = $this->getOwningEntityInterfaces($owningEntityFqn);
277
        $expectedInterfaces = $this->getExpectedInterfacesForEntityFqn($ownedEntityFqn, $hasType);
278
279
        $missingOwningInterfaces = [];
280
        foreach ($expectedInterfaces as $expectedInterface) {
281
            if (!\in_array($expectedInterface, $owningInterfaces, true)) {
282
                $missingOwningInterfaces[] = $expectedInterface;
283
            }
284
        }
285
        $this->assertEmpty(
286
            $missingOwningInterfaces,
287
            'Entity '.$owningEntityFqn.' has some expected owning interfaces missing for hasType: '
288
            .$hasType."\n\n"
289
            .print_r($missingOwningInterfaces, true)
290
        );
291
292
        if ($assertInverse) {
293
            $inverseHasType = $this->getInverseHasType($hasType);
294
            if (false === $inverseHasType) {
295
                return;
296
            }
297
            $this->assertCorrectInterfacesSet(
298
                $ownedEntityFqn,
299
                $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

299
                /** @scrutinizer ignore-type */ $inverseHasType,
Loading history...
300
                $owningEntityFqn,
301
                false
302
            );
303
        }
304
    }
305
306
    public function testSetRelationsBetweenEntities()
307
    {
308
        $errors = [];
309
        foreach (RelationsGenerator::HAS_TYPES as $hasType) {
310
            try {
311
                if (false !== strpos($hasType, RelationsGenerator::PREFIX_INVERSE)) {
312
                    //inverse types are tested implicitly
313
                    continue;
314
                }
315
                $this->setup();
316
317
                $this->relationsGenerator->setEntityHasRelationToEntity(
318
                    self::TEST_ENTITY_BASKET,
319
                    $hasType,
320
                    self::TEST_ENTITY_BASKET_ITEM
321
                );
322
                $this->assertCorrectInterfacesSet(
323
                    self::TEST_ENTITY_BASKET,
324
                    $hasType,
325
                    self::TEST_ENTITY_BASKET_ITEM
326
                );
327
328
                $this->relationsGenerator->setEntityHasRelationToEntity(
329
                    self::TEST_ENTITY_BASKET_ITEM,
330
                    $hasType,
331
                    self::TEST_ENTITY_BASKET_ITEM_OFFER
332
                );
333
                $this->assertCorrectInterfacesSet(
334
                    self::TEST_ENTITY_BASKET_ITEM,
335
                    $hasType,
336
                    self::TEST_ENTITY_BASKET_ITEM_OFFER
337
                );
338
339
                $this->relationsGenerator->setEntityHasRelationToEntity(
340
                    self::TEST_ENTITY_NESTED_THING,
341
                    $hasType,
342
                    self::TEST_ENTITY_NESTED_THING2
343
                );
344
                $this->assertCorrectInterfacesSet(
345
                    self::TEST_ENTITY_NESTED_THING,
346
                    $hasType,
347
                    self::TEST_ENTITY_NESTED_THING2
348
                );
349
            } catch (DoctrineStaticMetaException $e) {
350
                $errors[] = [
351
                    'Failed setting relations using' =>
352
                        [
353
                            self::TEST_ENTITIES[0],
354
                            $hasType,
355
                            self::TEST_ENTITIES[1],
356
                        ],
357
                    'Exception message'              => $e->getMessage(),
358
                    'Exception trace'                => $e->getTraceAsStringRelativePath(),
359
                ];
360
            }
361
        }
362
        $this->assertEmpty(
363
            $errors,
364
            'Found '.count($errors).' errors: '
365
            .print_r($errors, true)
366
        );
367
        $this->qaGeneratedCode();
368
    }
369
370
    public function testNamingCollisions()
371
    {
372
        $this->entityGenerator = $this->getEntityGenerator();
373
374
        foreach (self::TEST_ENTITIES_NAMESPACING as $fqn) {
375
            $this->entityGenerator->generateEntity($fqn);
376
        }
377
378
        $this->assertNull($this->relationsGenerator->setEntityHasRelationToEntity(
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->relationsGenerato...AMESPACING_SOME_CLIENT) targeting EdmondsCommerce\Doctrine...tyHasRelationToEntity() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
379
            self::TEST_ENTITY_NAMESPACING_COMPANY,
380
            'OneToMany',
381
            self::TEST_ENTITY_NAMESPACING_SOME_CLIENT
382
        ));
383
384
        $this->assertNull($this->relationsGenerator->setEntityHasRelationToEntity(
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->relationsGenerato...SPACING_ANOTHER_CLIENT) targeting EdmondsCommerce\Doctrine...tyHasRelationToEntity() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
385
            self::TEST_ENTITY_NAMESPACING_COMPANY,
386
            'OneToMany',
387
            self::TEST_ENTITY_NAMESPACING_ANOTHER_CLIENT
388
        ));
389
390
        $this->getSchema()->validate();
391
    }
392
393
    public function setup()
394
    {
395
        parent::setup();
396
        $this->entityGenerator    = $this->getEntityGenerator();
397
        $this->relationsGenerator = $this->getRelationsGenerator();
398
        foreach (self::TEST_ENTITIES as $fqn) {
399
            $this->entityGenerator->generateEntity($fqn);
400
            $this->relationsGenerator->generateRelationCodeForEntity($fqn);
401
        }
402
    }
403
}
404