Completed
Push — master ( 120b64...0f0d70 )
by Dmitry
02:02
created

Space::getIndexes()   C

Complexity

Conditions 8
Paths 3

Size

Total Lines 30
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 30
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 21
nc 3
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
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 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...
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
        $this->getFormat();
203
        $this->getIndexes();
204
205
        return [
206
            'formatNamesHash' => $this->formatNamesHash,
207
            'formatTypesHash' => $this->formatTypesHash,
208
            'indexes' => $this->indexes,
209
            'format' => $this->format,
210
        ];
211
    }
212
213
    public function getPropertyType($name)
214
    {
215
        if (!$this->hasProperty($name)) {
216
            throw new Exception("No property $name");
217
        }
218
        return $this->formatTypesHash[$name];
219
    }
220
221
    public function getPropertyIndex($name)
222
    {
223
        if (!$this->hasProperty($name)) {
224
            throw new Exception("No property $name");
225
        }
226
        return $this->formatNamesHash[$name];
227
    }
228
229
    public function getIndexes()
230
    {
231
        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...
232
            if ($this->isSpecial()) {
233
                $this->indexes = [];
234
                $indexTuples = $this->mapper->getClient()->getSpace(288)->select([$this->id])->getData();
235
                $indexFormat = $this->mapper->getSchema()->getSpace(288)->getFormat();
236
                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...
237
                    $instance = [];
238
                    foreach ($indexFormat as $index => $format) {
239
                        $instance[$format['name']] = $tuple[$index];
240
                    }
241
                    $this->indexes[] = $instance;
242
                }
243
            } else {
244
                $indexes = $this->mapper->find('_index', ['id' => $this->id]);
245
                $this->indexes = [];
246
                foreach ($indexes as $index) {
247
                    $index = get_object_vars($index);
248
                    foreach ($index as $key => $value) {
249
                        if (is_object($value)) {
250
                            unset($index[$key]);
251
                        }
252
                    }
253
                    $this->indexes[] = $index;
254
                }
255
            }
256
        }
257
        return $this->indexes;
258
    }
259
260
    public function castIndex($params, $suppressException = false)
261
    {
262
        if (!count($this->getIndexes())) {
263
            return;
264
        }
265
        $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...
266
267
        $keys = [];
268
        foreach ($params as $name => $value) {
269
            $keys[] = $this->getPropertyIndex($name);
270
        }
271
272
        // equals
273
        foreach ($this->getIndexes() as $index) {
274
            $equals = false;
275
            if (count($keys) == count($index['parts'])) {
276
                // same length
277
                $equals = true;
278
                foreach ($index['parts'] as $part) {
279
                    $equals = $equals && in_array($part[0], $keys);
280
                }
281
            }
282
283
            if ($equals) {
284
                return $index['iid'];
285
            }
286
        }
287
288
        // index part
289
        foreach ($this->getIndexes() as $index) {
290
            $partial = [];
291
            foreach ($index['parts'] as $n => $part) {
292
                if (!array_key_exists($n, $keys)) {
293
                    break;
294
                }
295
                if ($keys[$n] != $part[0]) {
296
                    break;
297
                }
298
                $partial[] = $keys[$n];
299
            }
300
301
            if (count($partial) == count($keys)) {
302
                return $index['iid'];
303
            }
304
        }
305
306
        if (!$suppressException) {
307
            throw new Exception("No index");
308
        }
309
    }
310
311
    public function getIndexValues($indexId, $params)
312
    {
313
        $index = null;
314
        foreach ($this->getIndexes() as $candidate) {
315
            if ($candidate['iid'] == $indexId) {
316
                $index = $candidate;
317
                break;
318
            }
319
        }
320
        if (!$index) {
321
            throw new Exception("Undefined index: $indexId");
322
        }
323
324
        $format = $this->getFormat();
325
        $values = [];
326
        foreach ($index['parts'] as $part) {
327
            $name = $format[$part[0]]['name'];
328
            if (!array_key_exists($name, $params)) {
329
                break;
330
            }
331
            $values[] = $this->mapper->getSchema()->formatValue($part[1], $params[$name]);
332
        }
333
        return $values;
334
    }
335
336
    public function getPrimaryIndex()
337
    {
338
        $indexes = $this->getIndexes();
339
        if (!count($indexes)) {
340
            throw new Exception("No primary index");
341
        }
342
        return $indexes[0];
343
    }
344
345
    public function getTupleKey($tuple)
346
    {
347
        $key = [];
348
        foreach ($this->getPrimaryIndex()['parts'] as $part) {
349
            $key[] = $tuple[$part[0]];
350
        }
351
        return count($key) == 1 ? $key[0] : implode(':', $key);
352
    }
353
354
    public function getInstanceKey($instance)
355
    {
356
        $key = [];
357
358 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...
359
            $name = $this->getFormat()[$part[0]]['name'];
360
            if (!property_exists($instance, $name)) {
361
                throw new Exception("Field $name is undefined", 1);
362
            }
363
            $key[] = $instance->$name;
364
        }
365
366
        return count($key) == 1 ? $key[0] : implode(':', $key);
367
    }
368
369
    public function getRepository()
370
    {
371
        $class = Repository::class;
372
        foreach ($this->mapper->getPlugins() as $plugin) {
373
            $repositoryClass = $plugin->getRepositoryClass($this);
374
            if ($repositoryClass) {
375
                if ($class != Repository::class) {
376
                    throw new Exception('Repository class override');
377
                }
378
                $class = $repositoryClass;
379
            }
380
        }
381
        return $this->repository ?: $this->repository = new $class($this);
382
    }
383
384
    public function repositoryExists()
385
    {
386
        return !!$this->repository;
387
    }
388
}
389