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 (#131)
by joseph
33:18 queued 10:43
created

TestEntityGenerator::fillColumns()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 9.9614

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 17
rs 9.5555
c 0
b 0
f 0
ccs 5
cts 12
cp 0.4167
cc 5
nc 6
nop 3
crap 9.9614
1
<?php declare(strict_types=1);
2
3
namespace EdmondsCommerce\DoctrineStaticMeta\Entity\Testing\EntityGenerator;
4
5
use Doctrine\Common\Collections\ArrayCollection;
6
use Doctrine\DBAL\Types\Type;
7
use Doctrine\ORM\EntityManagerInterface;
8
use Doctrine\ORM\Mapping\ClassMetadata;
9
use Doctrine\ORM\Mapping\ClassMetadataInfo;
10
use Doctrine\ORM\PersistentCollection;
11
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\NamespaceHelper;
12
use EdmondsCommerce\DoctrineStaticMeta\Entity\Factory\EntityFactory;
13
use EdmondsCommerce\DoctrineStaticMeta\Entity\Interfaces\EntityInterface;
14
use EdmondsCommerce\DoctrineStaticMeta\Entity\Savers\EntitySaverFactory;
15
use EdmondsCommerce\DoctrineStaticMeta\Entity\Validation\EntityValidatorFactory;
16
use EdmondsCommerce\DoctrineStaticMeta\MappingHelper;
17
use Faker;
18
19
/**
20
 * Class TestEntityGenerator
21
 *
22
 * This class handles utilising Faker to build up an Entity and then also possible build associated entities and handle
23
 * the association
24
 *
25
 * Unique columns are guaranteed to have a totally unique value in this particular process, but not between processes
26
 *
27
 * @package EdmondsCommerce\DoctrineStaticMeta\Entity\Testing
28
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
29
 */
30
class TestEntityGenerator
31
{
32
    /**
33
     * @var Faker\Generator
34
     */
35
    protected static $generator;
36
    /**
37
     * These two are used to keep track of unique fields and ensure we dont accidently make apply none unique values
38
     *
39
     * @var array
40
     */
41
    private static $uniqueStrings = [];
42
    /**
43
     * @var int
44
     */
45
    private static $uniqueInt;
46
    /**
47
     * @var EntityManagerInterface
48
     */
49
    protected $entityManager;
50
    /**
51
     * An array of fieldNames to class names that are to be instantiated as column formatters as required
52
     *
53
     * @var array|string[]
54
     */
55
    protected $fakerDataProviderClasses;
56
57
    /**
58
     * A cache of instantiated column data providers
59
     *
60
     * @var array
61
     */
62
    protected $fakerDataProviderObjects = [];
63
64
    /**
65
     * Reflection of the tested entity
66
     *
67
     * @var \ts\Reflection\ReflectionClass
68
     */
69
    protected $testedEntityReflectionClass;
70
    /**
71
     * @var EntitySaverFactory
72
     */
73
    protected $entitySaverFactory;
74
    /**
75
     * @var EntityValidatorFactory
76
     */
77
    protected $entityValidatorFactory;
78
    /**
79
     * @var EntityFactory
80
     */
81
    protected $entityFactory;
82
83
    /**
84
     * TestEntityGenerator constructor.
85
     *
86
     * @param array|string[]                 $fakerDataProviderClasses
87
     * @param \ts\Reflection\ReflectionClass $testedEntityReflectionClass
88
     * @param EntitySaverFactory             $entitySaverFactory
89
     * @param EntityValidatorFactory         $entityValidatorFactory
90
     * @param float|null                     $seed
91
     * @param EntityFactory|null             $entityFactory
92
     * @SuppressWarnings(PHPMD.StaticAccess)
93
     */
94 3
    public function __construct(
95
        array $fakerDataProviderClasses,
96
        \ts\Reflection\ReflectionClass $testedEntityReflectionClass,
97
        EntitySaverFactory $entitySaverFactory,
98
        EntityValidatorFactory $entityValidatorFactory,
99
        ?float $seed = null,
100
        ?EntityFactory $entityFactory = null
101
    ) {
102 3
        $this->initFakerGenerator($seed);
103 3
        $this->fakerDataProviderClasses    = $fakerDataProviderClasses;
104 3
        $this->testedEntityReflectionClass = $testedEntityReflectionClass;
105 3
        $this->entitySaverFactory          = $entitySaverFactory;
106 3
        $this->entityValidatorFactory      = $entityValidatorFactory;
107 3
        $this->entityFactory               = $entityFactory ?? new EntityFactory(
108 3
            $entityValidatorFactory,
109 3
            new NamespaceHelper()
110
        );
111 3
    }
112
113
    /**
114
     * @param float|null $seed
115
     * @SuppressWarnings(PHPMD.StaticAccess)
116
     */
117 3
    protected function initFakerGenerator(?float $seed): void
118
    {
119 3
        if (null === self::$generator) {
120
            self::$generator = Faker\Factory::create();
121
            if (null !== $seed) {
122
                self::$generator->seed($seed);
123
            }
124
        }
125 3
    }
126
127
    /**
128
     * Use the factory to generate a new Entity, possibly with values set as well
129
     *
130
     * @param EntityManagerInterface $entityManager
131
     * @param array                  $values
132
     *
133
     * @return EntityInterface
134
     */
135 2
    public function create(EntityManagerInterface $entityManager, array $values = []): EntityInterface
136
    {
137 2
        $this->entityFactory->setEntityManager($entityManager);
138
139 2
        return $this->entityFactory->create($this->testedEntityReflectionClass->getName(), $values);
140
    }
141
142
    /**
143
     * @param EntityManagerInterface $entityManager
144
     * @param EntityInterface        $generated
145
     *
146
     * @throws \Doctrine\ORM\Mapping\MappingException
147
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
148
     * @throws \ErrorException
149
     * @throws \ReflectionException
150
     * @SuppressWarnings(PHPMD.ElseExpression)
151
     */
152 1
    public function addAssociationEntities(
153
        EntityManagerInterface $entityManager,
154
        EntityInterface $generated
155
    ): void {
156 1
        $class    = $this->testedEntityReflectionClass->getName();
157 1
        $meta     = $entityManager->getClassMetadata($class);
158 1
        $mappings = $meta->getAssociationMappings();
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

158
        /** @scrutinizer ignore-call */ 
159
        $mappings = $meta->getAssociationMappings();

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...
159 1
        if (empty($mappings)) {
160
            return;
161
        }
162 1
        $namespaceHelper = new NamespaceHelper();
163 1
        $methods         = array_map('strtolower', get_class_methods($generated));
164 1
        foreach ($mappings as $mapping) {
165 1
            $mappingEntityClass = $mapping['targetEntity'];
166 1
            $mappingEntity      = $this->generateEntity($entityManager, $mappingEntityClass);
167 1
            $errorMessage       = "Error adding association entity $mappingEntityClass to $class: %s";
168 1
            $this->entitySaverFactory->getSaverForEntity($mappingEntity)->save($mappingEntity);
169 1
            $mappingEntityPluralInterface = $namespaceHelper->getHasPluralInterfaceFqnForEntity($mappingEntityClass);
170 1
            if (\interface_exists($mappingEntityPluralInterface)
171 1
                && $this->testedEntityReflectionClass->implementsInterface($mappingEntityPluralInterface)
172
            ) {
173 1
                $this->assertSame(
174 1
                    $mappingEntityClass::getDoctrineStaticMeta()->getPlural(),
175 1
                    $mapping['fieldName'],
176 1
                    sprintf($errorMessage, ' mapping should be plural')
177
                );
178 1
                $getter = 'get' . $mappingEntityClass::getDoctrineStaticMeta()->getPlural();
179 1
                $method = 'add' . $mappingEntityClass::getDoctrineStaticMeta()->getSingular();
180
            } else {
181 1
                $this->assertSame(
182 1
                    $mappingEntityClass::getDoctrineStaticMeta()->getSingular(),
183 1
                    $mapping['fieldName'],
184 1
                    sprintf($errorMessage, ' mapping should be singular')
185
                );
186 1
                $getter = 'get' . $mappingEntityClass::getDoctrineStaticMeta()->getSingular();
187 1
                $method = 'set' . $mappingEntityClass::getDoctrineStaticMeta()->getSingular();
188
            }
189 1
            $this->assertInArray(
190 1
                strtolower($method),
191 1
                $methods,
192 1
                sprintf($errorMessage, $method . ' method is not defined')
193
            );
194 1
            $currentlySet = $generated->$getter();
195 1
            switch (true) {
196
                case $currentlySet === null:
197 1
                case $currentlySet === []:
198 1
                case $currentlySet instanceof PersistentCollection:
199 1
                    $generated->$method($mappingEntity);
200 1
                    break;
201
            }
202
        }
203 1
    }
204
205
    /**
206
     * Generate an Entity. Optionally provide an offset from the first entity
207
     *
208
     * @param EntityManagerInterface $entityManager
209
     * @param string                 $class
210
     *
211
     * @param int                    $offset
212
     *
213
     * @return EntityInterface
214
     * @throws \Doctrine\ORM\Mapping\MappingException
215
     * @throws \ReflectionException
216
     * @SuppressWarnings(PHPMD.StaticAccess)
217
     */
218 3
    public function generateEntity(
219
        EntityManagerInterface $entityManager,
220
        string $class,
221
        int $offset = 0
222
    ): EntityInterface {
223
224 3
        $result = $this->generateEntities($entityManager, $class, 1, $offset);
225
226 3
        return current($result);
227
    }
228
229
    /**
230
     * Generate Entities.
231
     *
232
     * Optionally discard the first generated entities up to the value of offset
233
     *
234
     * @param EntityManagerInterface $entityManager
235
     * @param string                 $entityFqn
236
     * @param int                    $num
237
     *
238
     * @param int                    $offset
239
     *
240
     * @return array|EntityInterface[]
241
     * @throws \Doctrine\ORM\Mapping\MappingException
242
     * @throws \ReflectionException
243
     */
244 4
    public function generateEntities(
245
        EntityManagerInterface $entityManager,
246
        string $entityFqn,
247
        int $num,
248
        int $offset = 0
249
    ): array {
250 4
        $this->entityManager = $entityManager;
251 4
        $columnFormatters    = $this->generateColumnFormatters($entityManager, $entityFqn);
252 4
        $meta                = $entityManager->getClassMetadata($entityFqn);
253 4
        $entities            = [];
254 4
        for ($i = 0; $i < ($num + $offset); $i++) {
255 4
            $entity = new $entityFqn($this->entityValidatorFactory);
256 4
            $this->fillColumns($entity, $columnFormatters, $meta);
257 4
            if ($i < $offset) {
258 1
                continue;
259
            }
260 4
            $entities[] = $entity;
261
        }
262 4
        $this->entitySaverFactory->getSaverForEntityFqn($entityFqn)->saveAll($entities);
263
264 4
        return $entities;
265
    }
266
267
    /**
268
     * @param EntityManagerInterface $entityManager
269
     * @param string                 $entityFqn
270
     *
271
     * @return array
272
     * @throws \Doctrine\ORM\Mapping\MappingException
273
     */
274 3
    protected function generateColumnFormatters(EntityManagerInterface $entityManager, string $entityFqn): array
275
    {
276 3
        $meta              = $entityManager->getClassMetadata($entityFqn);
277 3
        $guessedFormatters = (new Faker\ORM\Doctrine\EntityPopulator($meta))->guessColumnFormatters(self::$generator);
278 3
        $customFormatters  = [];
279 3
        $mappings          = $meta->getAssociationMappings();
280 3
        $this->initialiseColumnFormatters($meta, $mappings, $guessedFormatters);
281 3
        $fieldNames = $meta->getFieldNames();
282
283 3
        foreach ($fieldNames as $fieldName) {
284 3
            if (isset($customFormatters[$fieldName])) {
285
                continue;
286
            }
287 3
            if (true === $this->addFakerDataProviderToColumnFormatters($customFormatters, $fieldName, $entityFqn)) {
288
                continue;
289
            }
290 3
            $fieldMapping = $meta->getFieldMapping($fieldName);
291 3
            if (true === ($fieldMapping['unique'] ?? false)) {
292 3
                $this->addUniqueColumnFormatter($fieldMapping, $customFormatters, $fieldName);
293 3
                continue;
294
            }
295
        }
296
297 3
        return array_merge($guessedFormatters, $customFormatters);
298
    }
299
300
    /**
301
     * Loop through mappings and initialise empty array collections for colection valued mappings, or null if not
302
     *
303
     * @param ClassMetadataInfo $meta
304
     * @param array             $mappings
305
     * @param array             $columnFormatters
306
     */
307 3
    protected function initialiseColumnFormatters(
308
        ClassMetadataInfo $meta,
309
        array &$mappings,
310
        array &$columnFormatters
311
    ): void {
312 3
        foreach ($mappings as $mapping) {
313 3
            if ($meta->isCollectionValuedAssociation($mapping['fieldName'])) {
314 3
                $columnFormatters[$mapping['fieldName']] = new ArrayCollection();
315 3
                continue;
316
            }
317
318 3
            if (isset($mapping['joinColumns']) && count($mapping['joinColumns']) === 1
319 3
                && ($mapping['joinColumns'][0]['nullable'] ?? null) === false
320
            ) {
321
                $columnFormatters[$mapping['fieldName']] = function () use ($mapping) {
322
                    $entity = $this->generateEntity($this->entityManager, $mapping['targetEntity']);
323
                    $this->entitySaverFactory->getSaverForEntity($entity)->save($entity);
324
325
                    return $entity;
326
                };
327
                continue;
328
            }
329 3
            $columnFormatters[$mapping['fieldName']] = null;
330
        }
331 3
    }
332
333
    /**
334
     * Add a faker data provider to the columnFormatters array (by reference) if there is one available
335
     *
336
     * Handles instantiating and caching of the data providers
337
     *
338
     * @param array  $columnFormatters
339
     * @param string $fieldName
340
     *
341
     * @param string $entityFqn
342
     *
343
     * @return bool
344
     */
345 3
    protected function addFakerDataProviderToColumnFormatters(
346
        array &$columnFormatters,
347
        string $fieldName,
348
        string $entityFqn
349
    ): bool {
350
        foreach ([
351 3
                     $entityFqn . '-' . $fieldName,
352 3
                     $fieldName,
353
                 ] as $key) {
354 3
            if (!isset($this->fakerDataProviderClasses[$key])) {
355 3
                continue;
356
            }
357
            if (!isset($this->fakerDataProviderObjects[$key])) {
358
                $class                                = $this->fakerDataProviderClasses[$key];
359
                $this->fakerDataProviderObjects[$key] = new $class(self::$generator);
360
            }
361
            $columnFormatters[$fieldName] = $this->fakerDataProviderObjects[$key];
362
363
            return true;
364
        }
365
366 3
        return false;
367
    }
368
369 3
    protected function addUniqueColumnFormatter(array &$fieldMapping, array &$columnFormatters, string $fieldName): void
370
    {
371 3
        switch ($fieldMapping['type']) {
372
            case MappingHelper::TYPE_UUID:
373 3
                return;
374
            case MappingHelper::TYPE_STRING:
375
                $columnFormatters[$fieldName] = $this->getUniqueString();
376
                break;
377
            case MappingHelper::TYPE_INTEGER:
378
            case Type::BIGINT:
379
                $columnFormatters[$fieldName] = $this->getUniqueInt();
380
                break;
381
            default:
382
                throw new \InvalidArgumentException('unique field has an unsupported type: '
383
                                                    . print_r($fieldMapping, true));
384
        }
385
    }
386
387
    protected function getUniqueString(): string
388
    {
389
        $string = 'unique string: ' . $this->getUniqueInt() . md5((string)time());
390
        while (isset(self::$uniqueStrings[$string])) {
391
            $string                       = md5((string)time());
392
            self::$uniqueStrings[$string] = true;
393
        }
394
395
        return $string;
396
    }
397
398
    protected function getUniqueInt(): int
399
    {
400
        return ++self::$uniqueInt;
401
    }
402
403 3
    protected function fillColumns(EntityInterface $entity, array &$columnFormatters, ClassMetadata $meta): void
404
    {
405 3
        foreach ($columnFormatters as $field => $formatter) {
406 3
            if (null !== $formatter) {
407
                try {
408 3
                    $value = \is_callable($formatter) ? $formatter($entity) : $formatter;
409
                } catch (\InvalidArgumentException $ex) {
410
                    throw new \InvalidArgumentException(
411
                        sprintf(
412
                            'Failed to generate a value for %s::%s: %s',
413
                            \get_class($entity),
414
                            $field,
415
                            $ex->getMessage()
416
                        )
417
                    );
418
                }
419 3
                $meta->reflFields[$field]->setValue($entity, $value);
420
            }
421
        }
422 3
    }
423
424
    /**
425
     * Stub of PHPUnit Assertion method
426
     *
427
     * @param mixed  $expected
428
     * @param mixed  $actual
429
     * @param string $error
430
     *
431
     * @throws \ErrorException
432
     */
433 1
    protected function assertSame($expected, $actual, string $error): void
434
    {
435 1
        if ($expected !== $actual) {
436
            throw new \ErrorException($error);
437
        }
438 1
    }
439
440
    /**
441
     * Stub of PHPUnit Assertion method
442
     *
443
     * @param mixed  $needle
444
     * @param array  $haystack
445
     * @param string $error
446
     *
447
     * @throws \ErrorException
448
     */
449 1
    protected function assertInArray($needle, array $haystack, string $error): void
450
    {
451 1
        if (false === \in_array($needle, $haystack, true)) {
452
            throw new \ErrorException($error);
453
        }
454 1
    }
455
}
456