Completed
Push — master ( f50da1...b30b9b )
by Dmitry
02:24
created

Repository::sync()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 4

Importance

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