Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.53% covered (success)
91.53%
54 / 59
66.67% covered (warning)
66.67%
6 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
InMemoryDatalayer
91.53% covered (success)
91.53%
54 / 59
66.67% covered (warning)
66.67%
6 / 9
23.32
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 restore
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 store
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 all
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 persistNew
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
7
 persistExisting
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 find
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 removeExisting
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
3.02
 upsert
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2namespace Apie\Core\Datalayers\InMemory;
3
4use Apie\Core\Attributes\ClassStoreOptions;
5use Apie\Core\BoundedContext\BoundedContextId;
6use Apie\Core\Datalayers\ApieDatalayer;
7use Apie\Core\Datalayers\Lists\EntityListInterface;
8use Apie\Core\Datalayers\Lists\InMemoryEntityList;
9use Apie\Core\Datalayers\Search\LazyLoadedListFilterer;
10use Apie\Core\Entities\EntityInterface;
11use Apie\Core\Enums\SortingOrder;
12use Apie\Core\Exceptions\EntityAlreadyPersisted;
13use Apie\Core\Exceptions\EntityNotFoundException;
14use Apie\Core\Exceptions\UnknownExistingEntityError;
15use Apie\Core\Identifiers\AutoIncrementInteger;
16use Apie\Core\Identifiers\IdentifierInterface;
17use Faker\Factory;
18use Faker\Generator;
19use ReflectionClass;
20use ReflectionProperty;
21
22class InMemoryDatalayer implements ApieDatalayer
23{
24    /**
25     * @var array<string, array<int, EntityInterface>>
26     */
27    private array $stored = [];
28
29    /**
30     * @var array<class-string<EntityInterface>, EntityListInterface<EntityInterface>>
31     */
32    private array $alreadyLoadedLists = [];
33
34    private Generator $generator;
35
36    public function __construct(private BoundedContextId $boundedContextId, private LazyLoadedListFilterer $filterer)
37    {
38        $this->generator = Factory::create();
39        $this->stored = $this->restore();
40    }
41
42    /**
43     * @return array<string, array<int, EntityInterface>>
44     */
45    protected function restore(): array
46    {
47        return [];
48    }
49
50    /**
51     * @param array<string, array<int, EntityInterface>> $list
52     */
53    protected function store(array $list): void
54    {
55    }
56
57    public function all(ReflectionClass $class, ?BoundedContextId $boundedContextId = null): EntityListInterface
58    {
59        $className = $class->name;
60        $this->stored[$className] ??= [];
61        if (!isset($this->alreadyLoadedLists[$className])) {
62            $this->alreadyLoadedLists[$className] = new InMemoryEntityList(
63                $class,
64                $this->boundedContextId,
65                $this->filterer,
66                $this->stored[$className]
67            );
68        }
69        return $this->alreadyLoadedLists[$className];
70    }
71
72    /**
73     * @template T of EntityInterface
74     * @param T $entity
75     * @return T
76     */
77    public function persistNew(EntityInterface $entity, ?BoundedContextId $boundedContextId = null): EntityInterface
78    {
79        $id = $entity->getId();
80        if ($id instanceof AutoIncrementInteger) {
81            $id = $id::createRandom($this->generator);
82            $reflProperty = new ReflectionProperty($entity, 'id');
83            $reflProperty->setValue($entity, $id);
84        }
85        $refl = $id::getReferenceFor();
86        $className = $refl->name;
87        $id = $entity->getId()->toNative();
88        $this->stored[$className] ??= [];
89        foreach ($this->stored[$className] as $entityInList) {
90            if ($entityInList->getId()->toNative() === $id) {
91                throw new EntityAlreadyPersisted($entity);
92            }
93        }
94        $sortOrder = SortingOrder::Descending;
95        foreach ($refl->getAttributes(ClassStoreOptions::class) as $classStoreOptionsAttribute) {
96            $classStoreOptions = $classStoreOptionsAttribute->newInstance();
97            if ($classStoreOptions->defaultSortingOrder === SortingOrder::Ascending) {
98                $sortOrder = SortingOrder::Ascending;
99            }
100        }
101        if ($sortOrder === SortingOrder::Ascending) {
102            $this->stored[$className][] = $entity;
103        } else {
104            array_unshift($this->stored[$className], $entity);
105        }
106        return $entity;
107    }
108
109    /**
110     * @template T of EntityInterface
111     * @param T $entity
112     * @return T
113     */
114    public function persistExisting(EntityInterface $entity, ?BoundedContextId $boundedContextId = null): EntityInterface
115    {
116        $id = $entity->getId()->toNative();
117        $className = $entity->getId()::getReferenceFor()->name;
118        foreach ($this->stored[$className] ?? [] as $key => $entityInList) {
119            if ($entityInList->getId()->toNative() === $id) {
120                $this->stored[$className][$key] = $entity;
121                return $entity;
122            }
123        }
124        throw new UnknownExistingEntityError($entity);
125    }
126
127    public function find(IdentifierInterface $identifier, ?BoundedContextId $boundedContextId = null): EntityInterface
128    {
129        $className = $identifier::getReferenceFor()->name;
130        $id = $identifier->toNative();
131        foreach ($this->stored[$className] ?? [] as $entityInList) {
132            if ($entityInList->getId()->toNative() === $id) {
133                return $entityInList;
134            }
135        }
136        throw new EntityNotFoundException($identifier);
137    }
138
139    public function removeExisting(EntityInterface $entity, ?BoundedContextId $boundedContextId = null): void
140    {
141        $identifier = $entity->getId();
142        $className = $identifier::getReferenceFor()->name;
143        $id = $identifier->toNative();
144        $newList = [];
145        foreach ($this->stored[$className] ?? [] as $entityInList) {
146            if ($entityInList->getId()->toNative() !== $id) {
147                $newList[] = $entityInList;
148            }
149        }
150        $this->stored[$className] = $newList;
151    }
152
153    public function upsert(EntityInterface $entity, ?BoundedContextId $boundedContextId): EntityInterface
154    {
155        try {
156            return $this->persistExisting($entity, $boundedContextId);
157        } catch (UnknownExistingEntityError) {
158            return $this->persistNew($entity, $boundedContextId);
159        }
160    }
161}