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
Push — master ( ecfca9...6a4e57 )
by joseph
25:26 queued 13s
created

AbstractEntityTest::initContainer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

405
        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...
406
            $getter = 'get' . $mapping['fieldName'];
407
            if ($meta->isCollectionValuedAssociation($mapping['fieldName'])) {
408
                $collection = $entity->$getter()->toArray();
409
                $this->assertCorrectMappings($class, $mapping, $entityManager);
410
                self::assertNotEmpty(
411
                    $collection,
412
                    'Failed to load the collection of the associated entity [' . $mapping['fieldName']
413
                    . '] from the generated ' . $class
414
                    . ', make sure you have reciprocal adding of the association'
415
                );
416
417
                continue;
418
            }
419
            $association = $entity->$getter();
420
            self::assertNotEmpty(
421
                $association,
422
                'Failed to load the associated entity: [' . $mapping['fieldName']
423
                . '] from the generated ' . $class
424
            );
425
            self::assertNotEmpty(
426
                $association->getId(),
427
                'Failed to get the ID of the associated entity: [' . $mapping['fieldName']
428
                . '] from the generated ' . $class
429
            );
430
        }
431
    }
432
433
    /**
434
     * Check the mapping of our class and the associated entity to make sure it's configured properly on both sides.
435
     * Very easy to get wrong. This is in addition to the standard Schema Validation
436
     *
437
     * @param string                 $classFqn
438
     * @param array                  $mapping
439
     * @param EntityManagerInterface $entityManager
440
     */
441
    protected function assertCorrectMappings(string $classFqn, array $mapping, EntityManagerInterface $entityManager)
442
    {
443
        $pass                                 = false;
444
        $associationFqn                       = $mapping['targetEntity'];
445
        $associationMeta                      = $entityManager->getClassMetadata($associationFqn);
446
        $classTraits                          = $entityManager->getClassMetadata($classFqn)
447
                                                              ->getReflectionClass()
448
                                                              ->getTraits();
449
        $unidirectionalTraitShortNamePrefixes = [
450
            'Has' . $associationFqn::getDoctrineStaticMeta()->getSingular() . RelationsGenerator::PREFIX_UNIDIRECTIONAL,
451
            'Has' . $associationFqn::getDoctrineStaticMeta()->getPlural() . RelationsGenerator::PREFIX_UNIDIRECTIONAL,
452
        ];
453
        foreach ($classTraits as $trait) {
454
            foreach ($unidirectionalTraitShortNamePrefixes as $namePrefix) {
455
                if (0 === \stripos($trait->getShortName(), $namePrefix)) {
456
                    return;
457
                }
458
            }
459
        }
460
        foreach ($associationMeta->getAssociationMappings() as $associationMapping) {
461
            if ($classFqn === $associationMapping['targetEntity']) {
462
                $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

462
                /** @scrutinizer ignore-call */ 
463
                $pass = self::assertCorrectMapping($mapping, $associationMapping, $classFqn);
Loading history...
463
                break;
464
            }
465
        }
466
        self::assertTrue(
467
            $pass,
468
            'Failed finding association mapping to test for ' . "\n" . $mapping['targetEntity']
469
        );
470
    }
471
472
    /**
473
     * @param array  $mapping
474
     * @param array  $associationMapping
475
     * @param string $classFqn
476
     *
477
     * @return bool
478
     */
479
    protected function assertCorrectMapping(array $mapping, array $associationMapping, string $classFqn): bool
480
    {
481
        if (empty($mapping['joinTable'])) {
482
            self::assertArrayNotHasKey(
483
                'joinTable',
484
                $associationMapping,
485
                $classFqn . ' join table is empty,
486
                        but association ' . $mapping['targetEntity'] . ' join table is not empty'
487
            );
488
489
            return true;
490
        }
491
        self::assertNotEmpty(
492
            $associationMapping['joinTable'],
493
            "$classFqn joinTable is set to " . $mapping['joinTable']['name']
494
            . " \n association " . $mapping['targetEntity'] . ' join table is empty'
495
        );
496
        self::assertSame(
497
            $mapping['joinTable']['name'],
498
            $associationMapping['joinTable']['name'],
499
            "join tables not the same: \n * $classFqn = " . $mapping['joinTable']['name']
500
            . " \n * association " . $mapping['targetEntity']
501
            . ' = ' . $associationMapping['joinTable']['name']
502
        );
503
        self::assertArrayHasKey(
504
            'inverseJoinColumns',
505
            $associationMapping['joinTable'],
506
            "join table join columns not the same: \n * $classFqn joinColumn = "
507
            . $mapping['joinTable']['joinColumns'][0]['name']
508
            . " \n * association " . $mapping['targetEntity']
509
            . ' inverseJoinColumn is not set'
510
        );
511
        self::assertSame(
512
            $mapping['joinTable']['joinColumns'][0]['name'],
513
            $associationMapping['joinTable']['inverseJoinColumns'][0]['name'],
514
            "join table join columns not the same: \n * $classFqn joinColumn = "
515
            . $mapping['joinTable']['joinColumns'][0]['name']
516
            . " \n * association " . $mapping['targetEntity']
517
            . ' inverseJoinColumn = ' . $associationMapping['joinTable']['inverseJoinColumns'][0]['name']
518
        );
519
        self::assertSame(
520
            $mapping['joinTable']['inverseJoinColumns'][0]['name'],
521
            $associationMapping['joinTable']['joinColumns'][0]['name'],
522
            "join table join columns  not the same: \n * $classFqn inverseJoinColumn = "
523
            . $mapping['joinTable']['inverseJoinColumns'][0]['name']
524
            . " \n * association " . $mapping['targetEntity'] . ' joinColumn = '
525
            . $associationMapping['joinTable']['joinColumns'][0]['name']
526
        );
527
528
        return true;
529
    }
530
531
    /**
532
     * @param EntityInterface $entity
533
     *
534
     * @throws ConfigException
535
     * @SuppressWarnings(PHPMD.StaticAccess)
536
     */
537
    protected function removeAllAssociations(EntityInterface $entity)
538
    {
539
        $entityManager = $this->getEntityManager();
540
        $class         = $this->getTestedEntityFqn();
541
        $meta          = $entityManager->getClassMetadata($class);
542
        $identifiers   = array_flip($meta->getIdentifier());
543
        foreach ($meta->getAssociationMappings() as $mapping) {
544
            if (isset($identifiers[$mapping['fieldName']])) {
545
                continue;
546
            }
547
            $remover = 'remove' . Inflector::singularize($mapping['fieldName']);
548
            if ($meta->isCollectionValuedAssociation($mapping['fieldName'])) {
549
                $getter    = 'get' . $mapping['fieldName'];
550
                $relations = $entity->$getter();
551
                foreach ($relations as $relation) {
552
                    $entity->$remover($relation);
553
                }
554
                continue;
555
            }
556
            $entity->$remover();
557
        }
558
        $this->assertAllAssociationsAreEmpty($entity);
559
    }
560
561
    protected function assertAllAssociationsAreEmpty(EntityInterface $entity)
562
    {
563
        $entityManager = $this->getEntityManager();
564
        $class         = $this->getTestedEntityFqn();
565
        $meta          = $entityManager->getClassMetadata($class);
566
        $identifiers   = array_flip($meta->getIdentifier());
567
        foreach ($meta->getAssociationMappings() as $mapping) {
568
            if (isset($identifiers[$mapping['fieldName']])) {
569
                continue;
570
            }
571
572
            $getter = 'get' . $mapping['fieldName'];
573
            if ($meta->isCollectionValuedAssociation($mapping['fieldName'])) {
574
                $collection = $entity->$getter()->toArray();
575
                self::assertEmpty(
576
                    $collection,
577
                    'Collection of the associated entity [' . $mapping['fieldName']
578
                    . '] is not empty after calling remove'
579
                );
580
                continue;
581
            }
582
            $association = $entity->$getter();
583
            self::assertEmpty(
584
                $association,
585
                'Failed to remove associated entity: [' . $mapping['fieldName']
586
                . '] from the generated ' . $class
587
            );
588
        }
589
    }
590
591
    /**
592
     * @param EntityInterface $entity
593
     *
594
     * @throws ConfigException
595
     * @depends testLoadedEntity
596
     */
597
    public function testReloadedEntityHasNoAssociations(EntityInterface $entity): void
598
    {
599
        $class         = $this->getTestedEntityFqn();
600
        $entityManager = $this->getEntityManager();
601
        $reLoaded      = $this->loadEntity($class, $entity->getId(), $entityManager);
602
        $entityDump    = $this->dump($entity);
603
        $reLoadedDump  = $this->dump($reLoaded);
604
        self::assertEquals($entityDump, $reLoadedDump);
605
        $this->assertAllAssociationsAreEmpty($reLoaded);
606
    }
607
608
    protected function dump(EntityInterface $entity): string
609
    {
610
        return $this->dumper->dump($entity, $this->getEntityManager());
611
    }
612
613
    /**
614
     * @depends testConstructor
615
     *
616
     * @param EntityInterface $entity
617
     */
618
    public function testGetGetters(EntityInterface $entity)
619
    {
620
        $getters = $entity::getDoctrineStaticMeta()->getGetters();
621
        self::assertNotEmpty($getters);
622
        foreach ($getters as $getter) {
623
            self::assertRegExp('%^(get|is|has).+%', $getter);
624
        }
625
    }
626
627
    /**
628
     * @depends testConstructor
629
     *
630
     * @param EntityInterface $entity
631
     */
632
    public function testSetSetters(EntityInterface $entity)
633
    {
634
        $setters = $entity::getDoctrineStaticMeta()->getSetters();
635
        self::assertNotEmpty($setters);
636
        foreach ($setters as $setter) {
637
            self::assertRegExp('%^(set|add).+%', $setter);
638
        }
639
    }
640
641
    /**
642
     * Loop through entity fields and find unique ones
643
     *
644
     * Then ensure that the unique rule is being enforced as expected
645
     *
646
     * @throws ConfigException
647
     * @throws \Doctrine\ORM\Mapping\MappingException
648
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
649
     * @throws \ReflectionException
650
     * @throws \Doctrine\ORM\Mapping\MappingException
651
     */
652
    public function testUniqueFieldsMustBeUnique(): void
653
    {
654
        $class         = $this->getTestedEntityFqn();
655
        $entityManager = $this->getEntityManager();
656
        $meta          = $entityManager->getClassMetadata($class);
657
        $uniqueFields  = [];
658
        foreach ($meta->getFieldNames() as $fieldName) {
659
            if (IdFieldInterface::PROP_ID === $fieldName) {
660
                continue;
661
            }
662
            if (true === $this->isUniqueField($meta, $fieldName)) {
663
                $uniqueFields[] = $fieldName;
664
            }
665
        }
666
        if ([] === $uniqueFields) {
667
            self::markTestSkipped('No unique fields to check');
668
669
            return;
670
        }
671
        foreach ($uniqueFields as $fieldName) {
672
            $primary      = $this->testEntityGenerator->generateEntity($entityManager, $class);
673
            $secondary    = $this->testEntityGenerator->generateEntity($entityManager, $class);
674
            $getter       = 'get' . $fieldName;
675
            $setter       = 'set' . $fieldName;
676
            $primaryValue = $primary->$getter();
677
            $secondary->$setter($primaryValue);
678
            $saver = $this->entitySaverFactory->getSaverForEntity($primary);
679
            $this->expectException(UniqueConstraintViolationException::class);
680
            $saver->saveAll([$primary, $secondary]);
681
        }
682
    }
683
684
    /**
685
     * @throws ConfigException
686
     * @throws \Exception
687
     * @SuppressWarnings(PHPMD.StaticAccess)
688
     */
689
    protected function setup()
690
    {
691
        $this->initContainerAndSetClassProperties();
692
693
        $this->dumper = new EntityDebugDumper();
694
    }
695
696
    /**
697
     * Get a \ReflectionClass for the currently tested Entity
698
     *
699
     * @return \ts\Reflection\ReflectionClass
700
     * @throws \ReflectionException
701
     */
702
    protected function getTestedEntityReflectionClass(): \ts\Reflection\ReflectionClass
703
    {
704
        if (null === $this->testedEntityReflectionClass) {
705
            $this->testedEntityReflectionClass = new \ts\Reflection\ReflectionClass(
706
                $this->getTestedEntityFqn()
707
            );
708
        }
709
710
        return $this->testedEntityReflectionClass;
711
    }
712
713
    /**
714
     * @throws ConfigException
715
     */
716
    protected function tearDown()
717
    {
718
        $entityManager = $this->getEntityManager(false);
719
        $connection    = $entityManager->getConnection();
720
721
        $entityManager->close();
722
        $connection->close();
723
    }
724
}
725