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 (#96)
by Ross
15:04 queued 12:23
created

TestEntityGenerator::generateEntity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 9
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 3
crap 1
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\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\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
    /**
78
     * TestEntityGenerator constructor.
79
     *
80
     * @param float|null                     $seed
81
     * @param array|string[]                 $fakerDataProviderClasses
82
     * @param \ts\Reflection\ReflectionClass $testedEntityReflectionClass
83
     * @param EntitySaverFactory             $entitySaverFactory
84
     * @param EntityValidatorFactory         $entityValidatorFactory
85
     * @SuppressWarnings(PHPMD.StaticAccess)
86 18
     */
87
    public function __construct(
88
        ?float $seed,
89
        array $fakerDataProviderClasses,
90
        \ts\Reflection\ReflectionClass $testedEntityReflectionClass,
91
        EntitySaverFactory $entitySaverFactory,
92
        EntityValidatorFactory $entityValidatorFactory
93 18
    ) {
94 18
        $this->initFakerGenerator($seed);
95 18
        $this->fakerDataProviderClasses    = $fakerDataProviderClasses;
96 18
        $this->testedEntityReflectionClass = $testedEntityReflectionClass;
97 18
        $this->entitySaverFactory          = $entitySaverFactory;
98 18
        $this->entityValidatorFactory      = $entityValidatorFactory;
99
    }
100
101
    /**
102
     * @param float|null $seed
103
     * @SuppressWarnings(PHPMD.StaticAccess)
104 18
     */
105
    protected function initFakerGenerator(?float $seed): void
106 18
    {
107 1
        if (null === self::$generator) {
108 1
            self::$generator = Faker\Factory::create();
109 1
            if (null !== $seed) {
110
                self::$generator->seed($seed);
111
            }
112 18
        }
113
    }
114
115
    /**
116
     * @param EntityManagerInterface   $entityManager
117
     * @param EntityInterface $generated
118
     *
119
     * @throws \Doctrine\ORM\Mapping\MappingException
120
     * @throws \EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException
121
     * @throws \ErrorException
122
     * @throws \ReflectionException
123
     * @SuppressWarnings(PHPMD.ElseExpression)
124 1
     */
125
    public function addAssociationEntities(
126
        EntityManagerInterface $entityManager,
127
        EntityInterface $generated
128 1
    ): void {
129 1
        $class    = $this->testedEntityReflectionClass->getName();
130 1
        $meta     = $entityManager->getClassMetadata($class);
131 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

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