Completed
Push — master ( 185f8b...c73321 )
by Dmitry
02:44
created

Repository::findOne()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 1
cts 1
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1
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 getSpace()
212 64
    {
213
        return $this->space;
214 64
    }
215
216
    public function knows($instance)
217 6
    {
218
        return $this->keys->offsetExists($instance);
219 6
    }
220 6
221 6
    public function update(Entity $instance, $operations)
222
    {
223 6
        if (!count($operations)) {
224 6
            return;
225
        }
226 1
227
        $tupleOperations = [];
228 1
        foreach ($operations as $operation) {
229 1
            $tupleIndex = $this->space->getPropertyIndex($operation[1]);
230 1
            $tupleOperations[] = [$operation[0], $tupleIndex, $operation[2]];
231
        }
232 1
233 1
        $pk = [];
234
        foreach ($this->space->getPrimaryIndex()->parts as $part) {
235 64
            $pk[] = $instance->{$this->space->getFormat()[$part[0]]['name']};
236
        }
237 64
238 64
        $client = $this->getMapper()->getClient();
239
        $result = $client->getSpace($this->space->getId())->update($pk, $tupleOperations);
240 64
        foreach ($result->getData() as $tuple) {
241 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...
242 64
                if (array_key_exists($index, $tuple)) {
243 1
                    $instance->{$info['name']} = $tuple[$index];
244
                }
245
            }
246 64
        }
247 64
    }
248 64
249 64
    public function truncate()
250
    {
251 19
        $this->results = [];
252 19
        $id = $this->space->getId();
253 19
        $this->getMapper()->getClient()->evaluate("box.space[$id]:truncate()");
254 19
    }
255
256
    public function remove($params = [])
257 19
    {
258 19
        if ($params instanceof Entity) {
259 4
            return $this->removeEntity($params);
260 19
        }
261 19
262
        if (!count($params)) {
263
            throw new Exception("Use truncate to flush space");
264
        }
265
266 19
        foreach ($this->find($params) as $entity) {
267 19
            $this->removeEntity($entity);
268 1
        }
269 19
    }
270
271
    public function removeEntity(Entity $instance)
272
    {
273 18
        $key = $this->space->getInstanceKey($instance);
274 18
275 18
        if (!array_key_exists($key, $this->original)) {
276 18
            return;
277
        }
278
279 18
        if (array_key_exists($key, $this->persisted)) {
280 17
            unset($this->persisted[$key]);
281 17
282 17
            $pk = [];
283 1 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...
284 1
                $pk[] = $this->original[$key][$part[0]];
285 1
            }
286 1
287
            foreach ($this->getMapper()->getPlugins() as $plugin) {
288 18
                $plugin->beforeRemove($instance, $this->space);
289
            }
290
291
            if (method_exists($instance, 'beforeRemove')) {
292 64
                $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...
293
            }
294 64
295
            $this->getMapper()->getClient()
296
                ->getSpace($this->space->getId())
297 64
                ->delete($pk);
298
        }
299 64
300 64
        unset($this->original[$key]);
301
        unset($this->keys[$instance]);
302 64
303 64
        $this->results = [];
304
    }
305
306 64
    public function save($instance)
307 64
    {
308
        $key = $this->space->getInstanceKey($instance);
309
        $client = $this->getMapper()->getClient();
310 64
311
        if (array_key_exists($key, $this->persisted)) {
312
313 64
            // update
314
            $tuple = $this->getTuple($instance);
315 64
            $update = [];
316 64
317 64
            foreach ($tuple as $i => $v) {
318
                if ($v !== $this->original[$key][$i]) {
319 64
                    $update[$i] = $v;
320 64
                }
321 64
            }
322 64
323 64
            if (!count($update)) {
324
                return $instance;
325 64
            }
326
327
            $operations = [];
328 64
            foreach ($update as $index => $value) {
329 64
                $operations[] = ['=', $index, $value];
330 64
            }
331 64
332 64
            $pk = [];
333 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...
334 64
                $pk[] = $this->original[$key][$part[0]];
335
            }
336 64
337
            foreach ($this->getMapper()->getPlugins() as $plugin) {
338 64
                $plugin->beforeUpdate($instance, $this->space);
339
            }
340
341 64
            if (method_exists($instance, 'beforeUpdate')) {
342
                $instance->beforeUpdate();
343 64
            }
344
345
            $client->getSpace($this->space->getId())->update($pk, $operations);
346 3
            $this->original[$key] = $tuple;
347
        } else {
348 3
            $this->addDefaultValues($instance);
349 3
            foreach ($this->getMapper()->getPlugins() as $plugin) {
350 3
                $plugin->beforeCreate($instance, $this->space);
351 2
            }
352 2
353 2
            if (method_exists($instance, 'beforeCreate')) {
354 2
                $instance->beforeCreate();
355
            }
356
357
            $tuple = $this->getTuple($instance);
358 2
            $client->getSpace($this->space->getId())->insert($tuple);
359
            $this->persisted[$key] = $instance;
360
            $this->original[$key] = $tuple;
361 3
        }
362
363
        $this->flushCache();
364
365
        return $instance;
366
    }
367
368
    private function addDefaultValues(Entity $instance)
369
    {
370
        $format = $this->space->getFormat();
371
372
        // complete indexes fields
373
        foreach ($this->space->getIndexes() as $index) {
374
            foreach ($index->parts as $part) {
375
                $name = $format[$part[0]]['name'];
376
                if (!property_exists($instance, $name)) {
377
                    $instance->{$name} = null;
378
                }
379
            }
380
        }
381
    }
382
383
    public  function getOriginal($instance)
384
    {
385
        return $this->original[$this->space->getInstanceKey($instance)];
386
    }
387
388
    private function getTuple(Entity $instance)
389
    {
390
        $tuple = [];
391
392
        $size = count(get_object_vars($instance));
393
        $skipped = 0;
394
395
        foreach ($this->space->getFormat() as $index => $info) {
396
            if (!property_exists($instance, $info['name'])) {
397
                $skipped++;
398
                $instance->{$info['name']} = null;
399
            }
400
401
            $instance->{$info['name']} = $this->getMapper()->getSchema()
402
                ->formatValue($info['type'], $instance->{$info['name']});
403
            $tuple[$index] = $instance->{$info['name']};
404
405
            if (count($tuple) == $size + $skipped) {
406
                break;
407
            }
408
        }
409
410
        return $tuple;
411
    }
412
413
    public function sync($id, $fields = null)
414
    {
415
        if (array_key_exists($id, $this->persisted)) {
416
            $tuple = $this->getMapper()->getClient()->getSpace($this->space->getId())->select([$id], 0)->getData()[0];
417
418
            foreach ($this->space->getFormat() as $index => $info) {
419
                if (!$fields || in_array($info['name'], $fields)) {
420
                    $value = array_key_exists($index, $tuple) ? $tuple[$index] : null;
421
                    $this->persisted[$id]->{$info['name']} = $value;
422
                    $this->original[$id][$index] = $value;
423
                }
424
            }
425
        }
426
    }
427
428
    public function flushCache()
429
    {
430
        $this->results = [];
431
    }
432
}
433