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 ( 6a4e57...36c412 )
by joseph
26:54
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\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
    protected 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 = static::$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
    protected function initContainerAndSetClassProperties(): void
159
    {
160
        $testConfig = $this->getTestContainerConfig();
161
        $this->buildContainer($testConfig);
162
        $this->entityManager       = static::$container->get(EntityManagerInterface::class);
163
        $this->entitySaverFactory  = static::$container->get(EntitySaverFactory::class);
164
        $this->testEntityGenerator = static::$container->get(TestEntityGeneratorFactory::class)
165
                                                       ->setFakerDataProviderClasses(
166
                                                           static::FAKER_DATA_PROVIDERS
167
                                                       )
168
                                                       ->createForEntityFqn($this->getTestedEntityFqn());
169
        $this->codeHelper          = static::$container->get(CodeHelper::class);
170
    }
171
172
    /**
173
     * @throws ConfigException
174
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
175
     * @SuppressWarnings(PHPMD.Superglobals)
176
     * @SuppressWarnings(PHPMD.StaticAccess)
177
     */
178
    protected function getTestContainerConfig(): array
179
    {
180
        SimpleEnv::setEnv(Config::getProjectRootDirectory() . '/.env');
181
        $testConfig                                 = $_SERVER;
182
        $testConfig[ConfigInterface::PARAM_DB_NAME] = $_SERVER[ConfigInterface::PARAM_DB_NAME] . '_test';
183
184
        return $testConfig;
185
    }
186
187
    /**
188
     * @SuppressWarnings(PHPMD.StaticAccess)
189
     * @param array $testConfig
190
     */
191
    protected function buildContainer(array $testConfig): void
192
    {
193
        static::$container = TestContainerFactory::getContainer($testConfig);
194
    }
195
196
    /**
197
     * Get the fully qualified name of the Entity we are testing,
198
     * assumes EntityNameTest as the entity class short name
199
     *
200
     * @return string
201
     */
202
    protected function getTestedEntityFqn(): string
203
    {
204
        if (null === $this->testedEntityFqn) {
205
            $this->testedEntityFqn = \substr(static::class, 0, -4);
206
        }
207
208
        return $this->testedEntityFqn;
209
    }
210
211
    public function testConstructor(): EntityInterface
212
    {
213
        $class  = $this->getTestedEntityFqn();
214
        $entity = new $class();
215
        self::assertInstanceOf($class, $entity);
216
217
        return $entity;
218
    }
219
220
    /**
221
     * @param EntityInterface $entity
222
     *
223
     * @throws ConfigException
224
     * @throws \Doctrine\ORM\Query\QueryException
225
     * @throws \ReflectionException
226
     * @depends testConstructor
227
     */
228
    public function testGetDefaults(EntityInterface $entity)
229
    {
230
        $this->callEntityGettersAndAssertNotNull($entity);
231
    }
232
233
    /**
234
     * Loop through Entity fields, call the getter and where possible assert there is a value returned
235
     *
236
     * @param EntityInterface $entity
237
     *
238
     * @throws ConfigException
239
     * @throws \Doctrine\ORM\Query\QueryException
240
     * @throws \ReflectionException
241
     * @SuppressWarnings(PHPMD.StaticAccess)
242
     */
243
    protected function callEntityGettersAndAssertNotNull(EntityInterface $entity): void
244
    {
245
        $class         = $this->getTestedEntityFqn();
246
        $entityManager = $this->getEntityManager();
247
        $meta          = $entityManager->getClassMetadata($class);
248
        foreach ($meta->getFieldNames() as $fieldName) {
249
            $type   = PersisterHelper::getTypeOfField($fieldName, $meta, $entityManager)[0];
250
            $method = $this->getGetterNameForField($fieldName, $type);
251
            if (\ts\stringContains($method, '.')) {
252
                list($getEmbeddableMethod,) = explode('.', $method);
253
                $embeddable = $entity->$getEmbeddableMethod();
254
                self::assertInstanceOf(AbstractEmbeddableObject::class, $embeddable);
255
                continue;
256
            }
257
            $reflectionMethod = new \ReflectionMethod($entity, $method);
258
            if ($reflectionMethod->hasReturnType()) {
259
                $returnType = $reflectionMethod->getReturnType();
260
                $allowsNull = $returnType->allowsNull();
261
                if ($allowsNull) {
262
                    // As we can't assert anything here so simply call
263
                    // the method and allow the type hint to raise any
264
                    // errors.
265
                    $entity->$method();
266
                    continue;
267
                }
268
                self::assertNotNull($entity->$method(), "$fieldName getter returned null");
269
                continue;
270
            }
271
            // If there is no return type then we can't assert anything,
272
            // but again we can just call the getter to check for errors
273
            $entity->$method();
274
        }
275
        if (0 === $this->getCount()) {
276
            self::markTestSkipped('No assertable getters in this Entity');
277
        }
278
    }
279
280
    protected function getGetterNameForField(string $fieldName, string $type): string
281
    {
282
        if ($type === 'boolean') {
283
            return $this->codeHelper->getGetterMethodNameForBoolean($fieldName);
284
        }
285
286
        return 'get' . $fieldName;
287
    }
288
289
    /**
290
     * Test that we have correctly generated an instance of our test entity
291
     *
292
     * @throws ConfigException
293
     * @throws \Doctrine\ORM\Query\QueryException
294
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
295
     * @throws \Exception
296
     * @throws \ReflectionException
297
     * @SuppressWarnings(PHPMD.StaticAccess)
298
     */
299
    public function testGeneratedCreate(): EntityInterface
300
    {
301
        $entityManager = $this->getEntityManager();
302
        $class         = $this->getTestedEntityFqn();
303
        $generated     = $this->testEntityGenerator->generateEntity($entityManager, $class);
304
        self::assertInstanceOf($class, $generated);
305
        $this->testEntityGenerator->addAssociationEntities($entityManager, $generated);
306
        $this->validateEntity($generated);
307
        $this->callEntityGettersAndAssertNotNull($generated);
308
        $this->entitySaverFactory->getSaverForEntity($generated)->save($generated);
309
310
        return $generated;
311
    }
312
313
    protected function validateEntity(EntityInterface $entity): void
314
    {
315
        $entity->validate();
316
    }
317
318
    /**
319
     * Test that we can load the entity and then get and set
320
     *
321
     * @param EntityInterface $entity
322
     *
323
     * @return EntityInterface|null
324
     * @throws ConfigException
325
     * @throws \Doctrine\ORM\Mapping\MappingException
326
     * @throws \Doctrine\ORM\Query\QueryException
327
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
328
     * @throws \ReflectionException
329
     * @depends testGeneratedCreate
330
     */
331
    public function testLoadedEntity(EntityInterface $entity): EntityInterface
332
    {
333
        $class         = $this->getTestedEntityFqn();
334
        $entityManager = $this->getEntityManager();
335
        $loaded        = $this->loadEntity($class, $entity->getId(), $entityManager);
336
        self::assertSame((string)$entity->getId(), (string)$loaded->getId());
337
        self::assertInstanceOf($class, $loaded);
338
        $this->updateEntityFields($loaded);
339
        $this->assertAllAssociationsAreNotEmpty($loaded);
340
        $this->validateEntity($loaded);
341
        $this->removeAllAssociations($loaded);
342
        $this->assertAllAssociationsAreEmpty($loaded);
343
        $this->entitySaverFactory->getSaverForEntity($loaded)->save($loaded);
344
345
        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...
346
    }
347
348
    /**
349
     * @param string                 $class
350
     * @param int|string             $id
351
     * @param EntityManagerInterface $entityManager
352
     *
353
     * @return EntityInterface|null
354
     */
355
    protected function loadEntity(string $class, $id, EntityManagerInterface $entityManager): ?EntityInterface
356
    {
357
        return $entityManager->getRepository($class)->find($id);
358
    }
359
360
    /**
361
     * Generate a new entity and then update our Entity with the values from the generated one
362
     *
363
     * @param EntityInterface $entity
364
     *
365
     * @throws ConfigException
366
     * @throws \Doctrine\ORM\Mapping\MappingException
367
     * @throws \Doctrine\ORM\Query\QueryException
368
     * @throws \ReflectionException
369
     * @SuppressWarnings(PHPMD.StaticAccess)
370
     */
371
    protected function updateEntityFields(EntityInterface $entity): void
372
    {
373
        $class         = $this->getTestedEntityFqn();
374
        $entityManager = $this->getEntityManager();
375
        $meta          = $entityManager->getClassMetadata($class);
376
        $entityManager = $this->getEntityManager();
377
        $class         = $this->getTestedEntityFqn();
378
        $generated     = $this->testEntityGenerator->generateEntity($entityManager, $class, 10);
379
        $identifiers   = \array_flip($meta->getIdentifier());
380
        foreach ($meta->getFieldNames() as $fieldName) {
381
            if (isset($identifiers[$fieldName])) {
382
                continue;
383
            }
384
            if (true === $this->isUniqueField($meta, $fieldName)) {
385
                continue;
386
            }
387
            $setter = 'set' . $fieldName;
388
            if (!\method_exists($entity, $setter)) {
389
                continue;
390
            }
391
            $type   = PersisterHelper::getTypeOfField($fieldName, $meta, $entityManager)[0];
392
            $getter = $this->getGetterNameForField($fieldName, $type);
393
            if (\ts\stringContains($getter, '.')) {
394
                list($getEmbeddableMethod, $fieldInEmbeddable) = explode('.', $getter);
395
                $getterInEmbeddable  = 'get' . $fieldInEmbeddable;
396
                $setterInEmbeddable  = 'set' . $fieldInEmbeddable;
397
                $generatedEmbeddable = $generated->$getEmbeddableMethod();
398
                $embeddable          = $entity->$getEmbeddableMethod();
399
                if (\method_exists($embeddable, $setterInEmbeddable)
400
                    && \method_exists($embeddable, $getterInEmbeddable)
401
                ) {
402
                    $embeddable->$setterInEmbeddable($generatedEmbeddable->$getterInEmbeddable());
403
                }
404
                continue;
405
            }
406
            $entity->$setter($generated->$getter());
407
        }
408
    }
409
410
    protected function isUniqueField(ClassMetadata $meta, string $fieldName): bool
411
    {
412
        $fieldMapping = $meta->getFieldMapping($fieldName);
413
414
        return array_key_exists('unique', $fieldMapping) && true === $fieldMapping['unique'];
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
                $this->assertCorrectMappings($class, $mapping, $entityManager);
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
     * Check the mapping of our class and the associated entity to make sure it's configured properly on both sides.
452
     * Very easy to get wrong. This is in addition to the standard Schema Validation
453
     *
454
     * @param string                 $classFqn
455
     * @param array                  $mapping
456
     * @param EntityManagerInterface $entityManager
457
     */
458
    protected function assertCorrectMappings(string $classFqn, array $mapping, EntityManagerInterface $entityManager)
459
    {
460
        $pass                                 = false;
461
        $associationFqn                       = $mapping['targetEntity'];
462
        $associationMeta                      = $entityManager->getClassMetadata($associationFqn);
463
        $classTraits                          = $entityManager->getClassMetadata($classFqn)
464
                                                              ->getReflectionClass()
465
                                                              ->getTraits();
466
        $unidirectionalTraitShortNamePrefixes = [
467
            'Has' . $associationFqn::getDoctrineStaticMeta()->getSingular() . RelationsGenerator::PREFIX_UNIDIRECTIONAL,
468
            'Has' . $associationFqn::getDoctrineStaticMeta()->getPlural() . RelationsGenerator::PREFIX_UNIDIRECTIONAL,
469
        ];
470
        foreach ($classTraits as $trait) {
471
            foreach ($unidirectionalTraitShortNamePrefixes as $namePrefix) {
472
                if (0 === \stripos($trait->getShortName(), $namePrefix)) {
473
                    return;
474
                }
475
            }
476
        }
477
        foreach ($associationMeta->getAssociationMappings() as $associationMapping) {
478
            if ($classFqn === $associationMapping['targetEntity']) {
479
                $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

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