GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#138)
by joseph
96:08 queued 65:06
created

AbstractEntityTest::dump()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
ccs 0
cts 2
cp 0
cc 1
nc 1
nop 1
crap 2
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->initContainer();
151
                $this->entityManager = self::$container->get(EntityManagerInterface::class);
152
            }
153
        }
154
155
        return $this->entityManager;
156
    }
157
158
    /**
159
     * @throws ConfigException
160
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
161
     * @SuppressWarnings(PHPMD.Superglobals)
162
     * @SuppressWarnings(PHPMD.StaticAccess)
163
     */
164
    protected function initContainer(): void
165
    {
166
        SimpleEnv::setEnv(Config::getProjectRootDirectory() . '/.env');
167
        $testConfig                                 = $_SERVER;
168
        $testConfig[ConfigInterface::PARAM_DB_NAME] = $_SERVER[ConfigInterface::PARAM_DB_NAME] . '_test';
169
        self::$container                            = TestContainerFactory::getContainer($testConfig);
170
    }
171
172
    /**
173
     * Get the fully qualified name of the Entity we are testing,
174
     * assumes EntityNameTest as the entity class short name
175
     *
176
     * @return string
177
     */
178
    protected function getTestedEntityFqn(): string
179
    {
180
        if (null === $this->testedEntityFqn) {
181
            $this->testedEntityFqn = \substr(static::class, 0, -4);
182
        }
183
184
        return $this->testedEntityFqn;
185
    }
186
187
    public function testConstructor(): EntityInterface
188
    {
189
        $class  = $this->getTestedEntityFqn();
190
        $entity = new $class();
191
        self::assertInstanceOf($class, $entity);
192
193
        return $entity;
194
    }
195
196
    /**
197
     * @param EntityInterface $entity
198
     *
199
     * @throws ConfigException
200
     * @throws \Doctrine\ORM\Query\QueryException
201
     * @throws \ReflectionException
202
     * @depends testConstructor
203
     */
204
    public function testGetDefaults(EntityInterface $entity)
205
    {
206
        $this->callEntityGettersAndAssertNotNull($entity);
207
    }
208
209
    /**
210
     * Loop through Entity fields, call the getter and where possible assert there is a value returned
211
     *
212
     * @param EntityInterface $entity
213
     *
214
     * @throws ConfigException
215
     * @throws \Doctrine\ORM\Query\QueryException
216
     * @throws \ReflectionException
217
     * @SuppressWarnings(PHPMD.StaticAccess)
218
     */
219
    protected function callEntityGettersAndAssertNotNull(EntityInterface $entity): void
220
    {
221
        $class         = $this->getTestedEntityFqn();
222
        $entityManager = $this->getEntityManager();
223
        $meta          = $entityManager->getClassMetadata($class);
224
        foreach ($meta->getFieldNames() as $fieldName) {
225
            $type   = PersisterHelper::getTypeOfField($fieldName, $meta, $entityManager)[0];
226
            $method = $this->getGetterNameForField($fieldName, $type);
227
            if (\ts\stringContains($method, '.')) {
228
                list($getEmbeddableMethod,) = explode('.', $method);
229
                $embeddable = $entity->$getEmbeddableMethod();
230
                self::assertInstanceOf(AbstractEmbeddableObject::class, $embeddable);
231
                continue;
232
            }
233
            $reflectionMethod = new \ReflectionMethod($entity, $method);
234
            if ($reflectionMethod->hasReturnType()) {
235
                $returnType = $reflectionMethod->getReturnType();
236
                $allowsNull = $returnType->allowsNull();
237
                if ($allowsNull) {
238
                    // As we can't assert anything here so simply call
239
                    // the method and allow the type hint to raise any
240
                    // errors.
241
                    $entity->$method();
242
                    continue;
243
                }
244
                self::assertNotNull($entity->$method(), "$fieldName getter returned null");
245
                continue;
246
            }
247
            // If there is no return type then we can't assert anything,
248
            // but again we can just call the getter to check for errors
249
            $entity->$method();
250
        }
251
        if (0 === $this->getCount()) {
252
            self::markTestSkipped('No assertable getters in this Entity');
253
        }
254
    }
255
256
    protected function getGetterNameForField(string $fieldName, string $type): string
257
    {
258
        if ($type === 'boolean') {
259
            return $this->codeHelper->getGetterMethodNameForBoolean($fieldName);
260
        }
261
262
        return 'get' . $fieldName;
263
    }
264
265
    /**
266
     * Test that we have correctly generated an instance of our test entity
267
     *
268
     * @throws ConfigException
269
     * @throws \Doctrine\ORM\Query\QueryException
270
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
271
     * @throws \Exception
272
     * @throws \ReflectionException
273
     * @SuppressWarnings(PHPMD.StaticAccess)
274
     */
275
    public function testGeneratedCreate(): EntityInterface
276
    {
277
        $entityManager = $this->getEntityManager();
278
        $class         = $this->getTestedEntityFqn();
279
        $generated     = $this->testEntityGenerator->generateEntity($entityManager, $class);
280
        self::assertInstanceOf($class, $generated);
281
        $this->testEntityGenerator->addAssociationEntities($entityManager, $generated);
282
        $this->validateEntity($generated);
283
        $this->callEntityGettersAndAssertNotNull($generated);
284
        $this->entitySaverFactory->getSaverForEntity($generated)->save($generated);
285
286
        return $generated;
287
    }
288
289
    protected function validateEntity(EntityInterface $entity): void
290
    {
291
        $entity->validate();
292
    }
293
294
    /**
295
     * Test that we can load the entity and then get and set
296
     *
297
     * @param EntityInterface $entity
298
     *
299
     * @return EntityInterface|null
300
     * @throws ConfigException
301
     * @throws \Doctrine\ORM\Mapping\MappingException
302
     * @throws \Doctrine\ORM\Query\QueryException
303
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
304
     * @throws \ReflectionException
305
     * @depends testGeneratedCreate
306
     */
307
    public function testLoadedEntity(EntityInterface $entity): EntityInterface
308
    {
309
        $class         = $this->getTestedEntityFqn();
310
        $entityManager = $this->getEntityManager();
311
        $loaded        = $this->loadEntity($class, $entity->getId(), $entityManager);
312
        self::assertSame((string)$entity->getId(), (string)$loaded->getId());
313
        self::assertInstanceOf($class, $loaded);
314
        $this->updateEntityFields($loaded);
315
        $this->assertAllAssociationsAreNotEmpty($loaded);
316
        $this->validateEntity($loaded);
317
        $this->removeAllAssociations($loaded);
318
        $this->assertAllAssociationsAreEmpty($loaded);
319
        $this->entitySaverFactory->getSaverForEntity($loaded)->save($loaded);
320
321
        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...
322
    }
323
324
    /**
325
     * @param string                 $class
326
     * @param int|string             $id
327
     * @param EntityManagerInterface $entityManager
328
     *
329
     * @return EntityInterface|null
330
     */
331
    protected function loadEntity(string $class, $id, EntityManagerInterface $entityManager): ?EntityInterface
332
    {
333
        return $entityManager->getRepository($class)->find($id);
334
    }
335
336
    /**
337
     * Generate a new entity and then update our Entity with the values from the generated one
338
     *
339
     * @param EntityInterface $entity
340
     *
341
     * @throws ConfigException
342
     * @throws \Doctrine\ORM\Mapping\MappingException
343
     * @throws \Doctrine\ORM\Query\QueryException
344
     * @throws \ReflectionException
345
     * @SuppressWarnings(PHPMD.StaticAccess)
346
     */
347
    protected function updateEntityFields(EntityInterface $entity): void
348
    {
349
        $class         = $this->getTestedEntityFqn();
350
        $entityManager = $this->getEntityManager();
351
        $meta          = $entityManager->getClassMetadata($class);
352
        $entityManager = $this->getEntityManager();
353
        $class         = $this->getTestedEntityFqn();
354
        $generated     = $this->testEntityGenerator->generateEntity($entityManager, $class, 10);
355
        $identifiers   = \array_flip($meta->getIdentifier());
356
        foreach ($meta->getFieldNames() as $fieldName) {
357
            if (isset($identifiers[$fieldName])) {
358
                continue;
359
            }
360
            if (true === $this->isUniqueField($meta, $fieldName)) {
361
                continue;
362
            }
363
            $setter = 'set' . $fieldName;
364
            if (!\method_exists($entity, $setter)) {
365
                continue;
366
            }
367
            $type   = PersisterHelper::getTypeOfField($fieldName, $meta, $entityManager)[0];
368
            $getter = $this->getGetterNameForField($fieldName, $type);
369
            if (\ts\stringContains($getter, '.')) {
370
                list($getEmbeddableMethod, $fieldInEmbeddable) = explode('.', $getter);
371
                $getterInEmbeddable  = 'get' . $fieldInEmbeddable;
372
                $setterInEmbeddable  = 'set' . $fieldInEmbeddable;
373
                $generatedEmbeddable = $generated->$getEmbeddableMethod();
374
                $embeddable          = $entity->$getEmbeddableMethod();
375
                if (\method_exists($embeddable, $setterInEmbeddable)
376
                    && \method_exists($embeddable, $getterInEmbeddable)
377
                ) {
378
                    $embeddable->$setterInEmbeddable($generatedEmbeddable->$getterInEmbeddable());
379
                }
380
                continue;
381
            }
382
            $entity->$setter($generated->$getter());
383
        }
384
    }
385
386
    protected function isUniqueField(ClassMetadata $meta, string $fieldName): bool
387
    {
388
        $fieldMapping = $meta->getFieldMapping($fieldName);
389
390
        return array_key_exists('unique', $fieldMapping) && true === $fieldMapping['unique'];
391
    }
392
393
    protected function assertAllAssociationsAreNotEmpty(EntityInterface $entity)
394
    {
395
        $entityManager = $this->getEntityManager();
396
        $class         = $this->getTestedEntityFqn();
397
        $meta          = $entityManager->getClassMetadata($class);
398
        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

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

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