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.

assertAllAssociationsAreEmpty()   A
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 28
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 21
c 1
b 0
f 0
nc 5
nop 1
dl 0
loc 28
rs 9.2728
ccs 0
cts 27
cp 0
crap 30
1
<?php
2
3
declare(strict_types=1);
4
5
namespace EdmondsCommerce\DoctrineStaticMeta\Entity\Testing;
6
7
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
8
use Doctrine\ORM\EntityManagerInterface;
9
use Doctrine\ORM\Mapping\ClassMetadata;
10
use Doctrine\ORM\Query\QueryException;
11
use Doctrine\ORM\Tools\SchemaValidator;
12
use Doctrine\ORM\Utility\PersisterHelper;
13
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\CodeHelper;
14
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\RelationsGenerator;
15
use EdmondsCommerce\DoctrineStaticMeta\Config;
16
use EdmondsCommerce\DoctrineStaticMeta\ConfigInterface;
17
use EdmondsCommerce\DoctrineStaticMeta\Entity\DataTransferObjects\DtoFactory;
18
use EdmondsCommerce\DoctrineStaticMeta\Entity\Embeddable\Objects\AbstractEmbeddableObject;
19
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Interfaces\PrimaryKey\IdFieldInterface;
20
use EdmondsCommerce\DoctrineStaticMeta\Entity\Interfaces\EntityInterface;
21
use EdmondsCommerce\DoctrineStaticMeta\Entity\Repositories\RepositoryFactory;
22
use EdmondsCommerce\DoctrineStaticMeta\Entity\Savers\EntitySaverFactory;
23
use EdmondsCommerce\DoctrineStaticMeta\Entity\Savers\EntitySaverInterface;
24
use EdmondsCommerce\DoctrineStaticMeta\Entity\Testing\EntityGenerator\TestEntityGenerator;
25
use EdmondsCommerce\DoctrineStaticMeta\Entity\Testing\EntityGenerator\TestEntityGeneratorFactory;
26
use EdmondsCommerce\DoctrineStaticMeta\Entity\Testing\Fixtures\AbstractEntityFixtureLoader;
27
use EdmondsCommerce\DoctrineStaticMeta\Entity\Testing\Fixtures\FixturesHelper;
28
use EdmondsCommerce\DoctrineStaticMeta\Entity\Testing\Fixtures\FixturesHelperFactory;
29
use EdmondsCommerce\DoctrineStaticMeta\Exception\ConfigException;
30
use EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException;
31
use EdmondsCommerce\DoctrineStaticMeta\Exception\ValidationException;
32
use EdmondsCommerce\DoctrineStaticMeta\MappingHelper;
33
use EdmondsCommerce\DoctrineStaticMeta\SimpleEnv;
34
use ErrorException;
35
use Exception;
36
use PHPUnit\Framework\TestCase;
37
use Psr\Container\ContainerInterface;
38
use ReflectionException;
39
use ReflectionMethod;
40
41
use function stripos;
42
use function substr;
43
44
/**
45
 * Class AbstractEntityTest
46
 *
47
 * This abstract test is designed to give you a good level of test coverage for your entities without any work required.
48
 *
49
 * You should extend the test with methods that test your specific business logic, your validators and anything else.
50
 *
51
 * You can override the methods, properties and constants as you see fit.
52
 *
53
 * @package EdmondsCommerce\DoctrineStaticMeta\Entity
54
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
55
 * @SuppressWarnings(PHPMD.NumberOfChildren)
56
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
57
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
58
 * @SuppressWarnings(PHPMD.StaticAccess)
59
 */
60
abstract class AbstractEntityTest extends TestCase implements EntityTestInterface
61
{
62
    /**
63
     * @var ContainerInterface
64
     */
65
    protected static $container;
66
    /**
67
     * The fully qualified name of the Entity being tested, as calculated by the test class name
68
     *
69
     * @var string
70
     */
71
    protected static $testedEntityFqn;
72
73
    /**
74
     * @var TestEntityGenerator
75
     */
76
    protected static $testEntityGenerator;
77
78
    /**
79
     * @var TestEntityGeneratorFactory
80
     */
81
    protected static $testEntityGeneratorFactory;
82
83
    /**
84
     * @var array
85
     */
86
    protected static $schemaErrors = [];
87
88
    public static function setUpBeforeClass(): void
89
    {
90
        if (null !== static::$container) {
91
            self::tearDownAfterClass();
92
        }
93
        static::initContainer();
94
        static::$testedEntityFqn            = substr(static::class, 0, -4);
95
        static::$testEntityGeneratorFactory = static::$container->get(TestEntityGeneratorFactory::class);
96
        static::$testEntityGenerator        =
97
            static::$testEntityGeneratorFactory->createForEntityFqn(static::$testedEntityFqn);
98
    }
99
100
    public static function tearDownAfterClass(): void
101
    {
102
        $entityManager = static::$container->get(EntityManagerInterface::class);
103
        $entityManager->close();
104
        $entityManager->getConnection()->close();
105
        self::$container   = null;
106
        static::$container = null;
107
    }
108
109
    public static function initContainer(): void
110
    {
111
        $testConfig        = self::getTestContainerConfig();
112
        static::$container = TestContainerFactory::getContainer($testConfig);
113
    }
114
115
    /**
116
     * @throws ConfigException
117
     * @throws DoctrineStaticMetaException
118
     * @SuppressWarnings(PHPMD.Superglobals)
119
     * @SuppressWarnings(PHPMD.StaticAccess)
120
     */
121
    public static function getTestContainerConfig(): array
122
    {
123
        SimpleEnv::setEnv(Config::getProjectRootDirectory() . '/.env');
124
        $testConfig = $_SERVER;
125
        if (preg_match('%_test$%m', $_SERVER[ConfigInterface::PARAM_DB_NAME]) !== 1) {
126
            $testConfig[ConfigInterface::PARAM_DB_NAME] = $_SERVER[ConfigInterface::PARAM_DB_NAME] . '_test';
127
        }
128
129
        return $testConfig;
130
    }
131
132
    /**
133
     * This test checks that the fixtures can be loaded properly
134
     *
135
     * The test returns the array of fixtures which means you could choose to add a test that `depends` on this test
136
     *
137
     * @test
138
     */
139
    public function theFixtureCanBeLoaded(): array
140
    {
141
        /**
142
         * @var FixturesHelper $fixtureHelper
143
         */
144
        $fixtureHelper = $this->getFixturesHelper();
145
        /**
146
         * This can seriously hurt performance, but is needed as a default
147
         */
148
        $fixtureHelper->setLoadFromCache(false);
149
        /**
150
         * @var AbstractEntityFixtureLoader $fixture
151
         */
152
        $fixture = $fixtureHelper->createFixtureInstanceForEntityFqn(static::$testedEntityFqn);
153
        $fixtureHelper->createDb($fixture);
154
        $loaded               = $this->loadAllEntities();
155
        $expectedAmountLoaded = $fixture::BULK_AMOUNT_TO_GENERATE;
156
        $actualAmountLoaded   = count($loaded);
157
        self::assertGreaterThanOrEqual(
158
            $expectedAmountLoaded,
159
            $actualAmountLoaded,
160
            "expected to load at least $expectedAmountLoaded but only loaded $actualAmountLoaded"
161
        );
162
163
        return $loaded;
164
    }
165
166
    /**
167
     * Test that we have correctly generated an instance of our test entity
168
     *
169
     * @param array $fixtureEntities
170
     *
171
     * @return EntityInterface
172
     * @SuppressWarnings(PHPMD.StaticAccess)
173
     * @depends theFixtureCanBeLoaded
174
     * @test
175
     */
176
    public function weCanGenerateANewEntityInstance(array $fixtureEntities): EntityInterface
177
    {
178
        $generated = current($fixtureEntities);
179
        self::assertInstanceOf(static::$testedEntityFqn, $generated);
180
181
        return $generated;
182
    }
183
184
    /**
185
     * @test
186
     * Use Doctrine's built in schema validation tool to catch issues
187
     */
188
    public function theSchemaIsValidForThisEntity()
189
    {
190
        $errors  = $this->getSchemaErrors();
191
        $message = '';
192
        if (isset($errors[static::$testedEntityFqn])) {
193
            $message = "Failed ORM Validate Schema:\n";
194
            foreach ($errors[static::$testedEntityFqn] as $err) {
195
                $message .= "\n * $err \n";
196
            }
197
        }
198
        self::assertEmpty($message, $message);
199
    }
200
201
    /**
202
     * Loop through Entity fields, call the getter and where possible assert there is a value returned
203
     *
204
     * @param EntityInterface $entity
205
     *
206
     * @return EntityInterface
207
     * @throws QueryException
208
     * @throws ReflectionException
209
     * @SuppressWarnings(PHPMD.StaticAccess)
210
     * @test
211
     * @depends weCanGenerateANewEntityInstance
212
     */
213
    public function theEntityGettersReturnValues(EntityInterface $entity): EntityInterface
214
    {
215
        $meta = $this->getEntityManager()->getClassMetadata(static::$testedEntityFqn);
216
        $dto  = $this->getDtoFactory()->createDtoFromEntity($entity);
217
        foreach ($meta->getFieldNames() as $fieldName) {
218
            if ('id' === $fieldName) {
219
                continue;
220
            }
221
            $type   = PersisterHelper::getTypeOfField($fieldName, $meta, $this->getEntityManager())[0];
222
            $method = $this->getGetterNameForField($fieldName, $type);
223
            if (\ts\stringContains($method, '.')) {
224
                [$getEmbeddableMethod,] = explode('.', $method);
225
                $embeddable = $entity->$getEmbeddableMethod();
226
                self::assertInstanceOf(AbstractEmbeddableObject::class, $embeddable);
227
                continue;
228
            }
229
            $reflectionMethod = new ReflectionMethod($entity, $method);
230
            if ($reflectionMethod->hasReturnType()) {
231
                $returnType = $reflectionMethod->getReturnType();
232
                $allowsNull = $returnType->allowsNull();
233
                if ($allowsNull) {
234
                    // As we can't assert anything here so simply call
235
                    // the method and allow the type hint to raise any
236
                    // errors.
237
                    $entity->$method();
238
                    continue;
239
                }
240
                self::assertNotNull($dto->$method(), "$fieldName getter returned null");
241
                continue;
242
            }
243
            // If there is no return type then we can't assert anything,
244
            // but again we can just call the getter to check for errors
245
            $dto->$method();
246
        }
247
        if (0 === $this->getCount()) {
248
            self::assertTrue(true);
249
        }
250
251
        return $entity;
252
    }
253
254
    /**
255
     * @param EntityInterface $generated
256
     *
257
     * @return EntityInterface
258
     * @test
259
     * @depends theEntityGettersReturnValues
260
     * @throws ErrorException
261
     */
262
    public function weCanExtendTheEntityWithUnrequiredAssociationEntities(EntityInterface $generated): EntityInterface
263
    {
264
        if ([] === $this->getTestedEntityClassMetaData()->getAssociationMappings()) {
265
            $this->markTestSkipped('No associations to test');
266
        }
267
        $this->getTestEntityGenerator()->addAssociationEntities($generated);
268
        $this->assertAllAssociationsAreNotEmpty($generated);
269
270
        return $generated;
271
    }
272
273
    /**
274
     * @param EntityInterface $generated
275
     *
276
     * @return EntityInterface
277
     * @throws DoctrineStaticMetaException
278
     * @throws ReflectionException
279
     * @test
280
     * @depends weCanExtendTheEntityWithUnrequiredAssociationEntities
281
     */
282
    public function theEntityCanBeSavedAndReloadedFromTheDatabase(EntityInterface $generated): EntityInterface
283
    {
284
        $this->getEntitySaver()->save($generated);
285
        $loaded = $this->loadEntity($generated->getId());
286
        self::assertSame((string)$generated->getId(), (string)$loaded->getId());
287
        self::assertInstanceOf(static::$testedEntityFqn, $loaded);
288
289
        return $loaded;
290
    }
291
292
    /**
293
     * @param EntityInterface $loaded
294
     *
295
     * @return EntityInterface
296
     * @throws DoctrineStaticMetaException
297
     * @throws ReflectionException
298
     * @throws ValidationException
299
     * @depends theEntityCanBeSavedAndReloadedFromTheDatabase
300
     * @test
301
     */
302
    public function theLoadedEntityCanBeUpdatedAndResaved(EntityInterface $loaded): EntityInterface
303
    {
304
        $this->updateEntityFields($loaded);
305
        $this->assertAllAssociationsAreNotEmpty($loaded);
306
        $this->removeAllAssociations($loaded);
307
        $this->getEntitySaver()->save($loaded);
308
309
        return $loaded;
310
    }
311
312
    /**
313
     * @param EntityInterface $entity
314
     *
315
     * @return EntityInterface
316
     * @depends theLoadedEntityCanBeUpdatedAndResaved
317
     * @test
318
     */
319
    public function theReloadedEntityHasNoAssociatedEntities(EntityInterface $entity): EntityInterface
320
    {
321
        $reLoaded     = $this->loadEntity($entity->getId());
322
        $entityDump   = $this->dump($entity);
323
        $reLoadedDump = $this->dump($reLoaded);
324
        self::assertEquals($entityDump, $reLoadedDump);
325
        $this->assertAllAssociationsAreEmpty($reLoaded);
326
327
        return $reLoaded;
328
    }
329
330
    /**
331
     * @depends weCanGenerateANewEntityInstance
332
     *
333
     * @param EntityInterface $entity
334
     *
335
     * @throws ReflectionException
336
     * @test
337
     */
338
    public function checkAllGettersCanBeReturnedFromDoctrineStaticMeta(EntityInterface $entity)
339
    {
340
        $getters = $entity::getDoctrineStaticMeta()->getGetters();
341
        self::assertNotEmpty($getters);
342
        foreach ($getters as $getter) {
343
            self::assertRegExp('%^(get|is|has).+%', $getter);
344
        }
345
    }
346
347
    /**
348
     * @depends weCanGenerateANewEntityInstance
349
     * @test
350
     *
351
     * @param EntityInterface $entity
352
     *
353
     * @throws ReflectionException
354
     */
355
    public function checkAllSettersCanBeReturnedFromDoctrineStaticMeta(EntityInterface $entity)
356
    {
357
        $setters = $entity::getDoctrineStaticMeta()->getSetters();
358
        self::assertNotEmpty($setters);
359
        foreach ($setters as $setter) {
360
            self::assertRegExp('%^(set|add).+%', $setter);
361
        }
362
    }
363
364
    /**
365
     * Loop through entity fields and find unique ones
366
     *
367
     * Then ensure that the unique rule is being enforced as expected
368
     *
369
     * @throws ReflectionException
370
     * @throws ValidationException
371
     * @test
372
     * @depends theReloadedEntityHasNoAssociatedEntities
373
     */
374
    public function checkThatWeCanNotSaveEntitiesWithDuplicateUniqueFieldValues(): void
375
    {
376
        $meta         = $this->getTestedEntityClassMetaData();
377
        $uniqueFields = [];
378
        foreach ($meta->getFieldNames() as $fieldName) {
379
            if (IdFieldInterface::PROP_ID === $fieldName) {
380
                continue;
381
            }
382
            if (true === $this->isUniqueField($fieldName)) {
383
                $uniqueFields[] = $fieldName;
384
            }
385
        }
386
        if ([] === $uniqueFields) {
387
            self::markTestSkipped('No unique fields to check');
388
389
            return;
390
        }
391
        foreach ($uniqueFields as $fieldName) {
392
            $primary      = $this->getTestEntityGenerator()->generateEntity();
393
            $secondary    = $this->getTestEntityGenerator()->generateEntity();
394
            $secondaryDto = $this->getDtoFactory()->createDtoFromEntity($secondary);
395
            $getter       = 'get' . $fieldName;
396
            $setter       = 'set' . $fieldName;
397
            $primaryValue = $primary->$getter();
398
            $secondaryDto->$setter($primaryValue);
399
            $secondary->update($secondaryDto);
400
            $saver = $this->getEntitySaver();
401
            $this->expectException(UniqueConstraintViolationException::class);
402
            $saver->saveAll([$primary, $secondary]);
403
        }
404
    }
405
406
    protected function getFixturesHelper(): FixturesHelper
407
    {
408
        return static::$container->get(FixturesHelperFactory::class)->getFixturesHelper();
409
    }
410
411
    protected function loadAllEntities(): array
412
    {
413
        return static::$container->get(RepositoryFactory::class)
414
                                 ->getRepository(static::$testedEntityFqn)
415
                                 ->findAll();
416
    }
417
418
    /**
419
     * Use Doctrine's standard schema validation to get errors for the whole schema
420
     *
421
     * We cache this as a class property because the schema only needs validating once as a whole, after that we can
422
     * pull out Entity specific issues as required
423
     *
424
     * @param bool $update
425
     *
426
     * @return array
427
     * @throws Exception
428
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
429
     */
430
    protected function getSchemaErrors(bool $update = false): array
431
    {
432
        if ([] === static::$schemaErrors || true === $update) {
433
            $validator            = new SchemaValidator($this->getEntityManager());
434
            static::$schemaErrors = $validator->validateMapping();
435
        }
436
437
        return static::$schemaErrors;
438
    }
439
440
    protected function getEntityManager(): EntityManagerInterface
441
    {
442
        return static::$container->get(EntityManagerInterface::class);
443
    }
444
445
    protected function getDtoFactory(): DtoFactory
446
    {
447
        return static::$container->get(DtoFactory::class);
448
    }
449
450
    protected function getGetterNameForField(string $fieldName, string $type): string
451
    {
452
        if ($type === 'boolean') {
453
            return static::$container->get(CodeHelper::class)->getGetterMethodNameForBoolean($fieldName);
454
        }
455
456
        return 'get' . $fieldName;
457
    }
458
459
    protected function getTestedEntityClassMetaData(): ClassMetadata
460
    {
461
        return $this->getEntityManager()->getClassMetadata(static::$testedEntityFqn);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getEntityM...tatic::testedEntityFqn) returns the type Doctrine\Common\Persistence\Mapping\ClassMetadata which includes types incompatible with the type-hinted return Doctrine\ORM\Mapping\ClassMetadata.
Loading history...
462
    }
463
464
    protected function getTestEntityGenerator(): TestEntityGenerator
465
    {
466
        return static::$testEntityGenerator;
467
    }
468
469
    protected function assertAllAssociationsAreNotEmpty(EntityInterface $entity)
470
    {
471
        $meta = $this->getTestedEntityClassMetaData();
472
        foreach ($meta->getAssociationMappings() as $mapping) {
473
            $getter = 'get' . $mapping['fieldName'];
474
            if ($meta->isCollectionValuedAssociation($mapping['fieldName'])) {
475
                $collection = $entity->$getter()->toArray();
476
                $this->assertCorrectMappings(static::$testedEntityFqn, $mapping);
477
                self::assertNotEmpty(
478
                    $collection,
479
                    'Failed to load the collection of the associated entity [' . $mapping['fieldName']
480
                    . '] from the generated ' . static::$testedEntityFqn
481
                    . ', make sure you have reciprocal adding of the association'
482
                );
483
484
                continue;
485
            }
486
            $association = $entity->$getter();
487
            self::assertNotEmpty(
488
                $association,
489
                'Failed to load the associated entity: [' . $mapping['fieldName']
490
                . '] from the generated ' . static::$testedEntityFqn
491
            );
492
            self::assertNotEmpty(
493
                $association->getId(),
494
                'Failed to get the ID of the associated entity: [' . $mapping['fieldName']
495
                . '] from the generated ' . static::$testedEntityFqn
496
            );
497
        }
498
    }
499
500
    /**
501
     * Check the mapping of our class and the associated entity to make sure it's configured properly on both sides.
502
     * Very easy to get wrong. This is in addition to the standard Schema Validation
503
     *
504
     * @param string $classFqn
505
     * @param array  $mapping
506
     */
507
    protected function assertCorrectMappings(string $classFqn, array $mapping)
508
    {
509
        $entityManager                        = $this->getEntityManager();
510
        $pass                                 = false;
511
        $associationFqn                       = $mapping['targetEntity'];
512
        $associationMeta                      = $entityManager->getClassMetadata($associationFqn);
513
        $classTraits                          = $entityManager->getClassMetadata($classFqn)
514
                                                              ->getReflectionClass()
515
                                                              ->getTraits();
516
        $unidirectionalTraitShortNamePrefixes = [
517
            'Has' . $associationFqn::getDoctrineStaticMeta()->getSingular() . RelationsGenerator::PREFIX_UNIDIRECTIONAL,
518
            'Has' . $associationFqn::getDoctrineStaticMeta()->getPlural() . RelationsGenerator::PREFIX_UNIDIRECTIONAL,
519
            'Has' . RelationsGenerator::PREFIX_REQUIRED .
520
            $associationFqn::getDoctrineStaticMeta()->getSingular() . RelationsGenerator::PREFIX_UNIDIRECTIONAL,
521
            'Has' . RelationsGenerator::PREFIX_REQUIRED .
522
            $associationFqn::getDoctrineStaticMeta()->getPlural() . RelationsGenerator::PREFIX_UNIDIRECTIONAL,
523
        ];
524
        foreach ($classTraits as $trait) {
525
            foreach ($unidirectionalTraitShortNamePrefixes as $namePrefix) {
526
                if (0 === stripos($trait->getShortName(), $namePrefix)) {
527
                    return;
528
                }
529
            }
530
        }
531
        foreach ($associationMeta->getAssociationMappings() as $associationMapping) {
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

531
        foreach ($associationMeta->/** @scrutinizer ignore-call */ getAssociationMappings() as $associationMapping) {

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...
532
            if ($classFqn === $associationMapping['targetEntity']) {
533
                $pass = $this->assertCorrectMapping($mapping, $associationMapping, $classFqn);
534
                break;
535
            }
536
        }
537
        self::assertTrue(
538
            $pass,
539
            'Failed finding association mapping to test for ' . "\n" . $mapping['targetEntity']
540
        );
541
    }
542
543
    /**
544
     * @param array  $mapping
545
     * @param array  $associationMapping
546
     * @param string $classFqn
547
     *
548
     * @return bool
549
     */
550
    protected function assertCorrectMapping(array $mapping, array $associationMapping, string $classFqn): bool
551
    {
552
        if (empty($mapping['joinTable'])) {
553
            self::assertArrayNotHasKey(
554
                'joinTable',
555
                $associationMapping,
556
                $classFqn . ' join table is empty,
557
                        but association ' . $mapping['targetEntity'] . ' join table is not empty'
558
            );
559
560
            return true;
561
        }
562
        self::assertNotEmpty(
563
            $associationMapping['joinTable'],
564
            "$classFqn joinTable is set to " . $mapping['joinTable']['name']
565
            . " \n association " . $mapping['targetEntity'] . ' join table is empty'
566
        );
567
        self::assertSame(
568
            $mapping['joinTable']['name'],
569
            $associationMapping['joinTable']['name'],
570
            "join tables not the same: \n * $classFqn = " . $mapping['joinTable']['name']
571
            . " \n * association " . $mapping['targetEntity']
572
            . ' = ' . $associationMapping['joinTable']['name']
573
        );
574
        self::assertArrayHasKey(
575
            'inverseJoinColumns',
576
            $associationMapping['joinTable'],
577
            "join table join columns not the same: \n * $classFqn joinColumn = "
578
            . $mapping['joinTable']['joinColumns'][0]['name']
579
            . " \n * association " . $mapping['targetEntity']
580
            . ' inverseJoinColumn is not set'
581
        );
582
        self::assertSame(
583
            $mapping['joinTable']['joinColumns'][0]['name'],
584
            $associationMapping['joinTable']['inverseJoinColumns'][0]['name'],
585
            "join table join columns not the same: \n * $classFqn joinColumn = "
586
            . $mapping['joinTable']['joinColumns'][0]['name']
587
            . " \n * association " . $mapping['targetEntity']
588
            . ' inverseJoinColumn = ' . $associationMapping['joinTable']['inverseJoinColumns'][0]['name']
589
        );
590
        self::assertSame(
591
            $mapping['joinTable']['inverseJoinColumns'][0]['name'],
592
            $associationMapping['joinTable']['joinColumns'][0]['name'],
593
            "join table join columns  not the same: \n * $classFqn inverseJoinColumn = "
594
            . $mapping['joinTable']['inverseJoinColumns'][0]['name']
595
            . " \n * association " . $mapping['targetEntity'] . ' joinColumn = '
596
            . $associationMapping['joinTable']['joinColumns'][0]['name']
597
        );
598
599
        return true;
600
    }
601
602
    protected function getEntitySaver(): EntitySaverInterface
603
    {
604
        return static::$container->get(EntitySaverFactory::class)->getSaverForEntityFqn(static::$testedEntityFqn);
605
    }
606
607
    /**
608
     * @param mixed $id
609
     *
610
     * @return EntityInterface|null
611
     */
612
    protected function loadEntity($id): EntityInterface
613
    {
614
        return static::$container->get(RepositoryFactory::class)
615
                                 ->getRepository(static::$testedEntityFqn)
616
                                 ->get($id);
617
    }
618
619
    /**
620
     * Generate a new entity and then update our Entity with the values from the generated one
621
     *
622
     * @param EntityInterface $entity
623
     *
624
     * @throws ValidationException
625
     * @SuppressWarnings(PHPMD.StaticAccess)
626
     */
627
    protected function updateEntityFields(EntityInterface $entity): void
628
    {
629
        $dto = $this->getDtoFactory()->createDtoFromEntity($entity);
630
        $this->getTestEntityGenerator()->fakerUpdateDto($dto);
631
        $entity->update($dto);
632
    }
633
634
    /**
635
     * @param EntityInterface $entity
636
     *
637
     * @throws ReflectionException
638
     * @SuppressWarnings(PHPMD.StaticAccess)
639
     */
640
    protected function removeAllAssociations(EntityInterface $entity)
641
    {
642
        $required    = $entity::getDoctrineStaticMeta()->getRequiredRelationProperties();
643
        $meta        = $this->getTestedEntityClassMetaData();
644
        $identifiers = array_flip($meta->getIdentifier());
645
        foreach ($meta->getAssociationMappings() as $mapping) {
646
            if (isset($identifiers[$mapping['fieldName']])) {
647
                continue;
648
            }
649
            if (isset($required[$mapping['fieldName']])) {
650
                continue;
651
            }
652
            $remover = 'remove' . MappingHelper::singularize($mapping['fieldName']);
653
            if ($meta->isCollectionValuedAssociation($mapping['fieldName'])) {
654
                $getter    = 'get' . $mapping['fieldName'];
655
                $relations = $entity->$getter();
656
                foreach ($relations as $relation) {
657
                    $this->initialiseEntity($relation);
658
                    $entity->$remover($relation);
659
                }
660
                continue;
661
            }
662
            $entity->$remover();
663
        }
664
        $this->assertAllAssociationsAreEmpty($entity);
665
    }
666
667
    protected function initialiseEntity(EntityInterface $entity): void
668
    {
669
        static::$testEntityGeneratorFactory
670
            ->createForEntityFqn($entity::getEntityFqn())
671
            ->getEntityFactory()
672
            ->initialiseEntity($entity);
673
    }
674
675
    protected function assertAllAssociationsAreEmpty(EntityInterface $entity)
676
    {
677
        $required    = $entity::getDoctrineStaticMeta()->getRequiredRelationProperties();
678
        $meta        = $this->getTestedEntityClassMetaData();
679
        $identifiers = array_flip($meta->getIdentifier());
680
        foreach ($meta->getAssociationMappings() as $mapping) {
681
            if (isset($identifiers[$mapping['fieldName']])) {
682
                continue;
683
            }
684
            if (isset($required[$mapping['fieldName']])) {
685
                continue;
686
            }
687
688
            $getter = 'get' . $mapping['fieldName'];
689
            if ($meta->isCollectionValuedAssociation($mapping['fieldName'])) {
690
                $collection = $entity->$getter()->toArray();
691
                self::assertEmpty(
692
                    $collection,
693
                    'Collection of the associated entity [' . $mapping['fieldName']
694
                    . '] is not empty after calling remove'
695
                );
696
                continue;
697
            }
698
            $association = $entity->$getter();
699
            self::assertEmpty(
700
                $association,
701
                'Failed to remove associated entity: [' . $mapping['fieldName']
702
                . '] from the generated ' . static::$testedEntityFqn
703
            );
704
        }
705
    }
706
707
    protected function dump(EntityInterface $entity): string
708
    {
709
        return (new EntityDebugDumper())->dump($entity, $this->getEntityManager());
710
    }
711
712
    protected function isUniqueField(string $fieldName): bool
713
    {
714
        $fieldMapping = $this->getTestedEntityClassMetaData()->getFieldMapping($fieldName);
715
716
        return array_key_exists('unique', $fieldMapping) && true === $fieldMapping['unique'];
717
    }
718
}
719