Completed
Push — master ( f6512a...56b616 )
by Dmitry
02:43
created

Repository   D

Complexity

Total Complexity 92

Size/Duplication

Total Lines 420
Duplicated Lines 7.62 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 98.48%

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 92
c 1
b 0
f 1
lcom 1
cbo 5
dl 32
loc 420
ccs 194
cts 197
cp 0.9848
rs 4.8717

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
C create() 9 68 17
A findOne() 0 4 1
A findOrCreate() 0 8 2
C find() 0 56 14
A forget() 0 6 2
C getInstance() 12 35 8
A getMapper() 0 4 1
A knows() 0 4 1
C update() 5 27 7
A truncate() 0 6 1
A remove() 0 14 4
B removeEntity() 3 34 6
C save() 3 61 11
A addDefaultValues() 0 14 4
A getOriginal() 0 4 1
B getTuple() 0 24 4
B sync() 0 14 6
A flushCache() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Repository 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Repository, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Tarantool\Mapper;
4
5
use Exception;
6
use SplObjectStorage;
7
8
class Repository
9
{
10
    private $space;
11
    private $persisted = [];
12
    private $original = [];
13
    private $keys;
14
15
    private $results = [];
16
17
    public function __construct(Space $space)
18
    {
19
        $this->space = $space;
20
        $this->keys = new SplObjectStorage;
21
    }
22
23 64
    public function create($data)
24
    {
25 64
        $data = (array) $data;
26 64
        $class = Entity::class;
27 View Code Duplication
        foreach ($this->getMapper()->getPlugins() as $plugin) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
28 64
            $entityClass = $plugin->getEntityClass($this->space);
29
            if ($entityClass) {
30 64
                if ($class != Entity::class) {
31 64
                    throw new Exception('Entity class override');
32
                }
33
                $class = $entityClass;
34 64
            }
35 2
        }
36
37
        if (array_key_exists(0, $data)) {
38 64
            $byType = [];
39 64
            foreach ($this->space->getFormat() as $row) {
40 64
                if (!array_key_exists($row['type'], $byType)) {
41 64
                    $byType[$row['type']] = [$row['name']];
42 4
                } else {
43 4
                    $byType[$row['type']][] = $row['name'];
44
                }
45 64
            }
46 64
            $mapping = [
47 64
                'is_numeric' => 'unsigned',
48 64
                'is_string' => 'str',
49
                'is_array' => '*',
50
            ];
51 64
            foreach ($data as $k => $v) {
52 64
                foreach ($mapping as $function => $type) {
53
                    if (call_user_func($function, $v)) {
54 1
                        if (count($byType[$type]) == 1) {
55
                            $data[$byType[$type][0]] = $v;
56
                            unset($data[$k]);
57
                        }
58 64
                    }
59 2
                }
60 2
            }
61
        }
62 64
63
        $instance = new $class($this);
64 64
65 64
        foreach ($this->space->getFormat() as $row) {
66 64
            if (array_key_exists($row['name'], $data)) {
67 64
                $instance->{$row['name']} = $data[$row['name']];
68 64
                if ($data[$row['name']] instanceof Entity) {
69
                    $instance->{$row['name']} = $instance->{$row['name']}->id;
70
                }
71
            }
72 64
        }
73
74 64
        foreach ($this->getMapper()->getPlugins() as $plugin) {
75
            $plugin->generateKey($instance, $this->space);
76
            $plugin->afterInstantiate($instance, $this->space);
77 64
        }
78
79 64
        // validate instance key
80
        $key = $this->space->getInstanceKey($instance);
81 64
82
        foreach ($this->keys as $_) {
83
            if ($this->keys[$_] == $key) {
84 64
                throw new Exception($this->space->getName().' '.json_encode($key).' exists');
85
            }
86 64
        }
87 64
88 64
        $this->keys[$instance] = $key;
89 64
        return $instance;
90
    }
91 64
92
    public function findOne($params = [])
93
    {
94
        return $this->find($params, true);
95 1
    }
96
97
    public function findOrCreate($params = [])
98 19
    {
99
        $entity = $this->findOne($params);
100 19
        if (!$entity) {
101
            $entity = $this->create($params);
102
        }
103 64
        return $entity;
104
    }
105 64
106
    public function find($params = [], $one = false)
107 64
    {
108 1
        $cacheKey = json_encode(func_get_args());
109
110
        if (array_key_exists($cacheKey, $this->results)) {
111 64
            return $this->results[$cacheKey];
112 10
        }
113 3
114
        if (!is_array($params)) {
115
            $params = [$params];
116 7
        }
117
        if (count($params) == 1 && array_key_exists(0, $params)) {
118 7
            $primary = $this->space->getPrimaryIndex();
119
            if (count($primary->parts) == 1) {
120
                $formatted = $this->getMapper()->getSchema()->formatValue($primary->parts[0][1], $params[0]);
121 64
                if ($params[0] == $formatted) {
122 2
                    $params = [
123
                        $this->space->getFormat()[$primary->parts[0][0]]['name'] => $params[0]
124
                    ];
125 64
                }
126 64
            }
127
        }
128 64
129 64
        if (array_key_exists('id', $params)) {
130 18
            if (array_key_exists($params['id'], $this->persisted)) {
131 64
                $instance = $this->persisted[$params['id']];
132
                return $one ? $instance : [$instance];
133
            }
134
        }
135 64
136 1
137
        $index = $this->space->castIndex($params);
138
        if (is_null($index)) {
139 64
            throw new Exception("No index for params ".json_encode($params));
140 64
        }
141 2
142 2
        $client = $this->getMapper()->getClient();
143
        $values = $this->space->getIndexValues($index, $params);
144 64
145 64
        $data = $client->getSpace($this->space->getId())->select($values, $index)->getData();
146
147 1
        $result = [];
148 64
        foreach ($data as $tuple) {
0 ignored issues
show
Bug introduced by
The expression $data of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
149
            $instance = $this->getInstance($tuple);
150
            if ($one) {
151
                return $this->results[$cacheKey] = $instance;
152
            }
153 64
            $result[] = $instance;
154 64
        }
155 64
156
        if ($one) {
157
            return $this->results[$cacheKey] = null;
158 64
        }
159 64
160 2
        return $this->results[$cacheKey] = $result;
161
    }
162
163 64
    public function forget($id)
164
    {
165 64
        if (array_key_exists($id, $this->persisted)) {
166 1
            unset($this->persisted[$id]);
167 1
        }
168 1
    }
169 1
170 1
    private function getInstance($tuple)
171
    {
172
        $key = $this->space->getTupleKey($tuple);
173 1
174
        if (array_key_exists($key, $this->persisted)) {
175
            return $this->persisted[$key];
176 1
        }
177
178 1
        $class = Entity::class;
179 1 View Code Duplication
        foreach ($this->getMapper()->getPlugins() as $plugin) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
180
            $entityClass = $plugin->getEntityClass($this->space);
181 1
            if ($entityClass) {
182
                if ($class != Entity::class) {
183
                    throw new Exception('Entity class override');
184 64
                }
185
                $class = $entityClass;
186 64
            }
187 64
        }
188 64
189 64
        $instance = new $class($this);
190 64
191 64
        $this->original[$key] = $tuple;
192 64
193 View Code Duplication
        foreach ($this->space->getFormat() as $index => $info) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
194 64
            $instance->{$info['name']} = array_key_exists($index, $tuple) ? $tuple[$index] : null;
195
        }
196 64
197 64
        $this->keys->offsetSet($instance, $key);
198
199 14
        foreach ($this->getMapper()->getPlugins() as $plugin) {
200
            $plugin->afterInstantiate($instance);
201
        }
202 64
203 64
        return $this->persisted[$key] = $instance;
204
    }
205 17
206
    public function getMapper()
207
    {
208
        return $this->space->getMapper();
209
    }
210
211
    public function knows($instance)
212 64
    {
213
        return $this->keys->offsetExists($instance);
214 64
    }
215
216
    public function update(Entity $instance, $operations)
217 6
    {
218
        if (!count($operations)) {
219 6
            return;
220 6
        }
221 6
222
        $tupleOperations = [];
223 6
        foreach ($operations as $operation) {
224 6
            $tupleIndex = $this->space->getPropertyIndex($operation[1]);
225
            $tupleOperations[] = [$operation[0], $tupleIndex, $operation[2]];
226 1
        }
227
228 1
        $pk = [];
229 1
        foreach ($this->space->getPrimaryIndex()->parts as $part) {
230 1
            $pk[] = $instance->{$this->space->getFormat()[$part[0]]['name']};
231
        }
232 1
233 1
        $client = $this->getMapper()->getClient();
234
        $result = $client->getSpace($this->space->getId())->update($pk, $tupleOperations);
235 64
        foreach ($result->getData() as $tuple) {
236 View Code Duplication
            foreach ($this->space->getFormat() as $index => $info) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
237 64
                if (array_key_exists($index, $tuple)) {
238 64
                    $instance->{$info['name']} = $tuple[$index];
239
                }
240 64
            }
241
        }
242 64
    }
243 1
244
    public function truncate()
245
    {
246 64
        $this->results = [];
247 64
        $id = $this->space->getId();
248 64
        $this->getMapper()->getClient()->evaluate("box.space[$id]:truncate()");
249 64
    }
250
251 19
    public function remove($params = [])
252 19
    {
253 19
        if ($params instanceof Entity) {
254 19
            return $this->removeEntity($params);
255
        }
256
257 19
        if (!count($params)) {
258 19
            throw new Exception("Use truncate to flush space");
259 4
        }
260 19
261 19
        foreach ($this->find($params) as $entity) {
262
            $this->removeEntity($entity);
263
        }
264
    }
265
266 19
    public function removeEntity(Entity $instance)
267 19
    {
268 1
        $key = $this->space->getInstanceKey($instance);
269 19
270
        if (!array_key_exists($key, $this->original)) {
271
            return;
272
        }
273 18
274 18
        if (array_key_exists($key, $this->persisted)) {
275 18
            unset($this->persisted[$key]);
276 18
277
            $pk = [];
278 View Code Duplication
            foreach ($this->space->getPrimaryIndex()->parts as $part) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
279 18
                $pk[] = $this->original[$key][$part[0]];
280 17
            }
281 17
282 17
            foreach ($this->getMapper()->getPlugins() as $plugin) {
283 1
                $plugin->beforeRemove($instance, $this->space);
284 1
            }
285 1
286 1
            if (method_exists($instance, 'beforeRemove')) {
287
                $instance->beforeRemove();
0 ignored issues
show
Bug introduced by
The method beforeRemove() does not seem to exist on object<Tarantool\Mapper\Entity>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
288 18
            }
289
290
            $this->getMapper()->getClient()
291
                ->getSpace($this->space->getId())
292 64
                ->delete($pk);
293
        }
294 64
295
        unset($this->original[$key]);
296
        unset($this->keys[$instance]);
297 64
298
        $this->results = [];
299 64
    }
300 64
301
    public function save($instance)
302 64
    {
303 64
        $key = $this->space->getInstanceKey($instance);
304
        $client = $this->getMapper()->getClient();
305
306 64
        if (array_key_exists($key, $this->persisted)) {
307 64
308
            // update
309
            $tuple = $this->getTuple($instance);
310 64
            $update = [];
311
312
            foreach ($tuple as $i => $v) {
313 64
                if ($v !== $this->original[$key][$i]) {
314
                    $update[$i] = $v;
315 64
                }
316 64
            }
317 64
318
            if (!count($update)) {
319 64
                return $instance;
320 64
            }
321 64
322 64
            $operations = [];
323 64
            foreach ($update as $index => $value) {
324
                $operations[] = ['=', $index, $value];
325 64
            }
326
327
            $pk = [];
328 64 View Code Duplication
            foreach ($this->space->getPrimaryIndex()->parts as $part) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
329 64
                $pk[] = $this->original[$key][$part[0]];
330 64
            }
331 64
332 64
            foreach ($this->getMapper()->getPlugins() as $plugin) {
333
                $plugin->beforeUpdate($instance, $this->space);
334 64
            }
335
336 64
            if (method_exists($instance, 'beforeUpdate')) {
337
                $instance->beforeUpdate();
338 64
            }
339
340
            $client->getSpace($this->space->getId())->update($pk, $operations);
341 64
            $this->original[$key] = $tuple;
342
        } else {
343 64
            $this->addDefaultValues($instance);
344
            foreach ($this->getMapper()->getPlugins() as $plugin) {
345
                $plugin->beforeCreate($instance, $this->space);
346 3
            }
347
348 3
            if (method_exists($instance, 'beforeCreate')) {
349 3
                $instance->beforeCreate();
350 3
            }
351 2
352 2
            $tuple = $this->getTuple($instance);
353 2
            $client->getSpace($this->space->getId())->insert($tuple);
354 2
            $this->persisted[$key] = $instance;
355
            $this->original[$key] = $tuple;
356
        }
357
358 2
        $this->flushCache();
359
360
        return $instance;
361 3
    }
362
363
    private function addDefaultValues(Entity $instance)
364
    {
365
        $format = $this->space->getFormat();
366
367
        // complete indexes fields
368
        foreach ($this->space->getIndexes() as $index) {
369
            foreach ($index->parts as $part) {
370
                $name = $format[$part[0]]['name'];
371
                if (!property_exists($instance, $name)) {
372
                    $instance->{$name} = null;
373
                }
374
            }
375
        }
376
    }
377
378
    public  function getOriginal($instance)
379
    {
380
        return $this->original[$this->space->getInstanceKey($instance)];
381
    }
382
383
    private function getTuple(Entity $instance)
384
    {
385
        $tuple = [];
386
387
        $size = count(get_object_vars($instance));
388
        $skipped = 0;
389
390
        foreach ($this->space->getFormat() as $index => $info) {
391
            if (!property_exists($instance, $info['name'])) {
392
                $skipped++;
393
                $instance->{$info['name']} = null;
394
            }
395
396
            $instance->{$info['name']} = $this->getMapper()->getSchema()
397
                ->formatValue($info['type'], $instance->{$info['name']});
398
            $tuple[$index] = $instance->{$info['name']};
399
400
            if (count($tuple) == $size + $skipped) {
401
                break;
402
            }
403
        }
404
405
        return $tuple;
406
    }
407
408
    public function sync($id, $fields = null)
409
    {
410
        if (array_key_exists($id, $this->persisted)) {
411
            $tuple = $this->getMapper()->getClient()->getSpace($this->space->getId())->select([$id], 0)->getData()[0];
412
413
            foreach ($this->space->getFormat() as $index => $info) {
414
                if (!$fields || in_array($info['name'], $fields)) {
415
                    $value = array_key_exists($index, $tuple) ? $tuple[$index] : null;
416
                    $this->persisted[$id]->{$info['name']} = $value;
417
                    $this->original[$id][$index] = $value;
418
                }
419
            }
420
        }
421
    }
422
423
    public function flushCache()
424
    {
425
        $this->results = [];
426
    }
427
}
428