Completed
Push — master ( 93f83d...a02d8d )
by Dmitry
02:31
created

Repository::getInstance()   C

Complexity

Conditions 8
Paths 20

Size

Total Lines 35
Code Lines 19

Duplication

Lines 12
Ratio 34.29 %

Code Coverage

Tests 22
CRAP Score 8.0052

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 12
loc 35
rs 5.3846
ccs 22
cts 23
cp 0.9565
cc 8
eloc 19
nc 20
nop 1
crap 8.0052
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
        $this->keys[$instance] = $key;
83
        return $instance;
84 64
    }
85
86 64
    public function findOne($params = [])
87 64
    {
88 64
        return $this->find($params, true);
89 64
    }
90
91 64
    public function findOrCreate($params = [])
92
    {
93
        $entity = $this->findOne($params);
94
        if (!$entity) {
95 1
            $entity = $this->create($params);
96
        }
97
        return $entity;
98 19
    }
99
100 19
    public function find($params = [], $one = false)
101
    {
102
        $cacheKey = json_encode(func_get_args());
103 64
104
        if (array_key_exists($cacheKey, $this->results)) {
105 64
            return $this->results[$cacheKey];
106
        }
107 64
108 1
        if (!is_array($params)) {
109
            $params = [$params];
110
        }
111 64
        if (count($params) == 1 && array_key_exists(0, $params)) {
112 10
            $primary = $this->space->getPrimaryIndex();
113 3
            if (count($primary->parts) == 1) {
114
                $formatted = $this->getMapper()->getSchema()->formatValue($primary->parts[0][1], $params[0]);
115
                if ($params[0] == $formatted) {
116 7
                    $params = [
117
                        $this->space->getFormat()[$primary->parts[0][0]]['name'] => $params[0]
118 7
                    ];
119
                }
120
            }
121 64
        }
122 2
123
        if (array_key_exists('id', $params)) {
124
            if (array_key_exists($params['id'], $this->persisted)) {
125 64
                $instance = $this->persisted[$params['id']];
126 64
                return $one ? $instance : [$instance];
127
            }
128 64
        }
129 64
130 18
131 64
        $index = $this->space->castIndex($params);
132
        if (is_null($index)) {
133
            throw new Exception("No index for params ".json_encode($params));
134
        }
135 64
136 1
        $client = $this->getMapper()->getClient();
137
        $values = $this->space->getIndexValues($index, $params);
138
139 64
        $data = $client->getSpace($this->space->getId())->select($values, $index)->getData();
140 64
141 2
        $result = [];
142 2
        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...
143
            $instance = $this->getInstance($tuple);
144 64
            if ($one) {
145 64
                return $this->results[$cacheKey] = $instance;
146
            }
147 1
            $result[] = $instance;
148 64
        }
149
150
        if ($one) {
151
            return $this->results[$cacheKey] = null;
152
        }
153 64
154 64
        return $this->results[$cacheKey] = $result;
155 64
    }
156
157
    public function forget($id)
158 64
    {
159 64
        if (array_key_exists($id, $this->persisted)) {
160 2
            unset($this->persisted[$id]);
161
        }
162
    }
163 64
164
    private function getInstance($tuple)
165 64
    {
166 1
        $key = $this->space->getTupleKey($tuple);
167 1
168 1
        if (array_key_exists($key, $this->persisted)) {
169 1
            return $this->persisted[$key];
170 1
        }
171
172
        $class = Entity::class;
173 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...
174
            $entityClass = $plugin->getEntityClass($this->space);
175
            if ($entityClass) {
176 1
                if ($class != Entity::class) {
177
                    throw new Exception('Entity class override');
178 1
                }
179 1
                $class = $entityClass;
180
            }
181 1
        }
182
183
        $instance = new $class($this);
184 64
185
        $this->original[$key] = $tuple;
186 64
187 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...
188 64
            $instance->{$info['name']} = array_key_exists($index, $tuple) ? $tuple[$index] : null;
189 64
        }
190 64
191 64
        $this->keys->offsetSet($instance, $key);
192 64
193
        foreach ($this->getMapper()->getPlugins() as $plugin) {
194 64
            $plugin->afterInstantiate($instance);
195
        }
196 64
197 64
        return $this->persisted[$key] = $instance;
198
    }
199 14
200
    public function getMapper()
201
    {
202 64
        return $this->space->getMapper();
203 64
    }
204
205 17
    public function knows($instance)
206
    {
207
        return $this->keys->offsetExists($instance);
208
    }
209
210
    public function update(Entity $instance, $operations)
211
    {
212 64
        if (!count($operations)) {
213
            return;
214 64
        }
215
216
        $tupleOperations = [];
217 6
        foreach ($operations as $operation) {
218
            $tupleIndex = $this->space->getPropertyIndex($operation[1]);
219 6
            $tupleOperations[] = [$operation[0], $tupleIndex, $operation[2]];
220 6
        }
221 6
222
        $pk = [];
223 6
        foreach ($this->space->getPrimaryIndex()->parts as $part) {
224 6
            $pk[] = $instance->{$this->space->getFormat()[$part[0]]['name']};
225
        }
226 1
227
        $client = $this->getMapper()->getClient();
228 1
        $result = $client->getSpace($this->space->getId())->update($pk, $tupleOperations);
229 1
        foreach ($result->getData() as $tuple) {
230 1 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...
231
                if (array_key_exists($index, $tuple)) {
232 1
                    $instance->{$info['name']} = $tuple[$index];
233 1
                }
234
            }
235 64
        }
236
    }
237 64
238 64
    public function truncate()
239
    {
240 64
        $this->results = [];
241
        $id = $this->space->getId();
242 64
        $this->getMapper()->getClient()->evaluate("box.space[$id]:truncate()");
243 1
    }
244
245
    public function remove($params = [])
246 64
    {
247 64
        if ($params instanceof Entity) {
248 64
            return $this->removeEntity($params);
249 64
        }
250
251 19
        if (!count($params)) {
252 19
            throw new Exception("Use truncate to flush space");
253 19
        }
254 19
255
        foreach ($this->find($params) as $entity) {
256
            $this->removeEntity($entity);
257 19
        }
258 19
    }
259 4
260 19
    public function removeEntity(Entity $instance)
261 19
    {
262
        $key = $this->space->getInstanceKey($instance);
263
264
        if (!array_key_exists($key, $this->original)) {
265
            return;
266 19
        }
267 19
268 1
        if (array_key_exists($key, $this->persisted)) {
269 19
            unset($this->persisted[$key]);
270
271
            $pk = [];
272 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...
273 18
                $pk[] = $this->original[$key][$part[0]];
274 18
            }
275 18
276 18
            foreach ($this->getMapper()->getPlugins() as $plugin) {
277
                $plugin->beforeRemove($instance, $this->space);
278
            }
279 18
280 17
            if (method_exists($instance, 'beforeRemove')) {
281 17
                $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...
282 17
            }
283 1
284 1
            $this->getMapper()->getClient()
285 1
                ->getSpace($this->space->getId())
286 1
                ->delete($pk);
287
        }
288 18
289
        unset($this->original[$key]);
290
291
        $this->results = [];
292 64
    }
293
294 64
    public function save($instance)
295
    {
296
        $key = $this->space->getInstanceKey($instance);
297 64
        $client = $this->getMapper()->getClient();
298
299 64
        if (array_key_exists($key, $this->persisted)) {
300 64
301
            // update
302 64
            $tuple = $this->getTuple($instance);
303 64
            $update = array_diff_assoc($tuple, $this->original[$key]);
304
            if (!count($update)) {
305
                return $instance;
306 64
            }
307 64
308
            $operations = [];
309
            foreach ($update as $index => $value) {
310 64
                $operations[] = ['=', $index, $value];
311
            }
312
313 64
            $pk = [];
314 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...
315 64
                $pk[] = $this->original[$key][$part[0]];
316 64
            }
317 64
318
            foreach ($this->getMapper()->getPlugins() as $plugin) {
319 64
                $plugin->beforeUpdate($instance, $this->space);
320 64
            }
321 64
322 64
            if (method_exists($instance, 'beforeUpdate')) {
323 64
                $instance->beforeUpdate();
324
            }
325 64
326
            $client->getSpace($this->space->getId())->update($pk, $operations);
327
            $this->original[$key] = $tuple;
328 64
        } else {
329 64
            $this->addDefaultValues($instance);
330 64
            foreach ($this->getMapper()->getPlugins() as $plugin) {
331 64
                $plugin->beforeCreate($instance, $this->space);
332 64
            }
333
334 64
            if (method_exists($instance, 'beforeCreate')) {
335
                $instance->beforeCreate();
336 64
            }
337
338 64
            $tuple = $this->getTuple($instance);
339
            $client->getSpace($this->space->getId())->insert($tuple);
340
            $this->persisted[$key] = $instance;
341 64
            $this->original[$key] = $tuple;
342
        }
343 64
344
        $this->flushCache();
345
346 3
        return $instance;
347
    }
348 3
349 3
    private function addDefaultValues(Entity $instance)
350 3
    {
351 2
        $format = $this->space->getFormat();
352 2
353 2
        // complete indexes fields
354 2
        foreach ($this->space->getIndexes() as $index) {
355
            foreach ($index->parts as $part) {
356
                $name = $format[$part[0]]['name'];
357
                if (!property_exists($instance, $name)) {
358 2
                    $instance->{$name} = null;
359
                }
360
            }
361 3
        }
362
    }
363
364
    public  function getOriginal($instance)
365
    {
366
        return $this->original[$this->space->getInstanceKey($instance)];
367
    }
368
369
    private function getTuple(Entity $instance)
370
    {
371
        $tuple = [];
372
373
        $size = count(get_object_vars($instance));
374
        $skipped = 0;
375
376
        foreach ($this->space->getFormat() as $index => $info) {
377
            if (!property_exists($instance, $info['name'])) {
378
                $skipped++;
379
                $instance->{$info['name']} = null;
380
            }
381
382
            $instance->{$info['name']} = $this->getMapper()->getSchema()
383
                ->formatValue($info['type'], $instance->{$info['name']});
384
            $tuple[$index] = $instance->{$info['name']};
385
386
            if (count($tuple) == $size + $skipped) {
387
                break;
388
            }
389
        }
390
391
        return $tuple;
392
    }
393
394
    public function sync($id, $fields = null)
395
    {
396
        if (array_key_exists($id, $this->persisted)) {
397
            $tuple = $this->getMapper()->getClient()->getSpace($this->space->getId())->select([$id], 0)->getData()[0];
398
399
            foreach ($this->space->getFormat() as $index => $info) {
400
                if (!$fields || in_array($info['name'], $fields)) {
401
                    $value = array_key_exists($index, $tuple) ? $tuple[$index] : null;
402
                    $this->persisted[$id]->{$info['name']} = $value;
403
                    $this->original[$id][$index] = $value;
404
                }
405
            }
406
        }
407
    }
408
409
    public function flushCache()
410
    {
411
        $this->results = [];
412
    }
413
}
414