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 (#121)
by joseph
56:14
created

addFakerDataProviderToColumnFormatters()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 5.5021

Importance

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

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