Completed
Push — master ( ca5d19...c4bd73 )
by Dmitry
02:17
created

Space::hasDefaultValue()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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