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

RelationsGeneratorIntegrationTest::testNamingCollisions()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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

330
                /** @scrutinizer ignore-type */ $inverseHasType,
Loading history...
331
                $owningEntityFqn,
332
                false
333
            );
334
        }
335
    }
336
337
    protected function getCopiedNamespaceRoot(): string
338
    {
339
        return parent::getCopiedNamespaceRoot().$this->copiedExtraSuffix;
340
    }
341
342
    public function testSetRelationsBetweenEntities()
343
    {
344
        $errors = [];
345
        foreach (RelationsGenerator::HAS_TYPES as $hasType) {
346
            try {
347
                if (false !== strpos($hasType, RelationsGenerator::PREFIX_INVERSE)) {
348
                    //inverse types are tested implicitly
349
                    continue;
350
                }
351
                $this->built             = false;
352
                $this->copiedExtraSuffix = $hasType;
353
                $this->setup();
354
355
                $this->relationsGenerator->setEntityHasRelationToEntity(
356
                    $this->getCopiedFqn(self::TEST_ENTITY_BASKET),
357
                    $hasType,
358
                    $this->getCopiedFqn(self::TEST_ENTITY_BASKET_ITEM)
359
                );
360
                $this->assertCorrectInterfacesSet(
361
                    $this->getCopiedFqn(self::TEST_ENTITY_BASKET),
362
                    $hasType,
363
                    $this->getCopiedFqn(self::TEST_ENTITY_BASKET_ITEM)
364
                );
365
366
                $this->relationsGenerator->setEntityHasRelationToEntity(
367
                    $this->getCopiedFqn(self::TEST_ENTITY_BASKET),
368
                    $hasType,
369
                    $this->getCopiedFqn(self::TEST_ENTITY_BASKET_ITEM_OFFER)
370
                );
371
                $this->assertCorrectInterfacesSet(
372
                    $this->getCopiedFqn(self::TEST_ENTITY_BASKET),
373
                    $hasType,
374
                    $this->getCopiedFqn(self::TEST_ENTITY_BASKET_ITEM_OFFER)
375
                );
376
377
                $this->relationsGenerator->setEntityHasRelationToEntity(
378
                    $this->getCopiedFqn(self::TEST_ENTITY_NESTED_THING),
379
                    $hasType,
380
                    $this->getCopiedFqn(self::TEST_ENTITY_NESTED_THING2)
381
                );
382
                $this->assertCorrectInterfacesSet(
383
                    $this->getCopiedFqn(self::TEST_ENTITY_NESTED_THING),
384
                    $hasType,
385
                    $this->getCopiedFqn(self::TEST_ENTITY_NESTED_THING2)
386
                );
387
                $this->qaGeneratedCode();
388
            } catch (DoctrineStaticMetaException $e) {
389
                $errors[] = [
390
                    'Failed setting relations using' =>
391
                        [
392
                            $this->getCopiedFqn(self::TEST_ENTITIES[0]),
393
                            $hasType,
394
                            $this->getCopiedFqn(self::TEST_ENTITIES[1]),
395
                        ],
396
                    'Exception message'              => $e->getMessage(),
397
                    'Exception trace'                => $e->getTraceAsStringRelativePath(),
398
                ];
399
            }
400
        }
401
        $this->assertEmpty(
402
            $errors,
403
            'Found '.count($errors).' errors: '
404
            .print_r($errors, true)
405
        );
406
        $this->copiedExtraSuffix   = null;
407
        $this->copiedRootNamespace = null;
408
    }
409
}
410