Completed
Push — master ( 2469a2...4a4199 )
by Dmitry
01:51
created

Repository::find()   C

Complexity

Conditions 14
Paths 113

Size

Total Lines 56
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 14

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 56
rs 6.2074
ccs 33
cts 33
cp 1
cc 14
eloc 32
nc 113
nop 2
crap 14

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' => '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 findOrFail($params = [])
107 64
    {
108 1
        $entity = $this->findOne($params);
109
        if (!$entity) {
110
            throw new Exception("Invalid entity");
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
154 64
        $data = $client->getSpace($this->space->getId())->select($values, $index)->getData();
155 64
156
        $result = [];
157
        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...
158 64
            $instance = $this->getInstance($tuple);
159 64
            if ($one) {
160 2
                return $this->results[$cacheKey] = $instance;
161
            }
162
            $result[] = $instance;
163 64
        }
164
165 64
        if ($one) {
166 1
            return $this->results[$cacheKey] = null;
167 1
        }
168 1
169 1
        return $this->results[$cacheKey] = $result;
170 1
    }
171
172
    public function forget($id)
173 1
    {
174
        if (array_key_exists($id, $this->persisted)) {
175
            unset($this->persisted[$id]);
176 1
        }
177
    }
178 1
179 1
    private function getInstance($tuple)
180
    {
181 1
        $key = $this->space->getTupleKey($tuple);
182
183
        if (array_key_exists($key, $this->persisted)) {
184 64
            return $this->persisted[$key];
185
        }
186 64
187 64
        $class = Entity::class;
188 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...
189 64
            $entityClass = $plugin->getEntityClass($this->space);
190 64
            if ($entityClass) {
191 64
                if ($class != Entity::class) {
192 64
                    throw new Exception('Entity class override');
193
                }
194 64
                $class = $entityClass;
195
            }
196 64
        }
197 64
198
        $instance = new $class($this);
199 14
200
        $this->original[$key] = $tuple;
201
202 64 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...
203 64
            $instance->{$info['name']} = array_key_exists($index, $tuple) ? $tuple[$index] : null;
204
        }
205 17
206
        $this->keys->offsetSet($instance, $key);
207
208
        foreach ($this->getMapper()->getPlugins() as $plugin) {
209
            $plugin->afterInstantiate($instance);
210
        }
211
212 64
        return $this->persisted[$key] = $instance;
213
    }
214 64
215
    public function getMapper()
216
    {
217 6
        return $this->space->getMapper();
218
    }
219 6
220 6
    public function getSpace()
221 6
    {
222
        return $this->space;
223 6
    }
224 6
225
    public function knows($instance)
226 1
    {
227
        return $this->keys->offsetExists($instance);
228 1
    }
229 1
230 1
    public function update(Entity $instance, $operations)
231
    {
232 1
        if (!count($operations)) {
233 1
            return;
234
        }
235 64
236
        $tupleOperations = [];
237 64
        foreach ($operations as $operation) {
238 64
            $tupleIndex = $this->space->getPropertyIndex($operation[1]);
239
            $tupleOperations[] = [$operation[0], $tupleIndex, $operation[2]];
240 64
        }
241
242 64
        $pk = [];
243 1
        foreach ($this->space->getPrimaryIndex()['parts'] as $part) {
244
            $pk[] = $instance->{$this->space->getFormat()[$part[0]]['name']};
245
        }
246 64
247 64
        $client = $this->getMapper()->getClient();
248 64
        $result = $client->getSpace($this->space->getId())->update($pk, $tupleOperations);
249 64
        foreach ($result->getData() as $tuple) {
250 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...
251 19
                if (array_key_exists($index, $tuple)) {
252 19
                    $instance->{$info['name']} = $tuple[$index];
253 19
                }
254 19
            }
255
        }
256
    }
257 19
258 19
    public function truncate()
259 4
    {
260 19
        $this->results = [];
261 19
        $id = $this->space->getId();
262
        $this->getMapper()->getClient()->evaluate("box.space[$id]:truncate()");
263
    }
264
265
    public function remove($params = [])
266 19
    {
267 19
        if ($params instanceof Entity) {
268 1
            return $this->removeEntity($params);
269 19
        }
270
271
        if (!count($params)) {
272
            throw new Exception("Use truncate to flush space");
273 18
        }
274 18
275 18
        foreach ($this->find($params) as $entity) {
276 18
            $this->removeEntity($entity);
277
        }
278
    }
279 18
280 17
    public function removeEntity(Entity $instance)
281 17
    {
282 17
        $key = $this->space->getInstanceKey($instance);
283 1
284 1
        if (!array_key_exists($key, $this->original)) {
285 1
            return;
286 1
        }
287
288 18
        if (array_key_exists($key, $this->persisted)) {
289
            unset($this->persisted[$key]);
290
291
            $pk = [];
292 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...
293
                $pk[] = $this->original[$key][$part[0]];
294 64
            }
295
296
            foreach ($this->getMapper()->getPlugins() as $plugin) {
297 64
                $plugin->beforeRemove($instance, $this->space);
298
            }
299 64
300 64
            if (method_exists($instance, 'beforeRemove')) {
301
                $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...
302 64
            }
303 64
304
            $this->getMapper()->getClient()
305
                ->getSpace($this->space->getId())
306 64
                ->delete($pk);
307 64
        }
308
309
        unset($this->original[$key]);
310 64
        unset($this->keys[$instance]);
311
312
        $this->results = [];
313 64
    }
314
315 64
    public function save($instance)
316 64
    {
317 64
        $key = $this->space->getInstanceKey($instance);
318
        $client = $this->getMapper()->getClient();
319 64
320 64
        if (array_key_exists($key, $this->persisted)) {
321 64
            // update
322 64
            $tuple = $this->getTuple($instance);
323 64
            $update = [];
324
325 64 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...
326
                if (!array_key_exists($i, $this->original[$key]) || $v !== $this->original[$key][$i]) {
327
                    $update[$i] = $v;
328 64
                }
329 64
            }
330 64
331 64
            if (!count($update)) {
332 64
                return $instance;
333
            }
334 64
335
            foreach ($this->getMapper()->getPlugins() as $plugin) {
336 64
                $plugin->beforeUpdate($instance, $this->space);
337
            }
338 64
339
            if (method_exists($instance, 'beforeUpdate')) {
340
                $instance->beforeUpdate();
341 64
            }
342
343 64
            $tuple = $this->getTuple($instance);
344
            $update = [];
345
346 3 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...
347
                if (!array_key_exists($i, $this->original[$key]) || $v !== $this->original[$key][$i]) {
348 3
                    $update[$i] = $v;
349 3
                }
350 3
            }
351 2
352 2
            if (!count($update)) {
353 2
                return $instance;
354 2
            }
355
356
            $operations = [];
357
            foreach ($update as $index => $value) {
358 2
                $operations[] = ['=', $index, $value];
359
            }
360
361 3
            $pk = [];
362 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...
363
                $pk[] = $this->original[$key][$part[0]];
364
            }
365
366
            $client->getSpace($this->space->getId())->update($pk, $operations);
367
            $this->original[$key] = $tuple;
368
369
            foreach ($this->getMapper()->getPlugins() as $plugin) {
370
                $plugin->afterUpdate($instance, $this->space);
371
            }
372
373
            if (method_exists($instance, 'afterUpdate')) {
374
                $instance->afterUpdate();
375
            }
376
377
        } else {
378
            $this->addDefaultValues($instance);
379
            foreach ($this->getMapper()->getPlugins() as $plugin) {
380
                $plugin->beforeCreate($instance, $this->space);
381
            }
382
383
            if (method_exists($instance, 'beforeCreate')) {
384
                $instance->beforeCreate();
385
            }
386
387
            $tuple = $this->getTuple($instance);
388
            $client->getSpace($this->space->getId())->insert($tuple);
389
            $this->persisted[$key] = $instance;
390
            $this->original[$key] = $tuple;
391
392
            foreach ($this->getMapper()->getPlugins() as $plugin) {
393
                $plugin->afterCreate($instance, $this->space);
394
            }
395
396
            if (method_exists($instance, 'afterCreate')) {
397
                $instance->afterCreate();
398
            }
399
        }
400
401
        $this->flushCache();
402
403
        return $instance;
404
    }
405
406
    private function addDefaultValues(Entity $instance)
407
    {
408
        $format = $this->space->getFormat();
409
410
        // complete indexes fields
411
        foreach ($this->space->getIndexes() as $index) {
412
            foreach ($index['parts'] as $part) {
413
                $name = $format[$part[0]]['name'];
414
                if (!property_exists($instance, $name)) {
415
                    $instance->{$name} = null;
416
                }
417
            }
418
        }
419
    }
420
421
    public function getOriginal($instance)
422
    {
423
        return $this->original[$this->space->getInstanceKey($instance)];
424
    }
425
426
    private function getTuple(Entity $instance)
427
    {
428
        $tuple = [];
429
430
        $size = count(get_object_vars($instance));
431
        $skipped = 0;
432
433
        foreach ($this->space->getFormat() as $index => $info) {
434
            if (!property_exists($instance, $info['name'])) {
435
                $skipped++;
436
                $instance->{$info['name']} = null;
437
            }
438
439
            $instance->{$info['name']} = $this->getMapper()->getSchema()
440
                ->formatValue($info['type'], $instance->{$info['name']});
441
            $tuple[$index] = $instance->{$info['name']};
442
443
            if (count($tuple) == $size + $skipped) {
444
                break;
445
            }
446
        }
447
448
        return $tuple;
449
    }
450
451
    public function sync($id, $fields = null)
452
    {
453
        if (array_key_exists($id, $this->persisted)) {
454
            $tuple = $this->getMapper()->getClient()->getSpace($this->space->getId())->select([$id], 0)->getData()[0];
455
456
            foreach ($this->space->getFormat() as $index => $info) {
457
                if (!$fields || in_array($info['name'], $fields)) {
458
                    $value = array_key_exists($index, $tuple) ? $tuple[$index] : null;
459
                    $this->persisted[$id]->{$info['name']} = $value;
460
                    $this->original[$id][$index] = $value;
461
                }
462
            }
463
        }
464
    }
465
466
    public function flushCache()
467
    {
468
        $this->results = [];
469
    }
470
}
471