Completed
Push — master ( f08430...0044c9 )
by Dawid
03:13
created

Storage   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 229
Duplicated Lines 0 %

Test Coverage

Coverage 93.33%

Importance

Changes 0
Metric Value
eloc 91
dl 0
loc 229
ccs 98
cts 105
cp 0.9333
rs 8.96
c 0
b 0
f 0
wmc 43

18 Methods

Rating   Name   Duplication   Size   Complexity  
A commit() 0 5 1
A __construct() 0 3 1
A rollback() 0 5 1
A commitAction() 0 5 3
A get() 0 6 1
A remove() 0 4 2
A persist() 0 4 2
A removeOne() 0 22 6
A getEntityManager() 0 3 1
A getState() 0 22 6
B persistOne() 0 21 7
A hasRepository() 0 3 1
A contains() 0 7 1
A getRepository() 0 3 1
A detach() 0 4 2
A detachOne() 0 18 4
A attach() 0 4 2
A addRepository() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Storage often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Storage, and based on these observations, apply Extract Interface, too.

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 EntityManager
44
     */
45
    private $entityManager;
46
47 44
    public function __construct(EntityManager $manager = null)
48
    {
49 44
        $this->entityManager = $manager ?? new EntityManager();
50 44
    }
51
52 1
    public function getRepository(string $entity): Repository
53
    {
54 1
        return $this->entityManager->getRepository($entity);
55
    }
56
57 1
    public function hasRepository(string $entity): bool
58
    {
59 1
        return $this->entityManager->hasRepository($entity);
60
    }
61
62 1
    public function addRepository(Repository ...$repositories): void
63
    {
64 1
        $this->entityManager->addRepository(...$repositories);
65 1
    }
66
67 1
    public function getEntityManager(): EntityManager
68
    {
69 1
        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 5
    public function persist(Storable ...$entities): void
84
    {
85 5
        foreach ($entities as $entity) {
86 5
            $this->persistOne($entity);
87
        }
88 4
    }
89
90
    /**
91
     * @param Storable[] ...$entities
92
     */
93 3
    public function remove(Storable ...$entities): void
94
    {
95 3
        foreach ($entities as $entity) {
96 3
            $this->removeOne($entity);
97
        }
98 3
    }
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 5
    private function persistOne(Storable $entity): void
124
    {
125 5
        $namespace = get_class($entity);
126
127 5
        switch ($this->getState($entity)) {
128 5
            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 3
            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 1
            case self::STATE_REMOVED:
141
            case self::STATE_DETACHED:
142
            default:
143 1
                throw UnitOfWorkException::forPersistingEntityInInvalidState($entity);
144
        }
145 4
    }
146
147 5
    public function getState(Storable $entity): int
148
    {
149 5
        $oid = spl_object_hash($entity);
150 5
        if (isset($this->states[$oid])) {
151 3
            return $this->states[$oid];
152
        }
153
154 3
        $namespace = get_class($entity);
155
156 3
        if (isset($this->remove[$namespace]) && $this->remove[$namespace]->contains($entity)) {
157 1
            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 3
    private function removeOne(Storable $entity): void
173
    {
174 3
        $namespace = get_class($entity);
175 3
        $oid = spl_object_hash($entity);
176
177 3
        if (isset($this->states[$oid]) && $this->states[$oid] === self::STATE_MANAGED) {
178 2
            $this->states[$oid] = self::STATE_REMOVED;
179
        }
180
181 3
        if (!isset($this->remove[$namespace])) {
182 3
            $this->remove[$namespace] = new SplObjectStorage();
183
        }
184
185 3
        if (isset($this->create[$namespace])) {
186
            $this->create[$namespace]->detach($entity);
187
        }
188
189 3
        if (isset($this->update[$namespace])) {
190 1
            $this->update[$namespace]->detach($entity);
191
        }
192
193 3
        $this->remove[$namespace]->attach($entity);
194 3
    }
195
196 1
    public function attach(Storable ...$entities): void
197
    {
198 1
        foreach ($entities as $entity) {
199 1
            $this->entityManager->attach($entity);
200
        }
201 1
    }
202
203 1
    public function contains(Storable $entity): bool
204
    {
205 1
        $contains = $this->entityManager->contains($entity);
206 1
        $oid = spl_object_hash($entity);
207 1
        $this->states[$oid] = self::STATE_MANAGED;
208
209 1
        return $contains;
210
    }
211
212 1
    public function detach(Storable ...$entities): void
213
    {
214 1
        foreach ($entities as $entity) {
215 1
            $this->detachOne($entity);
216
        }
217 1
    }
218
219 1
    private function detachOne(Storable $entity): void
220
    {
221 1
        $this->states[spl_object_hash($entity)] = self::STATE_DETACHED;
222 1
        $namespace = get_class($entity);
223
224 1
        if (isset($this->update[$namespace])) {
225
            $this->update[$namespace]->detach($entity);
226
        }
227
228 1
        if (isset($this->create[$namespace])) {
229
            $this->create[$namespace]->detach($entity);
230
        }
231
232 1
        if (isset($this->remove[$namespace])) {
233
            $this->remove[$namespace]->detach($entity);
234
        }
235
236 1
        $this->entityManager->detach($entity);
237 1
    }
238
}
239