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