Passed
Push — master ( a290cb...57bf28 )
by Dawid
02:40
created

Storage::get()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Igni\Storage;
4
5
use Igni\Storage\Exception\UnitOfWorkException;
6
use SplObjectStorage;
7
8
class Storage implements UnitOfWork, RepositoryContainer
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 Manager
44
     */
45
    private $entityManager;
46
47 29
    public function __construct(Manager $manager = null)
48
    {
49 29
        $this->entityManager = $manager ?? new Manager();
50 29
    }
51
52
    public function getRepository(string $entity): Repository
53
    {
54
        return $this->entityManager->getRepository($entity);
55
    }
56
57
    public function hasRepository(string $entity): bool
58
    {
59
        return $this->entityManager->hasRepository($entity);
60
    }
61
62
    public function addRepository(Repository ...$repositories): void
63
    {
64
        $this->entityManager->addRepository(...$repositories);
65
    }
66
67
    public function getEntityManager(): Manager
68
    {
69
        return $this->entityManager;
70
    }
71
72 4
    public function get(string $entity, $id): Storable
73
    {
74 4
        $entity = $this->entityManager->get($entity, $id);
75 4
        $this->states[spl_object_hash($entity)] = self::STATE_MANAGED;
76
77 4
        return $entity;
78
    }
79
80
    /**
81
     * @param Storable[] ...$entities
82
     */
83 4
    public function persist(Storable ...$entities): void
84
    {
85 4
        foreach ($entities as $entity) {
86 4
            $this->persistOne($entity);
87
        }
88 4
    }
89
90
    /**
91
     * @param Storable[] ...$entities
92
     */
93 2
    public function remove(Storable ...$entities): void
94
    {
95 2
        foreach ($entities as $entity) {
96 2
            $this->removeOne($entity);
97
        }
98 2
    }
99
100 3
    public function commit(): void
101
    {
102 3
        $this->commitAction(self::ACTION_CREATE);
103 3
        $this->commitAction(self::ACTION_UPDATE);
104 3
        $this->commitAction(self::ACTION_REMOVE);
105 3
    }
106
107 3
    private function commitAction(string $action): void
108
    {
109 3
        foreach ($this->{$action} as $namespace => $entities) {
110 3
            foreach ($entities as $entity) {
111 3
                call_user_func([$this->entityManager->getRepository($namespace), $action], $entity);
112
            }
113
        }
114 3
    }
115
116 1
    public function rollback(): void
117
    {
118 1
        $this->create = [];
119 1
        $this->update = [];
120 1
        $this->remove = [];
121 1
    }
122
123 4
    private function persistOne(Storable $entity): void
124
    {
125 4
        $namespace = get_class($entity);
126
127 4
        switch ($this->getState($entity)) {
128 4
            case self::STATE_MANAGED:
129 2
                if (!isset($this->update[$namespace])) {
130 2
                    $this->update[$namespace] = new SplObjectStorage();
131
                }
132 2
                $this->update[$namespace]->attach($entity);
133 2
                break;
134 2
            case self::STATE_NEW:
135 2
                if (!isset($this->create[$namespace])) {
136 2
                    $this->create[$namespace] = new SplObjectStorage();
137
                }
138 2
                $this->create[$namespace]->attach($entity);
139 2
                break;
140
            case self::STATE_REMOVED:
141
            case self::STATE_DETACHED:
142
            default:
143
                throw UnitOfWorkException::forPersistingEntityInInvalidState($entity);
144
        }
145 4
    }
146
147 4
    public function getState(Storable $entity): int
148
    {
149 4
        $oid = spl_object_hash($entity);
150 4
        if (isset($this->states[$oid])) {
151 3
            return $this->states[$oid];
152
        }
153
154 2
        $namespace = get_class($entity);
155
156 2
        if (isset($this->remove[$namespace]) && $this->remove[$namespace]->contains($entity)) {
157
            return $this->states[$oid] = self::STATE_REMOVED;
158
        }
159
160 2
        if ($this->entityManager->contains($entity)) {
161
            return $this->states[$oid] = self::STATE_MANAGED;
162
        }
163
164
        try {
165 2
            $this->entityManager->getRepository($namespace)->get($entity->getId());
166
            return $this->states[$oid] = self::STATE_DETACHED;
167 2
        } catch (\Exception $exception) {
168 2
            return $this->states[$oid] = self::STATE_NEW;
169
        }
170
    }
171
172 2
    private function removeOne(Storable $entity): void
173
    {
174 2
        $namespace = get_class($entity);
175 2
        $oid = spl_object_hash($entity);
176
177 2
        if (isset($this->states[$oid]) && $this->states[$oid] === self::STATE_MANAGED) {
178 2
            $this->states[$oid] = self::STATE_REMOVED;
179
        }
180
181 2
        if (!isset($this->remove[$namespace])) {
182 2
            $this->remove[$namespace] = new SplObjectStorage();
183
        }
184
185 2
        if (isset($this->create[$namespace])) {
186
            $this->create[$namespace]->detach($entity);
187
        }
188
189 2
        if (isset($this->update[$namespace])) {
190 1
            $this->update[$namespace]->detach($entity);
191
        }
192
193 2
        $this->remove[$namespace]->attach($entity);
194 2
    }
195
196
    public function attach(Storable ...$entities): void
197
    {
198
        foreach ($entities as $entity) {
199
            $this->entityManager->attach($entity);
200
        }
201
    }
202
203
    public function contains(Storable $entity): bool
204
    {
205
        $contains = $this->entityManager->contains($entity);
206
        $oid = spl_object_hash($entity);
207
        $this->states[$oid] = self::STATE_MANAGED;
208
209
        return $contains;
210
    }
211
212
    public function detach(Storable ...$entities): void
213
    {
214
        foreach ($entities as $entity) {
215
            $this->detachOne($entity);
216
        }
217
    }
218
219
    private function detachOne(Storable $entity): void
220
    {
221
        $this->states[spl_object_hash($entity)] = self::STATE_DETACHED;
222
        $namespace = get_class($entity);
223
224
        if (isset($this->update[$namespace])) {
225
            $this->update[$namespace]->detach($entity);
226
        }
227
228
        if (isset($this->create[$namespace])) {
229
            $this->create[$namespace]->detach($entity);
230
        }
231
232
        if (isset($this->remove[$namespace])) {
233
            $this->remove[$namespace]->detach($entity);
234
        }
235
236
        $this->entityManager->detach($entity);
237
    }
238
}
239