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 ( a81a76...d980e5 )
by joseph
11s
created

TestEntityGenerator::addAssociationEntities()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 49
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 37
CRAP Score 8.001

Importance

Changes 0
Metric Value
eloc 39
dl 0
loc 49
ccs 37
cts 38
cp 0.9737
rs 8.0515
c 0
b 0
f 0
cc 8
nc 10
nop 2
crap 8.001
1
<?php declare(strict_types=1);
2
3
namespace EdmondsCommerce\DoctrineStaticMeta\Entity\Testing;
4
5
use Doctrine\Common\Collections\ArrayCollection;
6
use Doctrine\ORM\EntityManager;
7
use Doctrine\ORM\Mapping\ClassMetadata;
8
use Doctrine\ORM\Mapping\ClassMetadataInfo;
9
use Doctrine\ORM\PersistentCollection;
10
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\NamespaceHelper;
11
use EdmondsCommerce\DoctrineStaticMeta\Entity\Interfaces\EntityInterface;
12
use EdmondsCommerce\DoctrineStaticMeta\Entity\Savers\EntitySaverFactory;
13
use EdmondsCommerce\DoctrineStaticMeta\Entity\Validation\EntityValidatorFactory;
14
use Faker;
15
16
/**
17
 * Class TestEntityGenerator
18
 *
19
 * This class handles utilising Faker to build up an Entity and then also possible build associated entities and handle
20
 * the association
21
 *
22
 * Unique columns are guaranteed to have a totally unique value in this particular process, but not between processes
23
 *
24
 * @package EdmondsCommerce\DoctrineStaticMeta\Entity\Testing
25
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
26
 */
27
class TestEntityGenerator
28
{
29
    /**
30
     * @var Faker\Generator
31
     */
32
    protected static $generator;
33
    /**
34
     * These two are used to keep track of unique fields and ensure we dont accidently make apply none unique values
35
     *
36
     * @var array
37
     */
38
    private static $uniqueStrings = [];
39
    /**
40
     * @var int
41
     */
42
    private static $uniqueInt;
43
    /**
44
     * @var EntityManager
45
     */
46
    protected $entityManager;
47
    /**
48
     * An array of fieldNames to class names that are to be instantiated as column formatters as required
49
     *
50
     * @var array|string[]
51
     */
52
    protected $fakerDataProviderClasses;
53
54
    /**
55
     * A cache of instantiated column data providers
56
     *
57
     * @var array
58
     */
59
    protected $fakerDataProviderObjects = [];
60
61
    /**
62
     * Reflection of the tested entity
63
     *
64
     * @var \ts\Reflection\ReflectionClass
65
     */
66
    protected $testedEntityReflectionClass;
67
    /**
68
     * @var EntitySaverFactory
69
     */
70
    protected $entitySaverFactory;
71
    /**
72
     * @var EntityValidatorFactory
73
     */
74
    protected $entityValidatorFactory;
75
76
    /**
77
     * TestEntityGenerator constructor.
78
     *
79
     * @param float|null                     $seed
80
     * @param array|string[]                 $fakerDataProviderClasses
81
     * @param \ts\Reflection\ReflectionClass $testedEntityReflectionClass
82
     * @param EntitySaverFactory             $entitySaverFactory
83
     * @param EntityValidatorFactory         $entityValidatorFactory
84
     * @SuppressWarnings(PHPMD.StaticAccess)
85
     */
86 18
    public function __construct(
87
        ?float $seed,
88
        array $fakerDataProviderClasses,
89
        \ts\Reflection\ReflectionClass $testedEntityReflectionClass,
90
        EntitySaverFactory $entitySaverFactory,
91
        EntityValidatorFactory $entityValidatorFactory
92
    ) {
93 18
        $this->initFakerGenerator($seed);
94 18
        $this->fakerDataProviderClasses    = $fakerDataProviderClasses;
95 18
        $this->testedEntityReflectionClass = $testedEntityReflectionClass;
96 18
        $this->entitySaverFactory          = $entitySaverFactory;
97 18
        $this->entityValidatorFactory      = $entityValidatorFactory;
98 18
    }
99
100
    /**
101
     * @param float|null $seed
102
     * @SuppressWarnings(PHPMD.StaticAccess)
103
     */
104 18
    protected function initFakerGenerator(?float $seed): void
105
    {
106 18
        if (null === self::$generator) {
107 1
            self::$generator = Faker\Factory::create();
108 1
            if (null !== $seed) {
109 1
                self::$generator->seed($seed);
110
            }
111
        }
112 18
    }
113
114
    /**
115
     * @param EntityManager   $entityManager
116
     * @param EntityInterface $generated
117
     *
118
     * @throws \Doctrine\ORM\Mapping\MappingException
119
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
120
     * @throws \ErrorException
121
     * @throws \ReflectionException
122
     * @SuppressWarnings(PHPMD.ElseExpression)
123
     */
124 1
    public function addAssociationEntities(
125
        EntityManager $entityManager,
126
        EntityInterface $generated
127
    ): void {
128 1
        $class    = $this->testedEntityReflectionClass->getName();
129 1
        $meta     = $entityManager->getClassMetadata($class);
130 1
        $mappings = $meta->getAssociationMappings();
131 1
        if (empty($mappings)) {
132
            return;
133
        }
134 1
        $namespaceHelper = new NamespaceHelper();
135 1
        $methods         = array_map('strtolower', get_class_methods($generated));
136 1
        foreach ($mappings as $mapping) {
137 1
            $mappingEntityClass = $mapping['targetEntity'];
138 1
            $mappingEntity      = $this->generateEntity($entityManager, $mappingEntityClass);
139 1
            $errorMessage = "Error adding association entity $mappingEntityClass to $class: %s";
140 1
            $this->entitySaverFactory->getSaverForEntity($mappingEntity)->save($mappingEntity);
141 1
            $mappingEntityPluralInterface = $namespaceHelper->getHasPluralInterfaceFqnForEntity($mappingEntityClass);
142 1
            if (\interface_exists($mappingEntityPluralInterface)
143 1
                && $this->testedEntityReflectionClass->implementsInterface($mappingEntityPluralInterface)
144
            ) {
145 1
                $this->assertSame(
146 1
                    $mappingEntityClass::getPlural(),
147 1
                    $mapping['fieldName'],
148 1
                    sprintf($errorMessage, ' mapping should be plural')
149
                );
150 1
                $getter = 'get' . $mappingEntityClass::getPlural();
151 1
                $method = 'add' . $mappingEntityClass::getSingular();
152
            } else {
153 1
                $this->assertSame(
154 1
                    $mappingEntityClass::getSingular(),
155 1
                    $mapping['fieldName'],
156 1
                    sprintf($errorMessage, ' mapping should be singular')
157
                );
158 1
                $getter = 'get' . $mappingEntityClass::getSingular();
159 1
                $method = 'set' . $mappingEntityClass::getSingular();
160
            }
161 1
            $this->assertInArray(
162 1
                strtolower($method),
163 1
                $methods,
164 1
                sprintf($errorMessage, $method . ' method is not defined')
165
            );
166 1
            $currentlySet = $generated->$getter();
167 1
            switch (true) {
168
                case $currentlySet === null:
169 1
                case $currentlySet === []:
170 1
                case $currentlySet instanceof PersistentCollection:
171 1
                    $generated->$method($mappingEntity);
172 1
                    break;
173
            }
174
        }
175 1
    }
176
177
    /**
178
     * Generate an Entity. Optionally provide an offset from the first entity
179
     *
180
     * @param EntityManager $entityManager
181
     * @param string        $class
182
     *
183
     * @param int           $offset
184
     *
185
     * @return EntityInterface
186
     * @throws \Doctrine\ORM\Mapping\MappingException
187
     * @throws \ReflectionException
188
     * @SuppressWarnings(PHPMD.StaticAccess)
189
     */
190 3
    public function generateEntity(EntityManager $entityManager, string $class, int $offset = 0): EntityInterface
191
    {
192
193 3
        $result = $this->generateEntities($entityManager, $class, 1, $offset);
194
195 3
        return current($result);
196
    }
197
198
    /**
199
     * Generate Entities.
200
     *
201
     * Optionally discard the first generated entities up to the value of offset
202
     *
203
     * @param EntityManager $entityManager
204
     * @param string        $entityFqn
205
     * @param int           $num
206
     *
207
     * @param int           $offset
208
     *
209
     * @return array|EntityInterface[]
210
     * @throws \Doctrine\ORM\Mapping\MappingException
211
     * @throws \ReflectionException
212
     */
213 18
    public function generateEntities(EntityManager $entityManager, string $entityFqn, int $num, int $offset = 0): array
214
    {
215 18
        $this->entityManager = $entityManager;
216 18
        $columnFormatters    = $this->generateColumnFormatters($entityManager, $entityFqn);
217 18
        $meta                = $entityManager->getClassMetadata($entityFqn);
218 18
        $entities            = [];
219 18
        for ($i = 0; $i < ($num + $offset); $i++) {
220 18
            $entity = new $entityFqn($this->entityValidatorFactory);
221 18
            $this->fillColumns($entity, $columnFormatters, $meta);
222 18
            if ($i < $offset) {
223 1
                continue;
224
            }
225 18
            $entities[] = $entity;
226
        }
227 18
        $this->entitySaverFactory->getSaverForEntityFqn($entityFqn)->saveAll($entities);
228
229 18
        return $entities;
230
    }
231
232
    /**
233
     * @param EntityManager $entityManager
234
     * @param string        $entityFqn
235
     *
236
     * @return array
237
     * @throws \Doctrine\ORM\Mapping\MappingException
238
     */
239 18
    protected function generateColumnFormatters(EntityManager $entityManager, string $entityFqn): array
240
    {
241 18
        $meta              = $entityManager->getClassMetadata($entityFqn);
242 18
        $guessedFormatters = (new Faker\ORM\Doctrine\EntityPopulator($meta))->guessColumnFormatters(self::$generator);
243 18
        $customFormatters  = [];
244 18
        $mappings          = $meta->getAssociationMappings();
245 18
        $this->initialiseColumnFormatters($meta, $mappings, $guessedFormatters);
246 18
        $fieldNames = $meta->getFieldNames();
247
248 18
        foreach ($fieldNames as $fieldName) {
249 18
            if (isset($customFormatters[$fieldName])) {
250
                continue;
251
            }
252 18
            if (true === $this->addFakerDataProviderToColumnFormatters($customFormatters, $fieldName)) {
253
                continue;
254
            }
255 18
            $fieldMapping = $meta->getFieldMapping($fieldName);
256 18
            if (true === ($fieldMapping['unique'] ?? false)) {
257
                $this->addUniqueColumnFormatter($fieldMapping, $customFormatters, $fieldName);
258 18
                continue;
259
            }
260
        }
261
262 18
        return array_merge($guessedFormatters, $customFormatters);
263
    }
264
265
    /**
266
     * Loop through mappings and initialise empty array collections for colection valued mappings, or null if not
267
     *
268
     * @param ClassMetadataInfo $meta
269
     * @param array             $mappings
270
     * @param array             $columnFormatters
271
     */
272 18
    protected function initialiseColumnFormatters(
273
        ClassMetadataInfo $meta,
274
        array &$mappings,
275
        array &$columnFormatters
276
    ): void {
277 18
        foreach ($mappings as $mapping) {
278 2
            if ($meta->isCollectionValuedAssociation($mapping['fieldName'])) {
279 2
                $columnFormatters[$mapping['fieldName']] = new ArrayCollection();
280 2
                continue;
281
            }
282
283 2
            if (isset($mapping['joinColumns']) && count($mapping['joinColumns']) === 1
284 2
                && ($mapping['joinColumns'][0]['nullable'] ?? null) === false
285
            ) {
286
                $columnFormatters[$mapping['fieldName']] = function () use ($mapping) {
287
                    $entity = $this->generateEntity($this->entityManager, $mapping['targetEntity']);
288
                    $this->entitySaverFactory->getSaverForEntity($entity)->save($entity);
289
290
                    return $entity;
291
                };
292
                continue;
293
            }
294 2
            $columnFormatters[$mapping['fieldName']] = null;
295
        }
296 18
    }
297
298
    /**
299
     * Add a faker data provider to the columnFormatters array (by reference) if there is one available
300
     *
301
     * Handles instantiating and caching of the data providers
302
     *
303
     * @param array  $columnFormatters
304
     * @param string $fieldName
305
     *
306
     * @return bool
307
     */
308 18
    protected function addFakerDataProviderToColumnFormatters(array &$columnFormatters, string $fieldName): bool
309
    {
310 18
        if (!isset($this->fakerDataProviderClasses[$fieldName])) {
311 18
            return false;
312
        }
313
        if (!isset($this->fakerDataProviderObjects[$fieldName])) {
314
            $class                                      = $this->fakerDataProviderClasses[$fieldName];
315
            $this->fakerDataProviderObjects[$fieldName] = new $class(self::$generator);
316
        }
317
        $columnFormatters[$fieldName] = $this->fakerDataProviderObjects[$fieldName];
318
319
        return true;
320
    }
321
322
    protected function addUniqueColumnFormatter(array &$fieldMapping, array &$columnFormatters, string $fieldName): void
323
    {
324
        switch ($fieldMapping['type']) {
325
            case 'string':
326
                $columnFormatters[$fieldName] = $this->getUniqueString();
327
                break;
328
            case 'integer':
329
            case 'bigint':
330
                $columnFormatters[$fieldName] = $this->getUniqueInt();
331
                break;
332
            default:
333
                throw new \InvalidArgumentException('unique field has an unsupported type: '
334
                                                    . print_r($fieldMapping, true));
335
        }
336
    }
337
338
    protected function getUniqueString(): string
339
    {
340
        $string = 'unique string: ' . $this->getUniqueInt() . md5((string) time());
341
        while (isset(self::$uniqueStrings[$string])) {
342
            $string                       = md5((string) time());
343
            self::$uniqueStrings[$string] = true;
344
        }
345
346
        return $string;
347
    }
348
349
    protected function getUniqueInt(): int
350
    {
351
        return ++self::$uniqueInt;
352
    }
353
354 18
    protected function fillColumns(EntityInterface $entity, array &$columnFormatters, ClassMetadata $meta)
355
    {
356 18
        foreach ($columnFormatters as $field => $formatter) {
357 16
            if (null !== $formatter) {
358
                try {
359 16
                    $value = \is_callable($formatter) ? $formatter($entity) : $formatter;
360
                } catch (\InvalidArgumentException $ex) {
361
                    throw new \InvalidArgumentException(
362
                        sprintf(
363
                            'Failed to generate a value for %s::%s: %s',
364
                            \get_class($entity),
365
                            $field,
366
                            $ex->getMessage()
367
                        )
368
                    );
369
                }
370 16
                $meta->reflFields[$field]->setValue($entity, $value);
371
            }
372
        }
373 18
    }
374
375
    /**
376
     * Stub of PHPUnit Assertion method
377
     *
378
     * @param mixed  $expected
379
     * @param mixed  $actual
380
     * @param string $error
381
     *
382
     * @throws \ErrorException
383
     */
384 1
    protected function assertSame($expected, $actual, string $error): void
385
    {
386 1
        if ($expected !== $actual) {
387
            throw new \ErrorException($error);
388
        }
389 1
    }
390
391
    /**
392
     * Stub of PHPUnit Assertion method
393
     *
394
     * @param mixed  $needle
395
     * @param array  $haystack
396
     * @param string $error
397
     *
398
     * @throws \ErrorException
399
     */
400 1
    protected function assertInArray($needle, array $haystack, string $error): void
401
    {
402 1
        if (false === \in_array($needle, $haystack, true)) {
403
            throw new \ErrorException($error);
404
        }
405 1
    }
406
}
407