Completed
Push — master ( c8b0a5...59e33a )
by Dmitry
01:58
created

Space::getTupleKey()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 5
nc 4
nop 1
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
19
    private $repository;
20
21
    public function __construct(Mapper $mapper, $id, $name, $meta = null)
22
    {
23
        $this->mapper = $mapper;
24
        $this->id = $id;
25
        $this->name = $name;
26
27
        if ($meta) {
28
            foreach ($meta as $key => $value) {
29
                $this->$key = $value;
30
            }
31
        }
32
    }
33
34
    public function addProperties($config)
35
    {
36
        foreach ($config as $name => $type) {
37
            $this->addProperty($name, $type);
38
        }
39
        return $this;
40
    }
41
42
    public function addProperty($name, $type)
43
    {
44
        $format = $this->getFormat();
45
        foreach ($format as $field) {
46
            if ($field['name'] == $name) {
47
                throw new Exception("Property $name exists");
48
            }
49
        }
50
        $format[] = compact('name', 'type');
51
        $this->format = $format;
52
        $this->mapper->getClient()->evaluate("box.space[$this->id]:format(...)", [$format]);
53
54
        $this->parseFormat();
55
56
        return $this;
57
    }
58
59
    public function removeProperty($name)
60
    {
61
        $format = $this->getFormat();
62
        $last = array_pop($format);
63
        if ($last['name'] != $name) {
64
            throw new Exception("Remove only last property");
65
        }
66
        $this->mapper->getClient()->evaluate("box.space[$this->id]:format(...)", [$format]);
67
        $this->format = $format;
68
69
        $this->parseFormat();
70
71
        return $this;
72
    }
73
74
    public function removeIndex($name)
75
    {
76
        $this->mapper->getClient()->evaluate("box.space[$this->id].index.$name:drop()");
77
        $this->indexes = [];
78
        $this->mapper->getRepository('_index')->flushCache();
79
80
        return $this;
81
    }
82
83
    public function addIndex($config)
84
    {
85
        return $this->createIndex($config);
86
    }
87
88
    public function createIndex($config)
89
    {
90
        if (!is_array($config)) {
91
            $config = ['fields' => $config];
92
        }
93
94
95
        if (!array_key_exists('fields', $config)) {
96
            if (array_values($config) != $config) {
97
                throw new Exception("Invalid index configuration");
98
            }
99
            $config = [
100
                'fields' => $config
101
            ];
102
        }
103
104
        if (!is_array($config['fields'])) {
105
            $config['fields'] = [$config['fields']];
106
        }
107
108
        $options = [
109
            'parts' => []
110
        ];
111
112
        foreach ($config as $k => $v) {
113
            if ($k != 'name' && $k != 'fields') {
114
                $options[$k] = $v;
115
            }
116
        }
117
118
        foreach ($config['fields'] as $property) {
119
            if (!$this->getPropertyType($property)) {
120
                throw new Exception("Unknown property $property", 1);
121
            }
122
            $options['parts'][] = $this->getPropertyIndex($property)+1;
123
            $options['parts'][] = $this->getPropertyType($property);
124
        }
125
126
        $name = array_key_exists('name', $config) ? $config['name'] : implode('_', $config['fields']);
127
128
        $this->mapper->getClient()->evaluate("box.space[$this->id]:create_index('$name', ...)", [$options]);
129
        $this->indexes = [];
130
131
        $this->mapper->getSchema()->getSpace('_index')->getRepository()->flushCache();
132
133
        return $this;
134
    }
135
136
    public function isSpecial()
137
    {
138
        return $this->id == 280 || $this->id == 288;
139
    }
140
141
    public function getId()
142
    {
143
        return $this->id;
144
    }
145
146
    public function getTupleMap()
147
    {
148
        $reverse = [];
149
        foreach ($this->getFormat() as $i => $field) {
150
            $reverse[$field['name']] = $i + 1;
151
        }
152
        return (object) $reverse;
153
    }
154
155
    public function getFormat()
156
    {
157
        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...
158
            if ($this->isSpecial()) {
159
                $this->format = $this->mapper->getClient()
160
                    ->getSpace(280)->select([$this->id])->getData()[0][6];
161
            } else {
162
                $this->format = $this->mapper->findOne('_space', ['id' => $this->id])->format;
163
            }
164
            if (!$this->format) {
165
                $this->format = [];
166
            }
167
            $this->parseFormat();
168
        }
169
170
        return $this->format;
171
    }
172
173
    public function getMapper()
174
    {
175
        return $this->mapper;
176
    }
177
178
    public function getName()
179
    {
180
        return $this->name;
181
    }
182
183
    private function parseFormat()
184
    {
185
        $this->formatTypesHash = [];
186
        $this->formatNamesHash = [];
187
        foreach ($this->format as $key => $row) {
188
            $this->formatTypesHash[$row['name']] = $row['type'];
189
            $this->formatNamesHash[$row['name']] = $key;
190
        }
191
        return $this;
192
    }
193
194
    public function hasProperty($name)
195
    {
196
        $this->getFormat();
197
        return array_key_exists($name, $this->formatNamesHash);
198
    }
199
200
    public function getMeta()
201
    {
202
        return [
203
            'formatNamesHash' => $this->formatNamesHash,
204
            'formatTypesHash' => $this->formatTypesHash,
205
            'indexes' => $this->indexes,
206
            'format' => $this->format,
207
        ];
208
    }
209
210
    public function getPropertyType($name)
211
    {
212
        if (!$this->hasProperty($name)) {
213
            throw new Exception("No property $name");
214
        }
215
        return $this->formatTypesHash[$name];
216
    }
217
218
    public function getPropertyIndex($name)
219
    {
220
        if (!$this->hasProperty($name)) {
221
            throw new Exception("No property $name");
222
        }
223
        return $this->formatNamesHash[$name];
224
    }
225
226
    public function getIndexes()
227
    {
228
        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...
229
            if ($this->isSpecial()) {
230
                $this->indexes = [];
231
                $indexTuples = $this->mapper->getClient()->getSpace(288)->select([$this->id])->getData();
232
                $indexFormat = $this->mapper->getSchema()->getSpace(288)->getFormat();
233
                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...
234
                    $instance = (object) [];
235
                    foreach ($indexFormat as $index => $format) {
236
                        $instance->{$format['name']} = $tuple[$index];
237
                    }
238
                    $this->indexes[] = $instance;
239
                }
240
            } else {
241
                $this->indexes = $this->mapper->find('_index', ['id' => $this->id]);
242
            }
243
        }
244
        return $this->indexes;
245
    }
246
247
    public function castIndex($params, $suppressException = false)
248
    {
249
        if (!count($this->getIndexes())) {
250
            return;
251
        }
252
        $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...
253
254
        $keys = [];
255
        foreach ($params as $name => $value) {
256
            $keys[] = $this->getPropertyIndex($name);
257
        }
258
259
        // equals
260
        foreach ($this->getIndexes() as $index) {
261
            $equals = false;
262
            if (count($keys) == count($index->parts)) {
263
                // same length
264
                $equals = true;
265
                foreach ($index->parts as $part) {
266
                    $equals = $equals && in_array($part[0], $keys);
267
                }
268
            }
269
270
            if ($equals) {
271
                return $index->iid;
272
            }
273
        }
274
275
        // index part
276
        foreach ($this->getIndexes() as $index) {
277
            $partial = [];
278
            foreach ($index->parts as $n => $part) {
279
                if (!array_key_exists($n, $keys)) {
280
                    break;
281
                }
282
                if ($keys[$n] != $part[0]) {
283
                    break;
284
                }
285
                $partial[] = $keys[$n];
286
            }
287
288
            if (count($partial) == count($keys)) {
289
                return $index->iid;
290
            }
291
        }
292
293
        if (!$suppressException) {
294
            throw new Exception("No index");
295
        }
296
    }
297
298
    public function getIndexValues($indexId, $params)
299
    {
300
        $index = null;
301
        foreach ($this->getIndexes() as $candidate) {
302
            if ($candidate->iid == $indexId) {
303
                $index = $candidate;
304
                break;
305
            }
306
        }
307
        if (!$index) {
308
            throw new Exception("Undefined index: $indexId");
309
        }
310
311
        $format = $this->getFormat();
312
        $values = [];
313
        foreach ($index->parts as $part) {
314
            $name = $format[$part[0]]['name'];
315
            if (!array_key_exists($name, $params)) {
316
                break;
317
            }
318
            $values[] = $this->mapper->getSchema()->formatValue($part[1], $params[$name]);
319
        }
320
        return $values;
321
    }
322
323
    public function getPrimaryIndex()
324
    {
325
        $indexes = $this->getIndexes();
326
        if (!count($indexes)) {
327
            throw new Exception("No primary index");
328
        }
329
        return $indexes[0];
330
    }
331
332
    public function getTupleKey($tuple)
333
    {
334
        $key = [];
335
        foreach ($this->getPrimaryIndex()->parts as $part) {
336
            $key[] = $tuple[$part[0]];
337
        }
338
        return count($key) == 1 ? $key[0] : implode(':', $key);
339
    }
340
341
    public function getInstanceKey($instance)
342
    {
343
        $key = [];
344
345
        foreach ($this->getPrimaryIndex()->parts as $part) {
346
            $name = $this->getFormat()[$part[0]]['name'];
347
            if (!property_exists($instance, $name)) {
348
                throw new Exception("Field $name is undefined", 1);
349
            }
350
            $key[] = $instance->$name;
351
        }
352
353
        return count($key) == 1 ? $key[0] : implode(':', $key);
354
    }
355
356
    public function getRepository()
357
    {
358
        $class = Repository::class;
359
        foreach ($this->mapper->getPlugins() as $plugin) {
360
            $repositoryClass = $plugin->getRepositoryClass($this);
361
            if ($repositoryClass) {
362
                if ($class != Repository::class) {
363
                    throw new Exception('Repository class override');
364
                }
365
                $class = $repositoryClass;
366
            }
367
        }
368
        return $this->repository ?: $this->repository = new $class($this);
369
    }
370
371
    public function repositoryExists()
372
    {
373
        return !!$this->repository;
374
    }
375
}
376