Passed
Push — master ( b583b2...f3eb03 )
by Dawid
06:31 queued 12s
created

EntityStorage::hasRepository()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
ccs 0
cts 2
cp 0
cc 1
nc 1
nop 1
crap 2
1
<?php declare(strict_types=1);
2
3
namespace Igni\Storage;
4
5
use Igni\Storage\Exception\UnitOfWorkException;
6
use SplObjectStorage;
7
8
class EntityStorage implements UnitOfWork
9
{
10
    private const STATE_NEW = 1;
11
    private const STATE_MANAGED = 2;
12
    private const STATE_REMOVED = 3;
13
    private const STATE_DETACHED = 4;
14
    private const ACTION_CREATE = 'create';
15
    private const ACTION_REMOVE = 'remove';
16
    private const ACTION_UPDATE = 'update';
17
18
    /**
19
     * Contains calculated states for entities.
20
     * @var array<string, int>
21
     */
22
    private $states = [];
23
24
    /**
25
     * Contains grouped by class name entities that should be saved.
26
     * @var SplObjectStorage[]
27
     */
28
    private $create = [];
29
30
    /**
31
     * Contains grouped by class name entities that should be removed.
32
     * @var SplObjectStorage[]
33
     */
34
    private $remove = [];
35
36
    /**
37
     * Contains grouped by class name entities that should be removed.
38
     * @var SplObjectStorage[]
39
     */
40
    private $update = [];
41
42
    /**
43
     * @var EntityManager
44
     */
45
    private $entityManager;
46
47 29
    public function __construct(EntityManager $manager = null)
48
    {
49 29
        $this->entityManager = $manager ?? new EntityManager();
50 29
    }
51
52
    public function getEntityManager(): EntityManager
53
    {
54
        return $this->entityManager;
55
    }
56
57
    public function get(string $entity, $id): Entity
58
    {
59
        $entity = $this->entityManager->get($entity, $id);
60
        $this->states[spl_object_hash($entity)] = self::STATE_MANAGED;
61
62
        return $entity;
63
    }
64
65
    /**
66
     * @param Entity[] ...$entities
67 4
     */
68
    public function persist(Entity ...$entities): void
69 4
    {
70 4
        foreach ($entities as $entity) {
71
            $this->persistOne($entity);
72 4
        }
73
    }
74
75
    /**
76
     * @param Entity[] ...$entities
77
     */
78 4
    public function remove(Entity ...$entities): void
79
    {
80 4
        foreach ($entities as $entity) {
81 4
            $this->removeOne($entity);
82
        }
83 4
    }
84
85
    public function commit(): void
86
    {
87
        $this->commitAction(self::ACTION_CREATE);
88 2
        $this->commitAction(self::ACTION_UPDATE);
89
        $this->commitAction(self::ACTION_REMOVE);
90 2
    }
91 2
92
    private function commitAction(string $action): void
93 2
    {
94
        foreach ($this->{$action} as $namespace => $entities) {
95 3
            foreach ($entities as $entity) {
96
                call_user_func([$this->entityManager->getRepository($namespace), $action], $entity);
97 3
            }
98 3
        }
99 3
    }
100 3
101
    public function rollback(): void
102 3
    {
103
        $this->create = [];
104 3
        $this->update = [];
105 3
        $this->remove = [];
106 3
    }
107
108
    private function persistOne(Entity $entity): void
109 3
    {
110
        $namespace = get_class($entity);
111 1
112
        switch ($this->getState($entity)) {
113 1
            case self::STATE_MANAGED:
114 1
                if (!isset($this->update[$namespace])) {
115 1
                    $this->update[$namespace] = new SplObjectStorage();
116 1
                }
117
                $this->update[$namespace]->attach($entity);
118 4
                break;
119
            case self::STATE_NEW:
120 4
                if (!isset($this->create[$namespace])) {
121
                    $this->create[$namespace] = new SplObjectStorage();
122 4
                }
123 4
                $this->create[$namespace]->attach($entity);
124 2
                break;
125 2
            case self::STATE_REMOVED:
126
            case self::STATE_DETACHED:
127 2
            default:
128 2
                throw UnitOfWorkException::forPersistingEntityInInvalidState($entity);
129 2
        }
130 2
    }
131 2
132
    public function getState(Entity $entity): int
133 2
    {
134 2
        $oid = spl_object_hash($entity);
135
        if (isset($this->states[$oid])) {
136
            return $this->states[$oid];
137
        }
138
139
        $namespace = get_class($entity);
140 4
141
        if (isset($this->remove[$namespace]) && $this->remove[$namespace]->contains($entity)) {
142 4
            return $this->states[$oid] = self::STATE_REMOVED;
143
        }
144 4
145 4
        if ($this->entityManager->contains($entity)) {
146 3
            return $this->states[$oid] = self::STATE_MANAGED;
147
        }
148
149 2
        try {
150
            $this->entityManager->getRepository($namespace)->get($entity->getId());
151 2
            return $this->states[$oid] = self::STATE_DETACHED;
152
        } catch (\Exception $exception) {
153
            return $this->states[$oid] = self::STATE_NEW;
154
        }
155 2
    }
156
157
    private function removeOne(Entity $entity): void
158
    {
159
        $namespace = get_class($entity);
160 2
        $oid = spl_object_hash($entity);
161
162 2
        if (isset($this->states[$oid]) && $this->states[$oid] === self::STATE_MANAGED) {
163 2
            $this->states[$oid] = self::STATE_REMOVED;
164
        }
165
166
        if (!isset($this->remove[$namespace])) {
167 2
            $this->remove[$namespace] = new SplObjectStorage();
168
        }
169 2
170 2
        if (isset($this->create[$namespace])) {
171
            $this->create[$namespace]->detach($entity);
172 2
        }
173 2
174
        if (isset($this->update[$namespace])) {
175
            $this->update[$namespace]->detach($entity);
176 2
        }
177 2
178
        $this->remove[$namespace]->attach($entity);
179
    }
180 2
181
    public function attach(Entity ...$entities): void
182
    {
183
        foreach ($entities as $entity) {
184 2
            $this->entityManager->attach($entity);
185 1
        }
186
    }
187
188 2
    public function contains(Entity $entity): bool
189 2
    {
190
        $contains = $this->entityManager->contains($entity);
191
        $oid = spl_object_hash($entity);
192
        $this->states[$oid] = self::STATE_MANAGED;
193
194
        return $contains;
195
    }
196
197
    public function detach(Entity ...$entities): void
198
    {
199
        foreach ($entities as $entity) {
200
            $this->detachOne($entity);
201
        }
202
    }
203
204
    private function detachOne(Entity $entity): void
205
    {
206
        $this->states[spl_object_hash($entity)] = self::STATE_DETACHED;
207
        $namespace = get_class($entity);
208
209
        if (isset($this->update[$namespace])) {
210
            $this->update[$namespace]->detach($entity);
211
        }
212
213
        if (isset($this->create[$namespace])) {
214
            $this->create[$namespace]->detach($entity);
215
        }
216
217
        if (isset($this->remove[$namespace])) {
218
            $this->remove[$namespace]->detach($entity);
219
        }
220
221
        $this->entityManager->detach($entity);
222
    }
223
}
224