Completed
Push — master ( bf7b4f...c8b0a5 )
by Dmitry
01:45
created

NestedSet   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 313
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 35
lcom 1
cbo 5
dl 0
loc 313
rs 9
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A addIndexes() 0 19 4
D beforeUpdate() 0 150 13
C beforeCreate() 0 58 9
A beforeRemove() 0 57 4
A isNested() 0 14 4
A resetNestedSpacesCache() 0 4 1
1
<?php
2
3
namespace Tarantool\Mapper\Plugin;
4
5
use Tarantool\Mapper\Entity;
6
use Tarantool\Mapper\Mapper;
7
use Tarantool\Mapper\Plugin;
8
use Tarantool\Mapper\Space;
9
10
class NestedSet extends Plugin
11
{
12
    private $keys = ['id', 'parent', 'group', 'depth', 'left', 'right'];
13
    private $nestedSpaces = [];
14
15
    public function addIndexes(Space $space)
16
    {
17
        $indexes = [
18
            ['id'],
19
            [
20
                'fields' => ['parent'],
21
                'unique' => false,
22
            ],
23
            ['group', 'left'],
24
            ['group', 'right'],
25
        ];
26
27
        foreach ($indexes as $index) {
28
            $fields = array_key_exists('fields', $index) ? $index['fields'] : $index;
29
            if ($space->castIndex(array_flip($fields), true) === null) {
30
                $space->createIndex($index);
31
            }
32
        }
33
    }
34
35
    public function beforeUpdate(Entity $entity, Space $space)
36
    {
37
        if ($this->isNested($space)) {
38
            $repository = $space->getRepository();
39
            $spaceName = $space->getName();
0 ignored issues
show
Unused Code introduced by
$spaceName 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...
40
41
            $parent = $repository->findOne($entity->parent);
0 ignored issues
show
Bug introduced by
The property parent does not seem to exist in Tarantool\Mapper\Entity.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
42
43
            $client = $this->mapper->getClient();
0 ignored issues
show
Unused Code introduced by
$client 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...
44
            $map = $space->getTupleMap();
45
46
            $old_entity = $repository->getOriginal($entity);
47
48
            foreach (['group'] as $field) {
49
                if ($old_entity[$map->$field-1] != $entity->$field) {
50
                    throw new \Exception(ucfirst($field)." can't be changed");
51
                }
52
            }
53
54
            if ($old_entity[$map->parent-1] != $entity->parent) {
55
                $leftValue = $entity->left;
0 ignored issues
show
Bug introduced by
The property left does not seem to exist in Tarantool\Mapper\Entity.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
56
                $rightValue = $entity->right;
0 ignored issues
show
Bug introduced by
The property right does not seem to exist in Tarantool\Mapper\Entity.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
57
                $toRoot = $entity->parent == 0 ? 1 : 0;
58
59
                $value = 0;
60
                $delta = 0;
61
                $depth = 0;
62
                if ($entity->parent) {
63
                    $value = $parent->right;
64
                    $depth = $parent->depth - $entity->depth + 1;
0 ignored issues
show
Bug introduced by
The property depth does not seem to exist in Tarantool\Mapper\Entity.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
65
                    $delta = $rightValue - $leftValue + 1;
66
                }
67
68
                $right_key_near = 0;
69
                if (!$entity->parent) {
70
                    foreach ($repository->find(['group' => $entity->group]) as $node) {
0 ignored issues
show
Bug introduced by
The property group does not seem to exist in Tarantool\Mapper\Entity.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
71
                        if (!$node->parent && $node->right > $right_key_near) {
72
                            $right_key_near = $node->right;
73
                        }
74
                    }
75
                }
76
                $skew_tree = $rightValue - $leftValue + 1;
77
                $skew_edit = $right_key_near - $leftValue + 1;
78
79
                $spaceName = $space->getName();
80
81
                if ($rightValue < $right_key_near) {
82
                    $skew_edit -= $skew_tree;
83
                }
84
85
                $result = $this->mapper->getClient()->evaluate("
86
                    local result = {}
87
                    local updates = {}
88
                    local maxRightTuple = box.space.$spaceName.index.group_right:max(right);
89
                    local maxLeftTuple = box.space.$spaceName.index.group_left:max(left);
90
                    local maxValue = 100
91
                    if maxRightTuple ~= nil then
92
                        maxValue = maxValue + maxRightTuple[$map->right]
93
                    end
94
                    if maxLeftTuple ~= nil then
95
                        maxValue = maxValue + maxLeftTuple[$map->left]
96
                    end
97
98
                    local leftValue = $entity->left
99
                    local rightValue = $entity->right
100
101
                    if $leftValue >= $value then
102
                        leftValue = leftValue + ($delta)
103
                        rightValue = rightValue + ($delta)
104
                    end
105
                    local left
106
                    local right
107
108
                    box.begin()
109
                    for i, node in box.space.$spaceName.index.group_right:pairs({{$entity->group}, 1}, 'ge') do
110
                        if node[$map->group] ~= $entity->group then
111
                            break
112
                        end
113
                        left = node[$map->left]
114
                        right = node[$map->right]
115
                        if $toRoot == 1 then
116
                            if left >= $leftValue and right <= $rightValue then
117
                                if node[$map->id] ~= $entity->id then
0 ignored issues
show
Bug introduced by
The property id does not seem to exist in Tarantool\Mapper\Entity.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
118
                                    table.insert(updates, {node[$map->id], $map->left, left + 1 - $leftValue})
119
                                    table.insert(updates, {node[$map->id], $map->right, right + 1 - $leftValue})
120
                                else
121
                                    table.insert(updates, {node[$map->id], $map->right, right + $skew_edit})
122
                                    table.insert(updates, {node[$map->id], $map->left, left + $skew_edit})
123
                                end
124
                                table.insert(updates, {node[$map->id], $map->depth, node[$map->depth] - $entity->depth})
125
                            end
126
                            if left >= $rightValue + 1 then
127
                                table.insert(updates, {node[$map->id], $map->left, left + $leftValue - $rightValue - 1})
128
                            end
129
                            if right >= $rightValue + 1 then
130
                                table.insert(updates, {node[$map->id], $map->right, right + $leftValue - $rightValue - 1})
131
                            end
132
                        else
133
                            if left >= $value then
134
                                left = left + ($delta)
135
                                table.insert(updates, {node[$map->id], $map->left, left})
136
                            end
137
                            if right >= $value then
138
                                right = right + ($delta)
139
                                table.insert(updates, {node[$map->id], $map->right, right})
140
                            end
141
142
                            if left >= leftValue and right <= rightValue then
143
                                table.insert(updates, {node[$map->id], $map->depth, node[$map->depth] + $depth})
144
                            end
145
146
                            if left >= leftValue and left <= rightValue then
147
                                left = left + $value - leftValue
148
                                table.insert(updates, {node[$map->id], $map->left, left})
149
                            end
150
                            if right >= leftValue and right <= rightValue then
151
                                right = right + $value - leftValue
152
                                table.insert(updates, {node[$map->id], $map->right, right})
153
                            end
154
                            if left >= rightValue + 1 then
155
                                left = left-($delta)
156
                                table.insert(updates, {node[$map->id], $map->left, left})
157
                            end
158
                            if right >= rightValue + 1 then
159
                                right = right-($delta)
160
                                table.insert(updates, {node[$map->id], $map->right, right})
161
                            end
162
                        end
163
                    end
164
                    for i, node in pairs(updates) do
165
                        box.space.$spaceName:update(node[1], {{'=', node[2], maxValue}})
166
                        maxValue = maxValue + 1
167
                    end
168
                    for i, node in pairs(updates) do
169
                        table.insert(result, node[1])
170
                        box.space.$spaceName:update(node[1], {{'=', node[2], node[3]}})
171
                    end
172
                    box.commit()
173
174
                    return result
175
                ")->getData();
176
177
                foreach (array_unique($result[0]) as $id) {
178
                    $space->getRepository()->sync($id, ['left', 'right', 'depth']);
179
                }
180
181
                $space->getRepository()->flushCache();
182
            }
183
        }
184
    }
185
186
    public function beforeCreate(Entity $entity, Space $space)
187
    {
188
        if (!$this->isNested($space)) {
189
            return;
190
        }
191
        $repository = $space->getRepository();
192
193
        if ($entity->parent) {
194
            $parent = $repository->findOne($entity->parent);
0 ignored issues
show
Bug introduced by
The property parent does not seem to exist in Tarantool\Mapper\Entity.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
195
            $entity->depth = $parent->depth + 1;
0 ignored issues
show
Bug introduced by
The property depth does not seem to exist in Tarantool\Mapper\Entity.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
196
197
            $updateLeft = [];
198
            $updateRight = [];
199
            foreach ($repository->find(['group' => $entity->group]) as $node) {
0 ignored issues
show
Bug introduced by
The property group does not seem to exist in Tarantool\Mapper\Entity.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
200
                if ($node->right >= $parent->right) {
201
                    if ($node->left > $parent->right) {
202
                        $updateLeft[$node->left] = $node;
203
                    }
204
                    $updateRight[$node->right] = $node;
205
                }
206
            }
207
208
            $entity->left = $parent->right;
0 ignored issues
show
Bug introduced by
The property left does not seem to exist in Tarantool\Mapper\Entity.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
209
            $entity->right = $entity->left + 1;
0 ignored issues
show
Bug introduced by
The property right does not seem to exist in Tarantool\Mapper\Entity.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
210
211
            krsort($updateRight);
212
            foreach ($updateRight as $node) {
213
                $node->right += 2;
214
                $node->save();
215
            }
216
217
            krsort($updateLeft);
218
            foreach ($updateLeft as $node) {
219
                $node->left += 2;
220
                $node->save();
221
            }
222
        } else {
223
            // new group
224
            $map = $space->getTupleMap();
225
            $spaceName = $space->getName();
226
227
            $entity->group = $entity->group ?: 0;
228
            $max = $this->mapper->getClient()->evaluate("
229
                local max = 0
230
                local group = $entity->group
231
                for i, n in box.space.$spaceName.index.group_right:pairs(group, {iterator = 'le'}) do
232
                    if n[$map->group] == group then
233
                        max = n[$map->right]
234
                    end
235
                    break
236
                end
237
                return max
238
            ")->getData()[0];
239
240
            $entity->left = $max + 1;
241
            $entity->right = $entity->left + 1;
242
        }
243
    }
244
245
    public function beforeRemove(Entity $instance, Space $space)
246
    {
247
        if (!$this->isNested($space)) {
248
            return;
249
        }
250
251
        $spaceName = $space->getName();
252
        $map = $space->getTupleMap();
253
254
        $result = $this->mapper->getClient()->evaluate("
255
            local removed_node = box.space.$spaceName:get($instance->id)
0 ignored issues
show
Bug introduced by
The property id does not seem to exist in Tarantool\Mapper\Entity.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
256
            local remove_list = {}
257
            local update_list = {}
258
            for i, current in box.space.$spaceName.index.group_left:pairs({removed_node[$map->group], removed_node[$map->left]}, 'gt') do
259
                if current[$map->group] ~= removed_node[$map->group] then
260
                    break
261
                end
262
                if current[$map->left] < removed_node[$map->right] then
263
                    table.insert(remove_list, current[$map->id])
264
                else
265
                    table.insert(update_list, current[$map->id])
266
                end
267
            end
268
269
            local delta = removed_node[$map->right] - removed_node[$map->left] + 1
270
271
            for i, id in ipairs(remove_list) do
272
                box.space.$spaceName:delete(id)
273
            end
274
275
            box.space.$spaceName:update($instance->id, {
276
                {'=', $map->left, 0},
277
                {'=', $map->right, 0},
278
            })
279
280
            for i, id in pairs(update_list) do
281
                box.space.$spaceName:update(id, {
282
                    {'-', $map->left, delta},
283
                    {'-', $map->right, delta}
284
                })
285
            end
286
287
            return remove_list, update_list, delta, removed_node
288
        ")->getData();
289
290
        // remove
291
        foreach ($result[0] as $id) {
292
            $space->getRepository()->forget($id);
293
        }
294
295
        // update
296
        foreach ($result[1] as $id) {
297
            $space->getRepository()->sync($id);
298
        }
299
300
        $space->getRepository()->flushCache();
301
    }
302
303
    public function isNested(Space $space, $force = false)
304
    {
305
        $spaceName = $space->getName();
306
        if ($force || !array_key_exists($spaceName, $this->nestedSpaces)) {
307
            $fields = [];
308
            foreach ($space->getFormat() as $field) {
309
                $fields[] = $field['name'];
310
            }
311
312
            $this->nestedSpaces[$spaceName] = !count(array_diff($this->keys, $fields));
313
        }
314
315
        return $this->nestedSpaces[$spaceName];
316
    }
317
318
    public function resetNestedSpacesCache()
319
    {
320
        $this->nestedSpaces = [];
321
    }
322
}
323