Completed
Push — master ( 8a13a1...5138b1 )
by Dmitry
02:21
created

Repository::knows()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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