Completed
Push — master ( fc9daa...0ac164 )
by Dmitry
01:50
created

Repository::find()   C

Complexity

Conditions 16
Paths 193

Size

Total Lines 60
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 34
CRAP Score 16.0059

Importance

Changes 0
Metric Value
dl 0
loc 60
rs 5.7121
c 0
b 0
f 0
ccs 34
cts 35
cp 0.9714
cc 16
eloc 35
nc 193
nop 2
crap 16.0059

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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' => 'string',
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 (array_key_exists($type, $byType) && 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 findOrFail($params = [])
107 64
    {
108 1
        $entity = $this->findOne($params);
109
        if (!$entity) {
110
            throw new Exception("No ".$this->getSpace()->getName().' found using '.json_encode($params));
111 64
        }
112 10
        return $entity;
113 3
    }
114
115
    public function find($params = [], $one = false)
116 7
    {
117
        $cacheKey = json_encode(func_get_args());
118 7
119
        if (array_key_exists($cacheKey, $this->results)) {
120
            return $this->results[$cacheKey];
121 64
        }
122 2
123
        if (!is_array($params)) {
124
            $params = [$params];
125 64
        }
126 64
        if (count($params) == 1 && array_key_exists(0, $params)) {
127
            $primary = $this->space->getPrimaryIndex();
128 64
            if (count($primary['parts']) == 1) {
129 64
                $formatted = $this->getMapper()->getSchema()->formatValue($primary['parts'][0][1], $params[0]);
130 18
                if ($params[0] == $formatted) {
131 64
                    $params = [
132
                        $this->space->getFormat()[$primary['parts'][0][0]]['name'] => $params[0]
133
                    ];
134
                }
135 64
            }
136 1
        }
137
138
        if (array_key_exists('id', $params)) {
139 64
            if (array_key_exists($params['id'], $this->persisted)) {
140 64
                $instance = $this->persisted[$params['id']];
141 2
                return $one ? $instance : [$instance];
142 2
            }
143
        }
144 64
145 64
146
        $index = $this->space->castIndex($params);
147 1
        if (is_null($index)) {
148 64
            throw new Exception("No index for params ".json_encode($params));
149
        }
150
151
        $client = $this->getMapper()->getClient();
152
        $values = $this->space->getIndexValues($index, $params);
153 64
        if ($this->space->getIndextype($index) == 'hash' && !count($values)) {
154 64
            //  iterator box.index.ALL == 2
155 64
            $data = $client->getSpace($this->space->getId())->select($values, $index, null, null, 2)->getData();
156
        } else {
157
            $data = $client->getSpace($this->space->getId())->select($values, $index)->getData();
158 64
        }
159 64
160 2
        $result = [];
161
        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...
162
            $instance = $this->getInstance($tuple);
163 64
            if ($one) {
164
                return $this->results[$cacheKey] = $instance;
165 64
            }
166 1
            $result[] = $instance;
167 1
        }
168 1
169 1
        if ($one) {
170 1
            return $this->results[$cacheKey] = null;
171
        }
172
173 1
        return $this->results[$cacheKey] = $result;
174
    }
175
176 1
    public function forget($id)
177
    {
178 1
        if (array_key_exists($id, $this->persisted)) {
179 1
            unset($this->persisted[$id]);
180
        }
181 1
    }
182
183
    public function getInstance($tuple)
184 64
    {
185
        $key = $this->space->getTupleKey($tuple);
186 64
187 64
        if (array_key_exists($key, $this->persisted)) {
188 64
            return $this->persisted[$key];
189 64
        }
190 64
191 64
        $class = Entity::class;
192 64 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...
193
            $entityClass = $plugin->getEntityClass($this->space);
194 64
            if ($entityClass) {
195
                if ($class != Entity::class) {
196 64
                    throw new Exception('Entity class override');
197 64
                }
198
                $class = $entityClass;
199 14
            }
200
        }
201
202 64
        $instance = new $class($this);
203 64
204
        $this->original[$key] = $tuple;
205 17
206 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...
207
            $instance->{$info['name']} = array_key_exists($index, $tuple) ? $tuple[$index] : null;
208
        }
209
210
        $this->keys->offsetSet($instance, $key);
211
212 64
        foreach ($this->getMapper()->getPlugins() as $plugin) {
213
            $plugin->afterInstantiate($instance);
214 64
        }
215
216
        return $this->persisted[$key] = $instance;
217 6
    }
218
219 6
    public function getMapper()
220 6
    {
221 6
        return $this->space->getMapper();
222
    }
223 6
224 6
    public function getSpace()
225
    {
226 1
        return $this->space;
227
    }
228 1
229 1
    public function knows($instance)
230 1
    {
231
        return $this->keys->offsetExists($instance);
232 1
    }
233 1
234
    public function update(Entity $instance, $operations)
235 64
    {
236
        if (!count($operations)) {
237 64
            return;
238 64
        }
239
240 64
        $tupleOperations = [];
241
        foreach ($operations as $operation) {
242 64
            $tupleIndex = $this->space->getPropertyIndex($operation[1]);
243 1
            $tupleOperations[] = [$operation[0], $tupleIndex, $operation[2]];
244
        }
245
246 64
        $pk = [];
247 64
        foreach ($this->space->getPrimaryIndex()['parts'] as $part) {
248 64
            $pk[] = $instance->{$this->space->getFormat()[$part[0]]['name']};
249 64
        }
250
251 19
        $client = $this->getMapper()->getClient();
252 19
        $result = $client->getSpace($this->space->getId())->update($pk, $tupleOperations);
253 19
        foreach ($result->getData() as $tuple) {
254 19 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...
255
                if (array_key_exists($index, $tuple)) {
256
                    $instance->{$info['name']} = $tuple[$index];
257 19
                }
258 19
            }
259 4
        }
260 19
    }
261 19
262
    public function truncate()
263
    {
264
        $this->results = [];
265
        $id = $this->space->getId();
266 19
        $this->getMapper()->getClient()->evaluate("box.space[$id]:truncate()");
267 19
    }
268 1
269 19
    public function remove($params = [])
270
    {
271
        if ($params instanceof Entity) {
272
            return $this->removeEntity($params);
273 18
        }
274 18
275 18
        if (!count($params)) {
276 18
            throw new Exception("Use truncate to flush space");
277
        }
278
279 18
        foreach ($this->find($params) as $entity) {
280 17
            $this->removeEntity($entity);
281 17
        }
282 17
    }
283 1
284 1
    public function removeEntity(Entity $instance)
285 1
    {
286 1
        $key = $this->space->getInstanceKey($instance);
287
288 18
        if (!array_key_exists($key, $this->original)) {
289
            return;
290
        }
291
292 64
        if (array_key_exists($key, $this->persisted)) {
293
            unset($this->persisted[$key]);
294 64
295
            $pk = [];
296 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...
297 64
                $pk[] = $this->original[$key][$part[0]];
298
            }
299 64
300 64
            foreach ($this->getMapper()->getPlugins() as $plugin) {
301
                $plugin->beforeRemove($instance, $this->space);
302 64
            }
303 64
304
            if (method_exists($instance, 'beforeRemove')) {
305
                $instance->beforeRemove();
0 ignored issues
show
Documentation Bug introduced by
The method beforeRemove does not exist on object<Tarantool\Mapper\Entity>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
306 64
            }
307 64
308
            $this->getMapper()->getClient()
309
                ->getSpace($this->space->getId())
310 64
                ->delete($pk);
311
312
            foreach ($this->getMapper()->getPlugins() as $plugin) {
313 64
                $plugin->afterRemove($instance, $this->space);
314
            }
315 64
316 64
            if (method_exists($instance, 'afterRemove')) {
317 64
                $instance->afterRemove();
0 ignored issues
show
Documentation Bug introduced by
The method afterRemove does not exist on object<Tarantool\Mapper\Entity>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
318
            }
319 64
        }
320 64
321 64
        unset($this->original[$key]);
322 64
        unset($this->keys[$instance]);
323 64
324
        $this->results = [];
325 64
    }
326
327
    public function save($instance)
328 64
    {
329 64
        $key = $this->space->getInstanceKey($instance);
330 64
        $client = $this->getMapper()->getClient();
331 64
332 64
        if (array_key_exists($key, $this->persisted)) {
333
            // update
334 64
            $tuple = $this->getTuple($instance);
335
            $update = [];
336 64
337 View Code Duplication
            foreach ($tuple as $i => $v) {
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...
338 64
                if (!array_key_exists($i, $this->original[$key]) || $v !== $this->original[$key][$i]) {
339
                    $update[$i] = $v;
340
                }
341 64
            }
342
343 64
            if (!count($update)) {
344
                return $instance;
345
            }
346 3
347
            foreach ($this->getMapper()->getPlugins() as $plugin) {
348 3
                $plugin->beforeUpdate($instance, $this->space);
349 3
            }
350 3
351 2
            if (method_exists($instance, 'beforeUpdate')) {
352 2
                $instance->beforeUpdate();
353 2
            }
354 2
355
            $tuple = $this->getTuple($instance);
356
            $update = [];
357
358 2 View Code Duplication
            foreach ($tuple as $i => $v) {
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...
359
                if (!array_key_exists($i, $this->original[$key]) || $v !== $this->original[$key][$i]) {
360
                    $update[$i] = $v;
361 3
                }
362
            }
363
364
            if (!count($update)) {
365
                return $instance;
366
            }
367
368
            $operations = [];
369
            foreach ($update as $index => $value) {
370
                $operations[] = ['=', $index, $value];
371
            }
372
373
            $pk = [];
374 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...
375
                $pk[] = $this->original[$key][$part[0]];
376
            }
377
378
            $client->getSpace($this->space->getId())->update($pk, $operations);
379
            $this->original[$key] = $tuple;
380
381
            foreach ($this->getMapper()->getPlugins() as $plugin) {
382
                $plugin->afterUpdate($instance, $this->space);
383
            }
384
385
            if (method_exists($instance, 'afterUpdate')) {
386
                $instance->afterUpdate();
387
            }
388
        } else {
389
            $this->addDefaultValues($instance);
390
            foreach ($this->getMapper()->getPlugins() as $plugin) {
391
                $plugin->beforeCreate($instance, $this->space);
392
            }
393
394
            if (method_exists($instance, 'beforeCreate')) {
395
                $instance->beforeCreate();
396
            }
397
398
            $tuple = $this->getTuple($instance);
399
            $client->getSpace($this->space->getId())->insert($tuple);
400
            $this->persisted[$key] = $instance;
401
            $this->original[$key] = $tuple;
402
403
            foreach ($this->getMapper()->getPlugins() as $plugin) {
404
                $plugin->afterCreate($instance, $this->space);
405
            }
406
407
            if (method_exists($instance, 'afterCreate')) {
408
                $instance->afterCreate();
409
            }
410
        }
411
412
        $this->flushCache();
413
414
        return $instance;
415
    }
416
417
    private function addDefaultValues(Entity $instance)
418
    {
419
        $format = $this->space->getFormat();
420
421
        // complete format fields
422
        foreach ($format as $info) {
423
            $name = $info['name'];
424
            if (!property_exists($instance, $name)) {
425
                $instance->$name = null;
426
            }
427
        }
428
    }
429
430
    public function getOriginal($instance)
431
    {
432
        return $this->original[$this->space->getInstanceKey($instance)];
433
    }
434
435
    private function getTuple(Entity $instance)
436
    {
437
        $schema = $this->getMapper()->getSchema();
438
        $tuple = [];
439
440
        foreach ($this->space->getFormat() as $index => $info) {
441
            $name = $info['name'];
442
            if (!property_exists($instance, $name)) {
443
                $instance->$name = null;
444
            }
445
446
            $instance->$name = $schema->formatValue($info['type'], $instance->$name);
447
            if (is_null($instance->$name)) {
448
                if (!$this->space->isPropertyNullable($name)) {
449
                    $instance->$name = $schema->getDefaultValue($info['type']);
450
                }
451
            }
452
453
            $tuple[$index] = $instance->$name;
454
        }
455
456
        return $tuple;
457
    }
458
459
    public function sync($id, $fields = null)
460
    {
461
        if (array_key_exists($id, $this->persisted)) {
462
            $tuple = $this->getMapper()->getClient()->getSpace($this->space->getId())->select([$id], 0)->getData()[0];
463
464
            foreach ($this->space->getFormat() as $index => $info) {
465
                if (!$fields || in_array($info['name'], $fields)) {
466
                    $value = array_key_exists($index, $tuple) ? $tuple[$index] : null;
467
                    $this->persisted[$id]->{$info['name']} = $value;
468
                    $this->original[$id][$index] = $value;
469
                }
470
            }
471
        }
472
    }
473
474
    public function flushCache()
475
    {
476
        $this->results = [];
477
    }
478
}
479