Completed
Push — master ( 2061c1...107548 )
by Dmitry
04:04
created

Space::isSystem()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
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 $engine;
15
    private $format;
16
    private $indexes;
17
18
    private $formatNamesHash = [];
19
    private $formatTypesHash = [];
20
    private $formatReferences = [];
21
22
    private $repository;
23
24
    public function __construct(Mapper $mapper, $id, $name, $engine, $meta = null)
25
    {
26
        $this->mapper = $mapper;
27
        $this->id = $id;
28
        $this->name = $name;
29
        $this->engine = $engine;
30
31
        if ($meta) {
32
            foreach ($meta as $key => $value) {
33
                $this->$key = $value;
34
            }
35
        }
36
    }
37
38
    public function getEngine()
39
    {
40
        return $this->engine;
41
    }
42
43
    public function addProperties($config)
44
    {
45
        foreach ($config as $name => $type) {
46
            $this->addProperty($name, $type);
47
        }
48
        return $this;
49
    }
50
51
    public function addProperty($name, $type, $opts = [])
52
    {
53
        $format = $this->getFormat();
54
55
        if ($this->getProperty($name, false)) {
56
            throw new Exception("Property $name exists");
57
        }
58
59
        $row = array_merge(compact('name', 'type'), $opts);
60
        if (!array_key_exists('is_nullable', $row)) {
61
            $row['is_nullable'] = true;
62
        }
63
64
        if (array_key_exists('default', $row)) {
65
            $row['defaultValue'] = $row['default'];
66
            unset($row['default']);
67
        }
68
69
        $format[] = $row;
70
71
        return $this->setFormat($format);
72
    }
73
74
    public function hasDefaultValue($name)
75
    {
76
        return array_key_exists('defaultValue', $this->getProperty($name));
77
    }
78
79
    public function getDefaultValue($name)
80
    {
81
        return $this->getPropertyFlag($name, 'defaultValue');
82
    }
83
84
    public function isPropertyNullable($name)
85
    {
86
        return $this->getPropertyFlag($name, 'is_nullable');
87
    }
88
89
    public function setFormat($format)
90
    {
91
        $this->format = $format;
92
        $this->mapper->getClient()->evaluate("box.space[$this->id]:format(...)", [$format]);
93
        return $this->parseFormat();
94
    }
95
96
    public function setPropertyNullable($name, $nullable = true)
97
    {
98
        $format = $this->getFormat();
99
        foreach ($format as $i => $field) {
100
            if ($field['name'] == $name) {
101
                $format[$i]['is_nullable'] = $nullable;
102
            }
103
        }
104
105
        return $this->setFormat($format);
106
    }
107
108
    public function removeProperty($name)
109
    {
110
        $format = $this->getFormat();
111
        $last = array_pop($format);
112
        if ($last['name'] != $name) {
113
            throw new Exception("Remove only last property");
114
        }
115
116
        return $this->setFormat($format);
117
    }
118
119
    public function removeIndex($name)
120
    {
121
        $this->mapper->getClient()->evaluate("box.space[$this->id].index.$name:drop()");
122
        $this->indexes = [];
123
        $this->mapper->getRepository('_vindex')->flushCache();
124
125
        return $this;
126
    }
127
128
    public function addIndex($config)
129
    {
130
        return $this->createIndex($config);
131
    }
132
133
    public function createIndex($config)
134
    {
135
        if (!is_array($config)) {
136
            $config = ['fields' => $config];
137
        }
138
139
        if (!array_key_exists('fields', $config)) {
140
            if (array_values($config) != $config) {
141
                throw new Exception("Invalid index configuration");
142
            }
143
            $config = [
144
                'fields' => $config
145
            ];
146
        }
147
148
        if (!is_array($config['fields'])) {
149
            $config['fields'] = [$config['fields']];
150
        }
151
152
        $options = [
153
            'parts' => []
154
        ];
155
156
        foreach ($config as $k => $v) {
157
            if ($k != 'name' && $k != 'fields') {
158
                $options[$k] = $v;
159
            }
160
        }
161
162
        foreach ($config['fields'] as $property) {
163
            if (!$this->getPropertyType($property)) {
164
                throw new Exception("Unknown property $property", 1);
165
            }
166
            $options['parts'][] = $this->getPropertyIndex($property)+1;
167
            $options['parts'][] = $this->getPropertyType($property);
168
            $this->setPropertyNullable($property, false);
169
        }
170
171
        $name = array_key_exists('name', $config) ? $config['name'] : implode('_', $config['fields']);
172
173
        $this->mapper->getClient()->evaluate("box.space[$this->id]:create_index('$name', ...)", [$options]);
174
        $this->indexes = [];
175
176
        $this->mapper->getSchema()->getSpace('_vindex')->getRepository()->flushCache();
177
178
        return $this;
179
    }
180
181
    public function getIndex($id)
182
    {
183
        foreach ($this->getIndexes() as $index) {
184
            if ($index['iid'] == $id) {
185
                return $index;
186
            }
187
        }
188
189
        throw new Exception("Invalid index #$index");
190
    }
191
192
    public function getIndexType($id)
193
    {
194
        return $this->getIndex($id)['type'];
195
    }
196
197
    public function isSpecial()
198
    {
199
        return $this->id == ClientSpace::VSPACE || $this->id == ClientSpace::VINDEX;
200
    }
201
202
    public function isSystem()
203
    {
204
        return $this->id < 512;
205
    }
206
207
    public function getId()
208
    {
209
        return $this->id;
210
    }
211
212
    public function getTupleMap()
213
    {
214
        $reverse = [];
215
        foreach ($this->getFormat() as $i => $field) {
216
            $reverse[$field['name']] = $i + 1;
217
        }
218
        return (object) $reverse;
219
    }
220
221
    public function getFormat()
222
    {
223
        if (!$this->format) {
224
            if ($this->isSpecial()) {
225
                $this->format = $this->mapper->getClient()
226
                    ->getSpace(ClientSpace::VSPACE)->select([$this->id])->getData()[0][6];
227
            } else {
228
                $this->format = $this->mapper->findOne('_vspace', ['id' => $this->id])->format;
229
            }
230
            if (!$this->format) {
231
                $this->format = [];
232
            }
233
            $this->parseFormat();
234
        }
235
236
        return $this->format;
237
    }
238
239
    public function getMapper()
240
    {
241
        return $this->mapper;
242
    }
243
244
    public function getName()
245
    {
246
        return $this->name;
247
    }
248
249
    private function parseFormat()
250
    {
251
        $this->formatTypesHash = [];
252
        $this->formatNamesHash = [];
253
        $this->formatReferences = [];
254
        foreach ($this->format as $key => $row) {
255
            $name = $row['name'];
256
            $this->formatTypesHash[$name] = $row['type'];
257
            $this->formatNamesHash[$name] = $key;
258
            if (array_key_exists('reference', $row)) {
259
                $this->formatReferences[$name] = $row['reference'];
260
            }
261
        }
262
        return $this;
263
    }
264
265
    public function hasProperty($name)
266
    {
267
        $this->getFormat();
268
        return array_key_exists($name, $this->formatNamesHash);
269
    }
270
271
    public function getMeta()
272
    {
273
        $this->getFormat();
274
        $this->getIndexes();
275
276
        return [
277
            'formatNamesHash' => $this->formatNamesHash,
278
            'formatTypesHash' => $this->formatTypesHash,
279
            'formatReferences' => $this->formatReferences,
280
            'indexes' => $this->indexes,
281
            'format' => $this->format,
282
        ];
283
    }
284
285
    public function getProperty($name, $required = true)
286
    {
287
        foreach ($this->getFormat() as $field) {
288
            if ($field['name'] == $name) {
289
                return $field;
290
            }
291
        }
292
293
        if ($required) {
294
            throw new Exception("Invalid property $name");
295
        }
296
    }
297
298
    public function getPropertyFlag($name, $flag)
299
    {
300
        $property = $this->getProperty($name);
301
        if (array_key_exists($flag, $property)) {
302
            return $property[$flag];
303
        }
304
    }
305
306
    public function getPropertyType($name)
307
    {
308
        if (!$this->hasProperty($name)) {
309
            throw new Exception("No property $name");
310
        }
311
        return $this->formatTypesHash[$name];
312
    }
313
314
    public function getPropertyIndex($name)
315
    {
316
        if (!$this->hasProperty($name)) {
317
            throw new Exception("No property $name");
318
        }
319
        return $this->formatNamesHash[$name];
320
    }
321
322
    public function getReference($name)
323
    {
324
        return $this->isReference($name) ? $this->formatReferences[$name] : null;
325
    }
326
327
    public function isReference($name)
328
    {
329
        return array_key_exists($name, $this->formatReferences);
330
    }
331
332
    public function getIndexes()
333
    {
334
        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...
335
            if ($this->isSpecial()) {
336
                $this->indexes = [];
337
                $indexTuples = $this->mapper->getClient()->getSpace(ClientSpace::VINDEX)->select([$this->id])->getData();
338
                $indexFormat = $this->mapper->getSchema()->getSpace(ClientSpace::VINDEX)->getFormat();
339
                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...
340
                    $instance = [];
341
                    foreach ($indexFormat as $index => $format) {
342
                        $instance[$format['name']] = $tuple[$index];
343
                    }
344
                    $this->indexes[] = $instance;
345
                }
346
            } else {
347
                $indexes = $this->mapper->find('_vindex', ['id' => $this->id]);
348
                $this->indexes = [];
349
                foreach ($indexes as $index) {
350
                    $index = get_object_vars($index);
351
                    foreach ($index as $key => $value) {
352
                        if (is_object($value)) {
353
                            unset($index[$key]);
354
                        }
355
                    }
356
                    $this->indexes[] = $index;
357
                }
358
            }
359
        }
360
        return $this->indexes;
361
    }
362
363
    public function castIndex($params, $suppressException = false)
364
    {
365
        if (!count($this->getIndexes())) {
366
            return;
367
        }
368
        $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...
369
370
        $keys = [];
371
        foreach ($params as $name => $value) {
372
            $keys[] = $this->getPropertyIndex($name);
373
        }
374
375
        // equals
376
        foreach ($this->getIndexes() as $index) {
377
            $equals = false;
378
            if (count($keys) == count($index['parts'])) {
379
                // same length
380
                $equals = true;
381
                foreach ($index['parts'] as $part) {
382
                    $equals = $equals && in_array($part[0], $keys);
383
                }
384
            }
385
386
            if ($equals) {
387
                return $index['iid'];
388
            }
389
        }
390
391
        // index part
392
        foreach ($this->getIndexes() as $index) {
393
            $partial = [];
394
            foreach ($index['parts'] as $n => $part) {
395
                if (!array_key_exists($n, $keys)) {
396
                    break;
397
                }
398
                if ($keys[$n] != $part[0]) {
399
                    break;
400
                }
401
                $partial[] = $keys[$n];
402
            }
403
404
            if (count($partial) == count($keys)) {
405
                return $index['iid'];
406
            }
407
        }
408
409
        if (!$suppressException) {
410
            throw new Exception("No index on ".$this->name.' for ['.implode(', ', array_keys($params)).']');
411
        }
412
    }
413
414
    public function getIndexValues($indexId, $params)
415
    {
416
        $index = $this->getIndex($indexId);
417
        $format = $this->getFormat();
418
419
        $values = [];
420
        foreach ($index['parts'] as $part) {
421
            $name = $format[$part[0]]['name'];
422
            if (!array_key_exists($name, $params)) {
423
                break;
424
            }
425
            $value = $this->mapper->getSchema()->formatValue($part[1], $params[$name]);
426
            if (is_null($value) && !$this->isPropertyNullable($name)) {
427
                $value = $this->mapper->getSchema()->getDefaultValue($format[$part[0]]['type']);
428
            }
429
            $values[] = $value;
430
        }
431
        return $values;
432
    }
433
434
    public function getPrimaryIndex()
435
    {
436
        return $this->getIndex(0);
437
    }
438
439
    public function getTupleKey($tuple)
440
    {
441
        $key = [];
442
        foreach ($this->getPrimaryIndex()['parts'] as $part) {
443
            $key[] = $tuple[$part[0]];
444
        }
445
        return count($key) == 1 ? $key[0] : implode(':', $key);
446
    }
447
448
    public function getInstanceKey($instance)
449
    {
450
        $key = [];
451
452
        foreach ($this->getPrimaryIndex()['parts'] as $part) {
453
            $name = $this->getFormat()[$part[0]]['name'];
454
            if (!property_exists($instance, $name)) {
455
                throw new Exception("Field $name is undefined", 1);
456
            }
457
            $key[] = $instance->$name;
458
        }
459
460
        return count($key) == 1 ? $key[0] : implode(':', $key);
461
    }
462
463
    public function getRepository()
464
    {
465
        $class = Repository::class;
466
        foreach ($this->mapper->getPlugins() as $plugin) {
467
            $repositoryClass = $plugin->getRepositoryClass($this);
468
            if ($repositoryClass) {
469
                if ($class != Repository::class) {
470
                    throw new Exception('Repository class override');
471
                }
472
                $class = $repositoryClass;
473
            }
474
        }
475
        return $this->repository ?: $this->repository = new $class($this);
476
    }
477
478
    public function repositoryExists()
479
    {
480
        return !!$this->repository;
481
    }
482
}
483