Completed
Push — master ( 519b66...9c519b )
by Dmitry
04:43
created

Space::createIndex()   D

Complexity

Conditions 11
Paths 122

Size

Total Lines 48
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 48
rs 4.9629
cc 11
eloc 26
nc 122
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Tarantool\Mapper;
4
5
use Exception;
6
use Tarantool\Client\Schema\Space as ClientSpace;
7
8
class Space
9
{
10
    private $mapper;
11
12
    private $id;
13
    private $name;
14
    private $format;
15
    private $indexes;
16
17
    private $formatNamesHash = [];
18
    private $formatTypesHash = [];
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, $is_nullable = true)
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
        $format[] = compact('name', 'type', 'is_nullable');
52
        $this->format = $format;
53
        $this->mapper->getClient()->evaluate("box.space[$this->id]:format(...)", [$format]);
54
55
        $this->parseFormat();
56
57
        return $this;
58
    }
59
60
    public function isPropertyNullable($name)
61
    {
62
        foreach($this->getFormat() as $field) {
63
            if ($field['name'] == $name) {
64
                return array_key_exists('is_nullable', $field) ? $field['is_nullable'] : false;
65
            }
66
        }
67
    }
68
69
    public function setPropertyNullable($name, $nullable = true)
70
    {
71
        $format = $this->getFormat();
72
        foreach ($format as $i => $field) {
73
            if ($field['name'] == $name) {
74
                $format[$i]['is_nullable'] = $nullable;
75
            }
76
        }
77
        $this->format = $format;
78
        $this->mapper->getClient()->evaluate("box.space[$this->id]:format(...)", [$format]);
79
80
        $this->parseFormat();
81
82
        return $this;
83
84
    }
85
86
    public function removeProperty($name)
87
    {
88
        $format = $this->getFormat();
89
        $last = array_pop($format);
90
        if ($last['name'] != $name) {
91
            throw new Exception("Remove only last property");
92
        }
93
        $this->mapper->getClient()->evaluate("box.space[$this->id]:format(...)", [$format]);
94
        $this->format = $format;
95
96
        $this->parseFormat();
97
98
        return $this;
99
    }
100
101
    public function removeIndex($name)
102
    {
103
        $this->mapper->getClient()->evaluate("box.space[$this->id].index.$name:drop()");
104
        $this->indexes = [];
105
        $this->mapper->getRepository('_vindex')->flushCache();
106
107
        return $this;
108
    }
109
110
    public function addIndex($config)
111
    {
112
        return $this->createIndex($config);
113
    }
114
115
    public function createIndex($config)
116
    {
117
        if (!is_array($config)) {
118
            $config = ['fields' => $config];
119
        }
120
121
122
        if (!array_key_exists('fields', $config)) {
123
            if (array_values($config) != $config) {
124
                throw new Exception("Invalid index configuration");
125
            }
126
            $config = [
127
                'fields' => $config
128
            ];
129
        }
130
131
        if (!is_array($config['fields'])) {
132
            $config['fields'] = [$config['fields']];
133
        }
134
135
        $options = [
136
            'parts' => []
137
        ];
138
139
        foreach ($config as $k => $v) {
140
            if ($k != 'name' && $k != 'fields') {
141
                $options[$k] = $v;
142
            }
143
        }
144
145
        foreach ($config['fields'] as $property) {
146
            if (!$this->getPropertyType($property)) {
147
                throw new Exception("Unknown property $property", 1);
148
            }
149
            $options['parts'][] = $this->getPropertyIndex($property)+1;
150
            $options['parts'][] = $this->getPropertyType($property);
151
            $this->setPropertyNullable($property, false);
152
        }
153
154
        $name = array_key_exists('name', $config) ? $config['name'] : implode('_', $config['fields']);
155
156
        $this->mapper->getClient()->evaluate("box.space[$this->id]:create_index('$name', ...)", [$options]);
157
        $this->indexes = [];
158
159
        $this->mapper->getSchema()->getSpace('_vindex')->getRepository()->flushCache();
160
161
        return $this;
162
    }
163
164
    public function getIndexType($id)
165
    {
166
        foreach ($this->getIndexes() as $index) {
167
            if ($index['iid'] == $id) {
168
                return $index['type'];
169
            }
170
        }
171
172
        throw new Exception("Invalid index #$index");
173
    }
174
175
    public function isSpecial()
176
    {
177
        return $this->id == ClientSpace::VSPACE || $this->id == ClientSpace::VINDEX;
178
    }
179
180
    public function getId()
181
    {
182
        return $this->id;
183
    }
184
185
    public function getTupleMap()
186
    {
187
        $reverse = [];
188
        foreach ($this->getFormat() as $i => $field) {
189
            $reverse[$field['name']] = $i + 1;
190
        }
191
        return (object) $reverse;
192
    }
193
194
    public function getFormat()
195
    {
196
        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...
197
            if ($this->isSpecial()) {
198
                $this->format = $this->mapper->getClient()
199
                    ->getSpace(ClientSpace::VSPACE)->select([$this->id])->getData()[0][6];
200
            } else {
201
                $this->format = $this->mapper->findOne('_vspace', ['id' => $this->id])->format;
202
            }
203
            if (!$this->format) {
204
                $this->format = [];
205
            }
206
            $this->parseFormat();
207
        }
208
209
        return $this->format;
210
    }
211
212
    public function getMapper()
213
    {
214
        return $this->mapper;
215
    }
216
217
    public function getName()
218
    {
219
        return $this->name;
220
    }
221
222
    private function parseFormat()
223
    {
224
        $this->formatTypesHash = [];
225
        $this->formatNamesHash = [];
226
        foreach ($this->format as $key => $row) {
227
            $this->formatTypesHash[$row['name']] = $row['type'];
228
            $this->formatNamesHash[$row['name']] = $key;
229
        }
230
        return $this;
231
    }
232
233
    public function hasProperty($name)
234
    {
235
        $this->getFormat();
236
        return array_key_exists($name, $this->formatNamesHash);
237
    }
238
239
    public function getMeta()
240
    {
241
        $this->getFormat();
242
        $this->getIndexes();
243
244
        return [
245
            'formatNamesHash' => $this->formatNamesHash,
246
            'formatTypesHash' => $this->formatTypesHash,
247
            'indexes' => $this->indexes,
248
            'format' => $this->format,
249
        ];
250
    }
251
252
    public function getPropertyType($name)
253
    {
254
        if (!$this->hasProperty($name)) {
255
            throw new Exception("No property $name");
256
        }
257
        return $this->formatTypesHash[$name];
258
    }
259
260
    public function getPropertyIndex($name)
261
    {
262
        if (!$this->hasProperty($name)) {
263
            throw new Exception("No property $name");
264
        }
265
        return $this->formatNamesHash[$name];
266
    }
267
268
    public function getIndexes()
269
    {
270
        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...
271
            if ($this->isSpecial()) {
272
                $this->indexes = [];
273
                $indexTuples = $this->mapper->getClient()->getSpace(ClientSpace::VINDEX)->select([$this->id])->getData();
274
                $indexFormat = $this->mapper->getSchema()->getSpace(ClientSpace::VINDEX)->getFormat();
275
                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...
276
                    $instance = [];
277
                    foreach ($indexFormat as $index => $format) {
278
                        $instance[$format['name']] = $tuple[$index];
279
                    }
280
                    $this->indexes[] = $instance;
281
                }
282
            } else {
283
                $indexes = $this->mapper->find('_vindex', ['id' => $this->id]);
284
                $this->indexes = [];
285
                foreach ($indexes as $index) {
286
                    $index = get_object_vars($index);
287
                    foreach ($index as $key => $value) {
288
                        if (is_object($value)) {
289
                            unset($index[$key]);
290
                        }
291
                    }
292
                    $this->indexes[] = $index;
293
                }
294
            }
295
        }
296
        return $this->indexes;
297
    }
298
299
    public function castIndex($params, $suppressException = false)
300
    {
301
        if (!count($this->getIndexes())) {
302
            return;
303
        }
304
        $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...
305
306
        $keys = [];
307
        foreach ($params as $name => $value) {
308
            $keys[] = $this->getPropertyIndex($name);
309
        }
310
311
        // equals
312
        foreach ($this->getIndexes() as $index) {
313
            $equals = false;
314
            if (count($keys) == count($index['parts'])) {
315
                // same length
316
                $equals = true;
317
                foreach ($index['parts'] as $part) {
318
                    $equals = $equals && in_array($part[0], $keys);
319
                }
320
            }
321
322
            if ($equals) {
323
                return $index['iid'];
324
            }
325
        }
326
327
        // index part
328
        foreach ($this->getIndexes() as $index) {
329
            $partial = [];
330
            foreach ($index['parts'] as $n => $part) {
331
                if (!array_key_exists($n, $keys)) {
332
                    break;
333
                }
334
                if ($keys[$n] != $part[0]) {
335
                    break;
336
                }
337
                $partial[] = $keys[$n];
338
            }
339
340
            if (count($partial) == count($keys)) {
341
                return $index['iid'];
342
            }
343
        }
344
345
        if (!$suppressException) {
346
            throw new Exception("No index");
347
        }
348
    }
349
350
    public function getIndexValues($indexId, $params)
351
    {
352
        $index = null;
353
        foreach ($this->getIndexes() as $candidate) {
354
            if ($candidate['iid'] == $indexId) {
355
                $index = $candidate;
356
                break;
357
            }
358
        }
359
        if (!$index) {
360
            throw new Exception("Undefined index: $indexId");
361
        }
362
363
        $format = $this->getFormat();
364
        $values = [];
365
        foreach ($index['parts'] as $part) {
366
            $name = $format[$part[0]]['name'];
367
            if (!array_key_exists($name, $params)) {
368
                break;
369
            }
370
            $value = $this->mapper->getSchema()->formatValue($part[1], $params[$name]);
371
            if(is_null($value) && !$this->isPropertyNullable($name)) {
372
                $value = $this->mapper->getSchema()->getDefaultValue($format[$part[0]]['type']);
373
            }
374
            $values[] = $value;
375
        }
376
        return $values;
377
    }
378
379
    public function getPrimaryIndex()
380
    {
381
        $indexes = $this->getIndexes();
382
        if (!count($indexes)) {
383
            throw new Exception("No primary index");
384
        }
385
        return $indexes[0];
386
    }
387
388
    public function getTupleKey($tuple)
389
    {
390
        $key = [];
391
        foreach ($this->getPrimaryIndex()['parts'] as $part) {
392
            $key[] = $tuple[$part[0]];
393
        }
394
        return count($key) == 1 ? $key[0] : implode(':', $key);
395
    }
396
397
    public function getInstanceKey($instance)
398
    {
399
        $key = [];
400
401
        foreach ($this->getPrimaryIndex()['parts'] as $part) {
402
            $name = $this->getFormat()[$part[0]]['name'];
403
            if (!property_exists($instance, $name)) {
404
                throw new Exception("Field $name is undefined", 1);
405
            }
406
            $key[] = $instance->$name;
407
        }
408
409
        return count($key) == 1 ? $key[0] : implode(':', $key);
410
    }
411
412
    public function getRepository()
413
    {
414
        $class = Repository::class;
415
        foreach ($this->mapper->getPlugins() as $plugin) {
416
            $repositoryClass = $plugin->getRepositoryClass($this);
417
            if ($repositoryClass) {
418
                if ($class != Repository::class) {
419
                    throw new Exception('Repository class override');
420
                }
421
                $class = $repositoryClass;
422
            }
423
        }
424
        return $this->repository ?: $this->repository = new $class($this);
425
    }
426
427
    public function repositoryExists()
428
    {
429
        return !!$this->repository;
430
    }
431
}
432