Completed
Push — master ( 9540af...12fe6d )
by Dmitry
01:44
created

Space::getMapper()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Tarantool\Mapper;
4
5
use Exception;
6
7
class Space
8
{
9
    private $mapper;
10
11
    private $id;
12
    private $name;
13
    private $format;
14
    private $indexes;
15
16
    private $formatNamesHash = [];
17
    private $formatTypesHash = [];
18
    private $formatReferences = [];
19
20
    private $repository;
21
22
    public function __construct(Mapper $mapper, $id, $name, $meta = null)
23
    {
24
        $this->mapper = $mapper;
25
        $this->id = $id;
26
        $this->name = $name;
27
28
        if ($meta) {
29
            foreach ($meta as $key => $value) {
30
                $this->$key = $value;
31
            }
32
        }
33
    }
34
35
    public function addProperties($config)
36
    {
37
        foreach ($config as $name => $type) {
38
            $this->addProperty($name, $type);
39
        }
40
        return $this;
41
    }
42
43
    public function addProperty($name, $type, $reference = null)
44
    {
45
        $format = $this->getFormat();
46
        foreach ($format as $field) {
47
            if ($field['name'] == $name) {
48
                throw new Exception("Property $name exists");
49
            }
50
        }
51
        $row = compact('name', 'type');
52
        if ($reference) {
53
            $row['reference'] = $reference;
54
        }
55
        $format[] = $row;
56
        $this->format = $format;
57
        $this->mapper->getClient()->evaluate("box.space[$this->id]:format(...)", [$format]);
58
59
        $this->parseFormat();
60
61
        return $this;
62
    }
63
64
    public function removeProperty($name)
65
    {
66
        $format = $this->getFormat();
67
        $last = array_pop($format);
68
        if ($last['name'] != $name) {
69
            throw new Exception("Remove only last property");
70
        }
71
        $this->mapper->getClient()->evaluate("box.space[$this->id]:format(...)", [$format]);
72
        $this->format = $format;
73
74
        $this->parseFormat();
75
76
        return $this;
77
    }
78
79
    public function removeIndex($name)
80
    {
81
        $this->mapper->getClient()->evaluate("box.space[$this->id].index.$name:drop()");
82
        $this->indexes = [];
83
        $this->mapper->getRepository('_index')->flushCache();
84
85
        return $this;
86
    }
87
88
    public function addIndex($config)
89
    {
90
        return $this->createIndex($config);
91
    }
92
93
    public function createIndex($config)
94
    {
95
        if (!is_array($config)) {
96
            $config = ['fields' => $config];
97
        }
98
99
100
        if (!array_key_exists('fields', $config)) {
101
            if (array_values($config) != $config) {
102
                throw new Exception("Invalid index configuration");
103
            }
104
            $config = [
105
                'fields' => $config
106
            ];
107
        }
108
109
        if (!is_array($config['fields'])) {
110
            $config['fields'] = [$config['fields']];
111
        }
112
113
        $options = [
114
            'parts' => []
115
        ];
116
117
        foreach ($config as $k => $v) {
118
            if ($k != 'name' && $k != 'fields') {
119
                $options[$k] = $v;
120
            }
121
        }
122
123 View Code Duplication
        foreach ($config['fields'] as $property) {
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...
124
            if (!$this->getPropertyType($property)) {
125
                throw new Exception("Unknown property $property", 1);
126
            }
127
            $options['parts'][] = $this->getPropertyIndex($property)+1;
128
            $options['parts'][] = $this->getPropertyType($property);
129
        }
130
131
        $name = array_key_exists('name', $config) ? $config['name'] : implode('_', $config['fields']);
132
133
        $this->mapper->getClient()->evaluate("box.space[$this->id]:create_index('$name', ...)", [$options]);
134
        $this->indexes = [];
135
136
        $this->mapper->getSchema()->getSpace('_index')->getRepository()->flushCache();
137
138
        return $this;
139
    }
140
141
    public function isSpecial()
142
    {
143
        return $this->id == 280 || $this->id == 288;
144
    }
145
146
    public function getId()
147
    {
148
        return $this->id;
149
    }
150
151
    public function getTupleMap()
152
    {
153
        $reverse = [];
154
        foreach ($this->getFormat() as $i => $field) {
155
            $reverse[$field['name']] = $i + 1;
156
        }
157
        return (object) $reverse;
158
    }
159
160
    public function getFormat()
161
    {
162
        if (!$this->format) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->format of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
163
            if ($this->isSpecial()) {
164
                $this->format = $this->mapper->getClient()
165
                    ->getSpace(280)->select([$this->id])->getData()[0][6];
166
            } else {
167
                $this->format = $this->mapper->findOne('_space', ['id' => $this->id])->format;
168
            }
169
            if (!$this->format) {
170
                $this->format = [];
171
            }
172
            $this->parseFormat();
173
        }
174
175
        return $this->format;
176
    }
177
178
    public function getMapper()
179
    {
180
        return $this->mapper;
181
    }
182
183
    public function getName()
184
    {
185
        return $this->name;
186
    }
187
188
    private function parseFormat()
189
    {
190
        $this->formatTypesHash = [];
191
        $this->formatNamesHash = [];
192
        $this->formatReferences = [];
193
        foreach ($this->format as $key => $row) {
194
            $this->formatTypesHash[$row['name']] = $row['type'];
195
            $this->formatNamesHash[$row['name']] = $key;
196
            if (array_key_exists('reference', $row)) {
197
                $this->formatReferences[$row['name']] = $row['reference'];
198
            }
199
        }
200
        return $this;
201
    }
202
203
    public function getReference($name)
204
    {
205
        return $this->isReference($name) ? $this->formatReferences[$name] : null;
206
    }
207
208
    public function isReference($name)
209
    {
210
        return array_key_exists($name, $this->formatReferences);
211
    }
212
213
    public function hasProperty($name)
214
    {
215
        $this->getFormat();
216
        return array_key_exists($name, $this->formatNamesHash);
217
    }
218
219
    public function getMeta()
220
    {
221
        $this->getFormat();
222
        $this->getIndexes();
223
224
        return [
225
            'formatNamesHash' => $this->formatNamesHash,
226
            'formatTypesHash' => $this->formatTypesHash,
227
            'indexes' => $this->indexes,
228
            'format' => $this->format,
229
        ];
230
    }
231
232
    public function getPropertyType($name)
233
    {
234
        if (!$this->hasProperty($name)) {
235
            throw new Exception("No property $name");
236
        }
237
        return $this->formatTypesHash[$name];
238
    }
239
240
    public function getPropertyIndex($name)
241
    {
242
        if (!$this->hasProperty($name)) {
243
            throw new Exception("No property $name");
244
        }
245
        return $this->formatNamesHash[$name];
246
    }
247
248
    public function getIndexes()
249
    {
250
        if (!$this->indexes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->indexes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
251
            if ($this->isSpecial()) {
252
                $this->indexes = [];
253
                $indexTuples = $this->mapper->getClient()->getSpace(288)->select([$this->id])->getData();
254
                $indexFormat = $this->mapper->getSchema()->getSpace(288)->getFormat();
255
                foreach ($indexTuples as $tuple) {
0 ignored issues
show
Bug introduced by
The expression $indexTuples 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...
256
                    $instance = [];
257
                    foreach ($indexFormat as $index => $format) {
258
                        $instance[$format['name']] = $tuple[$index];
259
                    }
260
                    $this->indexes[] = $instance;
261
                }
262
            } else {
263
                $indexes = $this->mapper->find('_index', ['id' => $this->id]);
264
                $this->indexes = [];
265
                foreach ($indexes as $index) {
266
                    $index = get_object_vars($index);
267
                    foreach ($index as $key => $value) {
268
                        if (is_object($value)) {
269
                            unset($index[$key]);
270
                        }
271
                    }
272
                    $this->indexes[] = $index;
273
                }
274
            }
275
        }
276
        return $this->indexes;
277
    }
278
279
    public function castIndex($params, $suppressException = false)
280
    {
281
        if (!count($this->getIndexes())) {
282
            return;
283
        }
284
        $keys = array_keys($params);
0 ignored issues
show
Unused Code introduced by
$keys is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
285
286
        $keys = [];
287
        foreach ($params as $name => $value) {
288
            $keys[] = $this->getPropertyIndex($name);
289
        }
290
291
        // equals
292
        foreach ($this->getIndexes() as $index) {
293
            $equals = false;
294
            if (count($keys) == count($index['parts'])) {
295
                // same length
296
                $equals = true;
297
                foreach ($index['parts'] as $part) {
298
                    $equals = $equals && in_array($part[0], $keys);
299
                }
300
            }
301
302
            if ($equals) {
303
                return $index['iid'];
304
            }
305
        }
306
307
        // index part
308
        foreach ($this->getIndexes() as $index) {
309
            $partial = [];
310
            foreach ($index['parts'] as $n => $part) {
311
                if (!array_key_exists($n, $keys)) {
312
                    break;
313
                }
314
                if ($keys[$n] != $part[0]) {
315
                    break;
316
                }
317
                $partial[] = $keys[$n];
318
            }
319
320
            if (count($partial) == count($keys)) {
321
                return $index['iid'];
322
            }
323
        }
324
325
        if (!$suppressException) {
326
            throw new Exception("No index");
327
        }
328
    }
329
330
    public function getIndexValues($indexId, $params)
331
    {
332
        $index = null;
333
        foreach ($this->getIndexes() as $candidate) {
334
            if ($candidate['iid'] == $indexId) {
335
                $index = $candidate;
336
                break;
337
            }
338
        }
339
        if (!$index) {
340
            throw new Exception("Undefined index: $indexId");
341
        }
342
343
        $format = $this->getFormat();
344
        $values = [];
345
        foreach ($index['parts'] as $part) {
346
            $name = $format[$part[0]]['name'];
347
            if (!array_key_exists($name, $params)) {
348
                break;
349
            }
350
            $values[] = $this->mapper->getSchema()->formatValue($part[1], $params[$name]);
351
        }
352
        return $values;
353
    }
354
355
    public function getPrimaryIndex()
356
    {
357
        $indexes = $this->getIndexes();
358
        if (!count($indexes)) {
359
            throw new Exception("No primary index");
360
        }
361
        return $indexes[0];
362
    }
363
364
    public function getTupleKey($tuple)
365
    {
366
        $key = [];
367
        foreach ($this->getPrimaryIndex()['parts'] as $part) {
368
            $key[] = $tuple[$part[0]];
369
        }
370
        return count($key) == 1 ? $key[0] : implode(':', $key);
371
    }
372
373
    public function getInstanceKey($instance)
374
    {
375
        $key = [];
376
377 View Code Duplication
        foreach ($this->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...
378
            $name = $this->getFormat()[$part[0]]['name'];
379
            if (!property_exists($instance, $name)) {
380
                throw new Exception("Field $name is undefined", 1);
381
            }
382
            $key[] = $instance->$name;
383
        }
384
385
        return count($key) == 1 ? $key[0] : implode(':', $key);
386
    }
387
388
    public function getRepository()
389
    {
390
        $class = Repository::class;
391
        foreach ($this->mapper->getPlugins() as $plugin) {
392
            $repositoryClass = $plugin->getRepositoryClass($this);
393
            if ($repositoryClass) {
394
                if ($class != Repository::class) {
395
                    throw new Exception('Repository class override');
396
                }
397
                $class = $repositoryClass;
398
            }
399
        }
400
        return $this->repository ?: $this->repository = new $class($this);
401
    }
402
403
    public function repositoryExists()
404
    {
405
        return !!$this->repository;
406
    }
407
}
408