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 ( b58c08...fe6969 )
by joseph
14:23
created

TestEntityGenerator::fillColumns()   A

Complexity

Conditions 5
Paths 6

Size

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