Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
69.44% covered (warning)
69.44%
25 / 36
66.67% covered (warning)
66.67%
4 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApieResourceSeeder
69.44% covered (warning)
69.44%
25 / 36
66.67% covered (warning)
66.67%
4 / 6
23.30
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 supports
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 getResourceClass
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getBoundedContextId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getResource
70.00% covered (warning)
70.00%
14 / 20
0.00% covered (danger)
0.00%
0 / 1
9.73
 fakeFor
44.44% covered (danger)
44.44%
4 / 9
0.00% covered (danger)
0.00%
0 / 1
2.69
1<?php
2namespace Apie\Faker\Seeders;
3
4use Apie\Core\Actions\BoundedContextEntityTuple;
5use Apie\Core\BoundedContext\BoundedContextId;
6use Apie\Core\Entities\EntityInterface;
7use Apie\Core\Exceptions\InvalidTypeException;
8use Apie\Core\Identifiers\IdentifierInterface;
9use Apie\Faker\Interfaces\ApieClassFaker;
10use Faker\Generator;
11use LogicException;
12use ReflectionClass;
13
14/**
15 * @implements ApieClassFaker<IdentifierInterface>
16 */
17final class ApieResourceSeeder implements ApieClassFaker
18{
19    /**
20     * @var array<int, EntityInterface|null> $createdResources
21     */
22    private array $createdResources = [];
23
24    /**
25     * @var array<string, bool> $idsCreated;
26     */
27    private array $idsCreated = [];
28
29    private bool $building = false;
30
31    public function __construct(
32        private readonly BoundedContextEntityTuple $contextAndClass,
33        private readonly int $amount
34    ) {
35        $this->createdResources = array_fill(0, $amount, null);
36    }
37
38    /**
39     * @param ReflectionClass<object> $class
40     */
41    public function supports(ReflectionClass $class): bool
42    {
43        if ($this->building || !$class->implementsInterface(IdentifierInterface::class)) {
44            return false;
45        }
46        $intendedClass = $class->getMethod('getReferenceFor')->invoke(null);
47        return $intendedClass->name === $this->contextAndClass->resourceClass->name;
48    }
49
50    /**
51     * @return ReflectionClass<EntityInterface>
52     */
53    public function getResourceClass(): ReflectionClass
54    {
55        return $this->contextAndClass->resourceClass;
56    }
57
58    public function getBoundedContextId(): BoundedContextId
59    {
60        return $this->contextAndClass->boundedContext->getId();
61    }
62
63    public function getResource(Generator $generator, int $index): ?EntityInterface
64    {
65        if ($index < 0 || $index >= $this->amount) {
66            return null;
67        }
68        if (!isset($this->createdResources[$index])) {
69            $retries = 0;
70            $this->building = true;
71            try {
72                do {
73                    $fakedResource = $generator->fakeClass($this->contextAndClass->resourceClass->name);
74                    $id = $fakedResource->getId()->toNative();
75                    $retries++;
76                } while ($id !== null && isset($this->idsCreated[$id]) && $retries < 1000);
77                if ($id !== null && isset($this->idsCreated[$id])) {
78                    throw new LogicException(
79                        sprintf(
80                            'I tried to create a unique resource, but it failed for 1000 times on class "%s"!',
81                            $this->contextAndClass->resourceClass->name
82                        )
83                    );
84                }
85                $this->idsCreated[$id] = true;
86                $this->createdResources[$index] = $fakedResource;
87            } finally {
88                $this->building = false;
89            }
90        }
91
92        return $this->createdResources[$index];
93    }
94
95    /**
96     * @template T of IdentifierInterface<EntityInterface>
97     * @param ReflectionClass<T> $class
98     * @return T
99     */
100    public function fakeFor(Generator $generator, ReflectionClass $class): IdentifierInterface
101    {
102        $randomNumber = $generator->numberBetween(0, $this->amount - 1);
103        $resource = $this->getResource($generator, $randomNumber);
104        if (!$resource instanceof EntityInterface) {
105            throw new InvalidTypeException(
106                $resource,
107                'EntityInterface',
108                new \Exception('Random number: ' . $randomNumber)
109            );
110        }
111        return $resource->getId();
112    }
113}