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

assertAllAssociationsAreNotEmpty()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 29
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
eloc 23
dl 0
loc 29
ccs 0
cts 24
cp 0
rs 9.552
c 0
b 0
f 0
cc 3
nc 3
nop 1
crap 12
1
<?php declare(strict_types=1);
2
3
namespace EdmondsCommerce\DoctrineStaticMeta\Entity\Testing;
4
5
use Doctrine\Common\Cache\ArrayCache;
6
use Doctrine\Common\Inflector\Inflector;
7
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
8
use Doctrine\ORM\EntityManagerInterface;
9
use Doctrine\ORM\Mapping\ClassMetadata;
10
use Doctrine\ORM\Tools\SchemaValidator;
11
use Doctrine\ORM\Utility\PersisterHelper;
12
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\CodeHelper;
13
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\RelationsGenerator;
14
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\NamespaceHelper;
15
use EdmondsCommerce\DoctrineStaticMeta\Config;
16
use EdmondsCommerce\DoctrineStaticMeta\ConfigInterface;
17
use EdmondsCommerce\DoctrineStaticMeta\Entity\Embeddable\Objects\AbstractEmbeddableObject;
18
use EdmondsCommerce\DoctrineStaticMeta\Entity\Factory\EntityFactory;
19
use EdmondsCommerce\DoctrineStaticMeta\Entity\Interfaces\EntityInterface;
20
use EdmondsCommerce\DoctrineStaticMeta\Entity\Savers\EntitySaver;
21
use EdmondsCommerce\DoctrineStaticMeta\Entity\Savers\EntitySaverFactory;
22
use EdmondsCommerce\DoctrineStaticMeta\Entity\Validation\EntityValidatorFactory;
23
use EdmondsCommerce\DoctrineStaticMeta\EntityManager\EntityManagerFactory;
24
use EdmondsCommerce\DoctrineStaticMeta\Exception\ConfigException;
25
use EdmondsCommerce\DoctrineStaticMeta\SimpleEnv;
26
use PHPUnit\Framework\TestCase;
27
use Symfony\Component\Validator\Mapping\Cache\DoctrineCache;
28
29
/**
30
 * Class AbstractEntityTest
31
 *
32
 * This abstract test is designed to give you a good level of test coverage for your entities without any work required.
33
 *
34
 * You should extend the test with methods that test your specific business logic, your validators and anything else.
35
 *
36
 * You can override the methods, properties and constants as you see fit.
37
 *
38
 * @package EdmondsCommerce\DoctrineStaticMeta\Entity
39
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
40
 * @SuppressWarnings(PHPMD.NumberOfChildren)
41
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
42
 */
43
abstract class AbstractEntityTest extends TestCase implements EntityTestInterface
44
{
45
    /**
46
     * The fully qualified name of the Entity being tested, as calculated by the test class name
47
     *
48
     * @var string
49
     */
50
    protected $testedEntityFqn;
51
52
    /**
53
     * Reflection of the tested entity
54
     *
55
     * @var \ts\Reflection\ReflectionClass
56
     */
57
    protected $testedEntityReflectionClass;
58
59
60
    /**
61
     * @var EntityValidatorFactory
62
     */
63
    protected $entityValidatorFactory;
64
65
    /**
66
     * @var EntityManagerInterface
67
     */
68
    protected $entityManager;
69
70
    /**
71
     * @var array
72
     */
73
    protected $schemaErrors = [];
74
75
76
    /**
77
     * @var TestEntityGenerator
78
     */
79
    protected $testEntityGenerator;
80
81
    /**
82
     * @var EntitySaverFactory
83
     */
84
    protected $entitySaverFactory;
85
86
    /**
87
     * @var CodeHelper
88
     */
89
    protected $codeHelper;
90
91
    /**
92
     * @var EntityDebugDumper
93
     */
94
    protected $dumper;
95
96
    /**
97
     * @throws ConfigException
98
     * @throws \Exception
99
     * @SuppressWarnings(PHPMD.StaticAccess)
100
     */
101
    protected function setup()
102
    {
103
        $this->getEntityManager(true);
104
        $this->entityValidatorFactory = new EntityValidatorFactory(new DoctrineCache(new ArrayCache()));
105
        $this->entitySaverFactory     = new EntitySaverFactory(
106
            $this->entityManager,
107
            new EntitySaver($this->entityManager),
108
            new NamespaceHelper()
109
        );
110
        $this->testEntityGenerator    = new TestEntityGenerator(
111
            static::SEED,
112
            static::FAKER_DATA_PROVIDERS,
113
            $this->getTestedEntityReflectionClass(),
114
            $this->entitySaverFactory,
115
            $this->entityValidatorFactory
116
        );
117
        $this->codeHelper             = new CodeHelper(new NamespaceHelper());
118
        $this->dumper                 = new EntityDebugDumper();
119
    }
120
121
    /**
122
     * Use Doctrine's standard schema validation to get errors for the whole schema
123
     *
124
     * @param bool $update
125
     *
126
     * @return array
127
     * @throws \Exception
128
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
129
     */
130
    protected function getSchemaErrors(bool $update = false): array
131
    {
132
        if (empty($this->schemaErrors) || true === $update) {
133
            $entityManager      = $this->getEntityManager();
134
            $validator          = new SchemaValidator($entityManager);
135
            $this->schemaErrors = $validator->validateMapping();
136
        }
137
138
        return $this->schemaErrors;
139
    }
140
141
    /**
142
     * If a global function dsmGetEntityManagerFactory is defined, we use this
143
     *
144
     * Otherwise, we use the standard DevEntityManagerFactory,
145
     * we define a DB name which is the main DB from env but with `_test` suffixed
146
     *
147
     * @param bool $new
148
     *
149
     * @return EntityManagerInterface
150
     * @throws ConfigException
151
     * @throws \Exception
152
     * @SuppressWarnings(PHPMD)
153
     */
154
    protected function getEntityManager(bool $new = false): EntityManagerInterface
155
    {
156
        if (null === $this->entityManager || true === $new) {
157
            if (\function_exists(self::GET_ENTITY_MANAGER_FUNCTION_NAME)) {
158
                $this->entityManager = \call_user_func(self::GET_ENTITY_MANAGER_FUNCTION_NAME);
159
            } else {
160
                SimpleEnv::setEnv(Config::getProjectRootDirectory() . '/.env');
161
                $testConfig                                 = $_SERVER;
162
                $testConfig[ConfigInterface::PARAM_DB_NAME] = $_SERVER[ConfigInterface::PARAM_DB_NAME] . '_test';
163
                $config                                     = new Config($testConfig);
164
                $this->entityManager                        =
165
                    (new EntityManagerFactory(
166
                        new ArrayCache(),
167
                        new EntityFactory(
168
                            new EntityValidatorFactory(
169
                                new DoctrineCache(
170
                                    new ArrayCache()
171
                                )
172
                            )
173
                        )
174
                    ))->getEntityManager($config);
175
            }
176
        }
177
178
        return $this->entityManager;
179
    }
180
181
182
    /**
183
     * Use Doctrine's built in schema validation tool to catch issues
184
     */
185
    public function testValidateSchema()
186
    {
187
        $errors  = $this->getSchemaErrors();
188
        $class   = $this->getTestedEntityFqn();
189
        $message = '';
190
        if (isset($errors[$class])) {
191
            $message = "Failed ORM Validate Schema:\n";
192
            foreach ($errors[$class] as $err) {
193
                $message .= "\n * $err \n";
194
            }
195
        }
196
        self::assertEmpty($message, $message);
197
    }
198
199
200
    /**
201
     * @param string                 $class
202
     * @param int|string             $id
203
     * @param EntityManagerInterface $entityManager
204
     *
205
     * @return EntityInterface|null
206
     */
207
    protected function loadEntity(string $class, $id, EntityManagerInterface $entityManager): ?EntityInterface
208
    {
209
        return $entityManager->getRepository($class)->find($id);
210
    }
211
212
    public function testConstructor(): EntityInterface
213
    {
214
        $class  = $this->getTestedEntityFqn();
215
        $entity = new $class($this->entityValidatorFactory);
216
        self::assertInstanceOf($class, $entity);
217
218
        return $entity;
219
    }
220
221
    /**
222
     * @param EntityInterface $entity
223
     *
224
     * @throws ConfigException
225
     * @throws \Doctrine\ORM\Query\QueryException
226
     * @throws \ReflectionException
227
     * @depends testConstructor
228
     */
229
    public function testGetDefaults(EntityInterface $entity)
230
    {
231
        $this->callEntityGettersAndAssertNotNull($entity);
232
    }
233
234
    /**
235
     * Loop through Entity fields, call the getter and where possible assert there is a value returned
236
     *
237
     * @param EntityInterface $entity
238
     *
239
     * @throws ConfigException
240
     * @throws \Doctrine\ORM\Query\QueryException
241
     * @throws \ReflectionException
242
     * @SuppressWarnings(PHPMD.StaticAccess)
243
     */
244
    protected function callEntityGettersAndAssertNotNull(EntityInterface $entity): void
245
    {
246
        $class         = $this->getTestedEntityFqn();
247
        $entityManager = $this->getEntityManager();
248
        $meta          = $entityManager->getClassMetadata($class);
249
        foreach ($meta->getFieldNames() as $fieldName) {
250
            $type   = PersisterHelper::getTypeOfField($fieldName, $meta, $entityManager)[0];
251
            $method = $this->getGetterNameForField($fieldName, $type);
252
            if (\ts\stringContains($method, '.')) {
253
                list($getEmbeddableMethod,) = explode('.', $method);
254
                $embeddable = $entity->$getEmbeddableMethod();
255
                self::assertInstanceOf(AbstractEmbeddableObject::class, $embeddable);
256
                continue;
257
            }
258
            $reflectionMethod = new \ReflectionMethod($entity, $method);
259
            if ($reflectionMethod->hasReturnType()) {
260
                $returnType = $reflectionMethod->getReturnType();
261
                $allowsNull = $returnType->allowsNull();
262
                if ($allowsNull) {
263
                    // As we can't assert anything here so simply call
264
                    // the method and allow the type hint to raise any
265
                    // errors.
266
                    $entity->$method();
267
                    continue;
268
                }
269
                self::assertNotNull($entity->$method(), "$fieldName getter returned null");
270
                continue;
271
            }
272
            // If there is no return type then we can't assert anything,
273
            // but again we can just call the getter to check for errors
274
            $entity->$method();
275
        }
276
        if (0 === $this->getCount()) {
277
            self::markTestSkipped('No assertable getters in this Entity');
278
        }
279
    }
280
281
    /**
282
     * Generate a new entity and then update our Entity with the values from the generated one
283
     *
284
     * @param EntityInterface $entity
285
     *
286
     * @throws ConfigException
287
     * @throws \Doctrine\ORM\Mapping\MappingException
288
     * @throws \Doctrine\ORM\Query\QueryException
289
     * @throws \ReflectionException
290
     * @SuppressWarnings(PHPMD.StaticAccess)
291
     */
292
    protected function updateEntityFields(EntityInterface $entity): void
293
    {
294
        $class         = $this->getTestedEntityFqn();
295
        $entityManager = $this->getEntityManager();
296
        $meta          = $entityManager->getClassMetadata($class);
297
        $entityManager = $this->getEntityManager();
298
        $class         = $this->getTestedEntityFqn();
299
        $generated     = $this->testEntityGenerator->generateEntity($entityManager, $class, 10);
300
        $identifiers   = \array_flip($meta->getIdentifier());
301
        foreach ($meta->getFieldNames() as $fieldName) {
302
            if (isset($identifiers[$fieldName])) {
303
                continue;
304
            }
305
            if (true === $this->isUniqueField($meta, $fieldName)) {
306
                continue;
307
            }
308
            $setter = 'set' . $fieldName;
309
            if (!\method_exists($entity, $setter)) {
310
                continue;
311
            }
312
            $type   = PersisterHelper::getTypeOfField($fieldName, $meta, $entityManager)[0];
313
            $getter = $this->getGetterNameForField($fieldName, $type);
314
            if (\ts\stringContains($getter, '.')) {
315
                list($getEmbeddableMethod, $fieldInEmbeddable) = explode('.', $getter);
316
                $getterInEmbeddable  = 'get' . $fieldInEmbeddable;
317
                $setterInEmbeddable  = 'set' . $fieldInEmbeddable;
318
                $generatedEmbeddable = $generated->$getEmbeddableMethod();
319
                $embeddable          = $entity->$getEmbeddableMethod();
320
                if (\method_exists($embeddable, $setterInEmbeddable)
321
                    && \method_exists($embeddable, $getterInEmbeddable)
322
                ) {
323
                    $embeddable->$setterInEmbeddable($generatedEmbeddable->$getterInEmbeddable());
324
                }
325
                continue;
326
            }
327
            $entity->$setter($generated->$getter());
328
        }
329
    }
330
331
    protected function isUniqueField(ClassMetadata $meta, string $fieldName): bool
332
    {
333
        $fieldMapping = $meta->getFieldMapping($fieldName);
334
        if (array_key_exists('unique', $fieldMapping) && true === $fieldMapping['unique']) {
335
            return true;
336
        }
337
338
        return false;
339
    }
340
341
    /**
342
     * Test that we have correctly generated an instance of our test entity
343
     *
344
     * @throws ConfigException
345
     * @throws \Doctrine\ORM\Query\QueryException
346
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
347
     * @throws \Exception
348
     * @throws \ReflectionException
349
     * @SuppressWarnings(PHPMD.StaticAccess)
350
     */
351
    public function testGeneratedCreate(): EntityInterface
352
    {
353
        $entityManager = $this->getEntityManager();
354
        $class         = $this->getTestedEntityFqn();
355
        $generated     = $this->testEntityGenerator->generateEntity($entityManager, $class);
356
        self::assertInstanceOf($class, $generated);
357
        $this->testEntityGenerator->addAssociationEntities($entityManager, $generated);
358
        $this->validateEntity($generated);
359
        $this->callEntityGettersAndAssertNotNull($generated);
360
        $this->entitySaverFactory->getSaverForEntity($generated)->save($generated);
361
362
        return $generated;
363
    }
364
365
    /**
366
     * Test that we can load the entity and then get and set
367
     *
368
     * @param EntityInterface $entity
369
     *
370
     * @return EntityInterface|null
371
     * @throws ConfigException
372
     * @throws \Doctrine\ORM\Mapping\MappingException
373
     * @throws \Doctrine\ORM\Query\QueryException
374
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
375
     * @throws \ReflectionException
376
     * @depends testGeneratedCreate
377
     */
378
    public function testLoadedEntity(EntityInterface $entity): EntityInterface
379
    {
380
        $class         = $this->getTestedEntityFqn();
381
        $entityManager = $this->getEntityManager();
382
        $loaded        = $this->loadEntity($class, $entity->getId(), $entityManager);
383
        self::assertSame($entity->getId(), $loaded->getId());
384
        self::assertInstanceOf($class, $loaded);
385
        $this->updateEntityFields($loaded);
386
        $this->assertAllAssociationsAreNotEmpty($loaded);
387
        $this->validateEntity($loaded);
388
        $this->removeAllAssociations($loaded);
389
        $this->assertAllAssociationsAreEmpty($loaded);
390
        $this->entitySaverFactory->getSaverForEntity($loaded)->save($loaded);
391
392
        return $loaded;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $loaded could return the type null which is incompatible with the type-hinted return EdmondsCommerce\Doctrine...erfaces\EntityInterface. Consider adding an additional type-check to rule them out.
Loading history...
393
    }
394
395
    protected function dump(EntityInterface $entity): string
396
    {
397
        return $this->dumper->dump($entity, $this->getEntityManager());
398
    }
399
400
    /**
401
     * @param EntityInterface $entity
402
     *
403
     * @throws ConfigException
404
     * @depends testLoadedEntity
405
     */
406
    public function testReloadedEntityHasNoAssociations(EntityInterface $entity): void
407
    {
408
        $class         = $this->getTestedEntityFqn();
409
        $entityManager = $this->getEntityManager();
410
        $reLoaded      = $this->loadEntity($class, $entity->getId(), $entityManager);
411
        $entityDump    = $this->dump($entity);
412
        $reLoadedDump  = $this->dump($reLoaded);
413
        self::assertEquals($entityDump, $reLoadedDump);
414
        $this->assertAllAssociationsAreEmpty($reLoaded);
415
    }
416
417
    protected function assertAllAssociationsAreNotEmpty(EntityInterface $entity)
418
    {
419
        $entityManager = $this->getEntityManager();
420
        $class         = $this->getTestedEntityFqn();
421
        $meta          = $entityManager->getClassMetadata($class);
422
        foreach ($meta->getAssociationMappings() as $mapping) {
0 ignored issues
show
Bug introduced by
The method getAssociationMappings() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. Did you maybe mean getAssociationNames()? ( Ignorable by Annotation )

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

422
        foreach ($meta->/** @scrutinizer ignore-call */ getAssociationMappings() as $mapping) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
423
            $getter = 'get' . $mapping['fieldName'];
424
            if ($meta->isCollectionValuedAssociation($mapping['fieldName'])) {
425
                $collection = $entity->$getter()->toArray();
426
                self::assertCorrectMappings($class, $mapping, $entityManager);
0 ignored issues
show
Bug Best Practice introduced by
The method EdmondsCommerce\Doctrine...assertCorrectMappings() is not static, but was called statically. ( Ignorable by Annotation )

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

426
                self::/** @scrutinizer ignore-call */ 
427
                      assertCorrectMappings($class, $mapping, $entityManager);
Loading history...
427
                self::assertNotEmpty(
428
                    $collection,
429
                    'Failed to load the collection of the associated entity [' . $mapping['fieldName']
430
                    . '] from the generated ' . $class
431
                    . ', make sure you have reciprocal adding of the association'
432
                );
433
434
                continue;
435
            }
436
            $association = $entity->$getter();
437
            self::assertNotEmpty(
438
                $association,
439
                'Failed to load the associated entity: [' . $mapping['fieldName']
440
                . '] from the generated ' . $class
441
            );
442
            self::assertNotEmpty(
443
                $association->getId(),
444
                'Failed to get the ID of the associated entity: [' . $mapping['fieldName']
445
                . '] from the generated ' . $class
446
            );
447
        }
448
    }
449
450
    /**
451
     * @param EntityInterface $entity
452
     *
453
     * @throws ConfigException
454
     * @SuppressWarnings(PHPMD.StaticAccess)
455
     */
456
    protected function removeAllAssociations(EntityInterface $entity)
457
    {
458
        $entityManager = $this->getEntityManager();
459
        $class         = $this->getTestedEntityFqn();
460
        $meta          = $entityManager->getClassMetadata($class);
461
        $identifiers   = array_flip($meta->getIdentifier());
462
        foreach ($meta->getAssociationMappings() as $mapping) {
463
            if (isset($identifiers[$mapping['fieldName']])) {
464
                continue;
465
            }
466
            $remover = 'remove' . Inflector::singularize($mapping['fieldName']);
467
            if ($meta->isCollectionValuedAssociation($mapping['fieldName'])) {
468
                $getter    = 'get' . $mapping['fieldName'];
469
                $relations = $entity->$getter();
470
                foreach ($relations as $relation) {
471
                    $entity->$remover($relation);
472
                }
473
                continue;
474
            }
475
            $entity->$remover();
476
        }
477
        $this->assertAllAssociationsAreEmpty($entity);
478
    }
479
480
    protected function assertAllAssociationsAreEmpty(EntityInterface $entity)
481
    {
482
        $entityManager = $this->getEntityManager();
483
        $class         = $this->getTestedEntityFqn();
484
        $meta          = $entityManager->getClassMetadata($class);
485
        $identifiers   = array_flip($meta->getIdentifier());
486
        foreach ($meta->getAssociationMappings() as $mapping) {
487
            if (isset($identifiers[$mapping['fieldName']])) {
488
                continue;
489
            }
490
491
            $getter = 'get' . $mapping['fieldName'];
492
            if ($meta->isCollectionValuedAssociation($mapping['fieldName'])) {
493
                $collection = $entity->$getter()->toArray();
494
                self::assertEmpty(
495
                    $collection,
496
                    'Collection of the associated entity [' . $mapping['fieldName']
497
                    . '] is not empty after calling remove'
498
                );
499
                continue;
500
            }
501
            $association = $entity->$getter();
502
            self::assertEmpty(
503
                $association,
504
                'Failed to remove associated entity: [' . $mapping['fieldName']
505
                . '] from the generated ' . $class
506
            );
507
        }
508
    }
509
510
    /**
511
     * @depends testConstructor
512
     *
513
     * @param EntityInterface $entity
514
     */
515
    public function testGetGetters(EntityInterface $entity)
516
    {
517
        $getters = $entity->getGetters();
518
        self::assertNotEmpty($getters);
519
        foreach ($getters as $getter) {
520
            self::assertRegExp('%^(get|is|has).+%', $getter);
521
        }
522
    }
523
524
    /**
525
     * @depends testConstructor
526
     *
527
     * @param EntityInterface $entity
528
     */
529
    public function testSetSetters(EntityInterface $entity)
530
    {
531
        $setters = $entity->getSetters();
532
        self::assertNotEmpty($setters);
533
        foreach ($setters as $setter) {
534
            self::assertRegExp('%^(set|add).+%', $setter);
535
        }
536
    }
537
538
539
    protected function getGetterNameForField(string $fieldName, string $type): string
540
    {
541
        if ($type === 'boolean') {
542
            return $this->codeHelper->getGetterMethodNameForBoolean($fieldName);
543
        }
544
545
        return 'get' . $fieldName;
546
    }
547
548
    /**
549
     * Loop through entity fields and find unique ones
550
     *
551
     * Then ensure that the unique rule is being enforced as expected
552
     *
553
     * @throws ConfigException
554
     * @throws \Doctrine\ORM\Mapping\MappingException
555
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
556
     * @throws \ReflectionException
557
     * @throws \Doctrine\ORM\Mapping\MappingException
558
     */
559
    public function testUniqueFieldsMustBeUnique(): void
560
    {
561
        $class         = $this->getTestedEntityFqn();
562
        $entityManager = $this->getEntityManager();
563
        $meta          = $entityManager->getClassMetadata($class);
564
        $uniqueFields  = [];
565
        foreach ($meta->getFieldNames() as $fieldName) {
566
            if (true === $this->isUniqueField($meta, $fieldName)) {
567
                $uniqueFields[] = $fieldName;
568
            }
569
        }
570
        if ([] === $uniqueFields) {
571
            self::markTestSkipped('No unique fields to check');
572
573
            return;
574
        }
575
        foreach ($uniqueFields as $fieldName) {
576
            $primary      = $this->testEntityGenerator->generateEntity($entityManager, $class);
577
            $secondary    = $this->testEntityGenerator->generateEntity($entityManager, $class);
578
            $getter       = 'get' . $fieldName;
579
            $setter       = 'set' . $fieldName;
580
            $primaryValue = $primary->$getter();
581
            $secondary->$setter($primaryValue);
582
            $saver = $this->entitySaverFactory->getSaverForEntity($primary);
583
            $this->expectException(UniqueConstraintViolationException::class);
584
            $saver->saveAll([$primary, $secondary]);
585
        }
586
    }
587
588
    /**
589
     * Check the mapping of our class and the associated entity to make sure it's configured properly on both sides.
590
     * Very easy to get wrong. This is in addition to the standard Schema Validation
591
     *
592
     * @param string                 $classFqn
593
     * @param array                  $mapping
594
     * @param EntityManagerInterface $entityManager
595
     */
596
    protected function assertCorrectMappings(string $classFqn, array $mapping, EntityManagerInterface $entityManager)
597
    {
598
        $pass                                 = false;
599
        $associationFqn                       = $mapping['targetEntity'];
600
        $associationMeta                      = $entityManager->getClassMetadata($associationFqn);
601
        $classTraits                          = $entityManager->getClassMetadata($classFqn)
602
                                                              ->getReflectionClass()
603
                                                              ->getTraits();
604
        $unidirectionalTraitShortNamePrefixes = [
605
            'Has' . $associationFqn::getSingular() . RelationsGenerator::PREFIX_UNIDIRECTIONAL,
606
            'Has' . $associationFqn::getPlural() . RelationsGenerator::PREFIX_UNIDIRECTIONAL,
607
        ];
608
        foreach ($classTraits as $trait) {
609
            foreach ($unidirectionalTraitShortNamePrefixes as $namePrefix) {
610
                if (0 === \stripos($trait->getShortName(), $namePrefix)) {
611
                    return;
612
                }
613
            }
614
        }
615
        foreach ($associationMeta->getAssociationMappings() as $associationMapping) {
616
            if ($classFqn === $associationMapping['targetEntity']) {
617
                $pass = self::assertCorrectMapping($mapping, $associationMapping, $classFqn);
0 ignored issues
show
Bug Best Practice introduced by
The method EdmondsCommerce\Doctrine...:assertCorrectMapping() is not static, but was called statically. ( Ignorable by Annotation )

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

617
                /** @scrutinizer ignore-call */ 
618
                $pass = self::assertCorrectMapping($mapping, $associationMapping, $classFqn);
Loading history...
618
                break;
619
            }
620
        }
621
        self::assertTrue(
622
            $pass,
623
            'Failed finding association mapping to test for ' . "\n" . $mapping['targetEntity']
624
        );
625
    }
626
627
    /**
628
     * @param array  $mapping
629
     * @param array  $associationMapping
630
     * @param string $classFqn
631
     *
632
     * @return bool
633
     */
634
    protected function assertCorrectMapping(array $mapping, array $associationMapping, string $classFqn): bool
635
    {
636
        if (empty($mapping['joinTable'])) {
637
            self::assertArrayNotHasKey(
638
                'joinTable',
639
                $associationMapping,
640
                $classFqn . ' join table is empty,
641
                        but association ' . $mapping['targetEntity'] . ' join table is not empty'
642
            );
643
644
            return true;
645
        }
646
        self::assertNotEmpty(
647
            $associationMapping['joinTable'],
648
            "$classFqn joinTable is set to " . $mapping['joinTable']['name']
649
            . " \n association " . $mapping['targetEntity'] . ' join table is empty'
650
        );
651
        self::assertSame(
652
            $mapping['joinTable']['name'],
653
            $associationMapping['joinTable']['name'],
654
            "join tables not the same: \n * $classFqn = " . $mapping['joinTable']['name']
655
            . " \n * association " . $mapping['targetEntity']
656
            . ' = ' . $associationMapping['joinTable']['name']
657
        );
658
        self::assertArrayHasKey(
659
            'inverseJoinColumns',
660
            $associationMapping['joinTable'],
661
            "join table join columns not the same: \n * $classFqn joinColumn = "
662
            . $mapping['joinTable']['joinColumns'][0]['name']
663
            . " \n * association " . $mapping['targetEntity']
664
            . ' inverseJoinColumn is not set'
665
        );
666
        self::assertSame(
667
            $mapping['joinTable']['joinColumns'][0]['name'],
668
            $associationMapping['joinTable']['inverseJoinColumns'][0]['name'],
669
            "join table join columns not the same: \n * $classFqn joinColumn = "
670
            . $mapping['joinTable']['joinColumns'][0]['name']
671
            . " \n * association " . $mapping['targetEntity']
672
            . ' inverseJoinColumn = ' . $associationMapping['joinTable']['inverseJoinColumns'][0]['name']
673
        );
674
        self::assertSame(
675
            $mapping['joinTable']['inverseJoinColumns'][0]['name'],
676
            $associationMapping['joinTable']['joinColumns'][0]['name'],
677
            "join table join columns  not the same: \n * $classFqn inverseJoinColumn = "
678
            . $mapping['joinTable']['inverseJoinColumns'][0]['name']
679
            . " \n * association " . $mapping['targetEntity'] . ' joinColumn = '
680
            . $associationMapping['joinTable']['joinColumns'][0]['name']
681
        );
682
683
        return true;
684
    }
685
686
687
    protected function validateEntity(EntityInterface $entity): void
688
    {
689
        $entity->validate();
690
    }
691
692
693
    /**
694
     * Get the fully qualified name of the Entity we are testing,
695
     * assumes EntityNameTest as the entity class short name
696
     *
697
     * @return string
698
     */
699
    protected function getTestedEntityFqn(): string
700
    {
701
        if (null === $this->testedEntityFqn) {
702
            $this->testedEntityFqn = \substr(static::class, 0, -4);
703
        }
704
705
        return $this->testedEntityFqn;
706
    }
707
708
709
    /**
710
     * Get a \ReflectionClass for the currently tested Entity
711
     *
712
     * @return \ts\Reflection\ReflectionClass
713
     * @throws \ReflectionException
714
     */
715
    protected function getTestedEntityReflectionClass(): \ts\Reflection\ReflectionClass
716
    {
717
        if (null === $this->testedEntityReflectionClass) {
718
            $this->testedEntityReflectionClass = new \ts\Reflection\ReflectionClass(
719
                $this->getTestedEntityFqn()
720
            );
721
        }
722
723
        return $this->testedEntityReflectionClass;
724
    }
725
}
726