Test Setup Failed
Push — dev ( 460265...df7ea0 )
by
unknown
02:33
created

NodeMoveAction   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 310
Duplicated Lines 3.23 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 29
c 2
b 0
f 0
lcom 1
cbo 5
dl 10
loc 310
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 6 1
B run() 0 22 4
C reorder() 0 77 9
D move() 10 137 10
A getLr() 0 11 1
A getCount() 0 9 1
A getChildIds() 0 9 1
A applyRootCondition() 0 6 2

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
namespace devgroup\JsTreeWidget\actions\nestedset;
4
5
use yii\base\Action;
6
use Yii;
7
use yii\db\ActiveRecord;
8
use yii\db\Expression;
9
use yii\web\Response;
10
11
class NodeMoveAction extends Action
12
{
13
    /** @var  ActiveRecord */
14
    public $className;
15
16
    public $rootAttribute = 'tree';
17
    public $leftAttribute = 'lft';
18
    public $rightAttribute = 'rgt';
19
    public $depthAttribute = 'depth';
20
21
    private $node;
22
    private $parent;
23
24
25
    public function init()
26
    {
27
        //check for move node as root
28
        //root reorder
29
        //maybe move root to another root
30
    }
31
32
    public function run()
33
    {
34
        Yii::$app->response->format = Response::FORMAT_JSON;
35
        $newParentId = Yii::$app->request->post('parent');
36
        $oldParentId = Yii::$app->request->post('old_parent');
37
        $position = Yii::$app->request->post('position');
38
        $oldPosition = Yii::$app->request->post('old_position');
39
        $nodeId = Yii::$app->request->post('node_id');
40
        $siblings = Yii::$app->request->post('siblings', []);
41
        $oldSiblings = Yii::$app->request->post('oldSiblings', []);
0 ignored issues
show
Unused Code introduced by
$oldSiblings 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...
42
        $class = $this->className;
43
        if ((null === $node = $class::findOne($nodeId)) || (null === $parent = $class::findOne($newParentId))) {
44
            return ['error' => 'Invalid data received'];
45
        }
46
        $this->node = $node;
47
        $this->parent = $parent;
48
        if ($newParentId == $oldParentId) {
49
            return $this->reorder($oldPosition, $position, $siblings);
50
        } else {
51
            return $this->move($position, $siblings, $oldParentId);
52
        }
53
    }
54
55
    public function reorder($oldPosition = null, $position = null, $siblings = [])
56
    {
57
        if (null === $oldPosition || null === $position || true === empty($siblings)) {
58
            return ['info' => 'nothing to change'];
59
        }
60
        $nodeId = $siblings[$position];
61
        $class = $this->className;
62
        $lr = $workWith = [];
0 ignored issues
show
Unused Code introduced by
$workWith 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...
Unused Code introduced by
$lr 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...
63
        $nodeOperator = $siblingsOperator = '';
0 ignored issues
show
Unused Code introduced by
$siblingsOperator 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...
Unused Code introduced by
$nodeOperator 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...
64
        if ($oldPosition > $position) {
65
            //change next
66
            $nodeOperator = '-';
67
            $siblingsOperator = '+';
68
            $workWith = array_slice($siblings, $position, $oldPosition - $position + 1);
69
        } else if ($oldPosition < $position) {
70
            //change previous
71
            $nodeOperator = '+';
72
            $siblingsOperator = '-';
73
            $workWith = array_slice($siblings, $oldPosition, $position - $oldPosition + 1);
74
        } else {
75
            return ['info' => 'nothing to change'];
76
        }
77
        if (true === empty($workWith)) {
78
            return ['info' => 'nothing to change'];
79
        }
80
        $lr = $workWithLr = $this->getLr($workWith);
81
        if (true === empty($lr)) {
82
            return ['info' => 'nothing to change'];
83
        }
84
        unset($workWithLr[$nodeId]);
85
        $lft = array_column($workWithLr, $this->leftAttribute);
86
        $lft = min($lft);
87
        $rgt = array_column($workWithLr, $this->rightAttribute);
88
        $rgt = max($rgt);
89
        $nodeCondition = [
90
            'and',
91
            ['>=', $this->leftAttribute, $lft],
92
            ['<=', $this->rightAttribute, $rgt]
93
        ];
94
        $this->applyRootCondition($nodeCondition);
95
        $nodeDelta = $this->getCount($nodeCondition);
96
        $nodeDelta *= 2;
97
        $siblingsCondition = [
98
            'and',
99
            ['>=', $this->leftAttribute, $lr[$nodeId][$this->leftAttribute]],
100
            ['<=', $this->rightAttribute, $lr[$nodeId][$this->rightAttribute]]
101
        ];
102
        $this->applyRootCondition($siblingsCondition);
103
        $nodeChildren = $this->getChildIds($siblingsCondition);
104
        $siblingsDelta = count($nodeChildren) * 2;
105
        $db = Yii::$app->getDb();
106
        $transaction = $db->beginTransaction();
107
        try {
108
            //updating necessary node siblings
109
            $db->createCommand()->update(
110
                $class::tableName(),
111
                [
112
                    $this->leftAttribute => new Expression($this->leftAttribute . sprintf('%s%d', $siblingsOperator, $siblingsDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 134 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
113
                    $this->rightAttribute => new Expression($this->rightAttribute . sprintf('%s%d', $siblingsOperator, $siblingsDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 136 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
114
                ],
115
                $nodeCondition
116
            )->execute();
117
            //updating node
118
            $db->createCommand()->update(
119
                $class::tableName(),
120
                [
121
                    $this->leftAttribute => new Expression($this->leftAttribute . sprintf('%s%d', $nodeOperator, $nodeDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 126 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
122
                    $this->rightAttribute => new Expression($this->rightAttribute . sprintf('%s%d', $nodeOperator, $nodeDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 128 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
123
                ],
124
                ['id' => $nodeChildren]
125
            )->execute();
126
            $transaction->commit();
127
        } catch (\Exception $e) {
128
            $transaction->rollBack();
129
            return ['error', $e->getMessage()];
130
        }
131
    }
132
133
    private function move($position = null, $siblings = [], $oldParentId)
0 ignored issues
show
Coding Style introduced by
Parameters which have default values should be placed at the end.

If you place a parameter with a default value before a parameter with a default value, the default value of the first parameter will never be used as it will always need to be passed anyway:

// $a must always be passed; it's default value is never used.
function someFunction($a = 5, $b) { }
Loading history...
134
    {
135
        $class = $this->className;
136
        if (null === $oldParent = $class::findOne($oldParentId)) {
137
            return ['error' => "Old parent with id '{$oldParentId}' not found"];
138
        }
139
        $nodeCountCondition = [
140
            'and',
141
            ['>=', $this->leftAttribute, $this->node{$this->leftAttribute}],
142
            ['<=', $this->rightAttribute, $this->node{$this->rightAttribute}]
143
        ];
144
        $this->applyRootCondition($nodeCountCondition);
145
        $nodeChildren = $this->getChildIds($nodeCountCondition);
146
        $siblingsDelta = count($nodeChildren) * 2;
147
        if ($position == 0) {
148
            $compareRight = $this->parent->{$this->leftAttribute} + 1;
149
        } else {
150
            if (false === isset($siblings[$position - 1])) {
151
                return ['error' => 'New previous sibling not exists'];
152
            }
153
            $newPrevSiblingId = $siblings[$position - 1];
154
            $newPrevSiblingData = $this->getLr($newPrevSiblingId);
155
            $compareRight = $newPrevSiblingData[$newPrevSiblingId][$this->rightAttribute];
156
        }
157
158
        if ($this->node->{$this->leftAttribute} > $compareRight) {
159
            //move node up
160 View Code Duplication
            if ($position == 0) {
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...
161
                $leftFrom = $this->parent->{$this->leftAttribute} + 1;
162
            } else {
163
                $leftFrom = $newPrevSiblingData[$newPrevSiblingId][$this->rightAttribute] + 1;
0 ignored issues
show
Bug introduced by
The variable $newPrevSiblingData does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $newPrevSiblingId does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
164
            }
165
            $rightTo = $this->node->{$this->leftAttribute};
166
            $nodeDelta = $this->node->{$this->leftAttribute} - $leftFrom;
167
            $nodeOperator = '-';
168
            $parentOperator = $siblingsOperator = '+';
169
            $newParentUpdateField = $this->rightAttribute;
170
            $oldParentUpdateField = $this->leftAttribute;
171
        } else if ($this->node->{$this->leftAttribute} < $compareRight) {
172
            //move node down
173
            $leftFrom = $this->node->{$this->rightAttribute};
174 View Code Duplication
            if ($position == 0) {
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...
175
                $rightTo = $this->parent->{$this->leftAttribute};
176
            } else {
177
                $rightTo = $newPrevSiblingData[$newPrevSiblingId][$this->rightAttribute];
178
            }
179
            $nodeOperator = '+';
180
            $parentOperator = $siblingsOperator = '-';
181
            $nodeDelta = $rightTo - $siblingsDelta + 1 - $this->node->{$this->leftAttribute};
182
            $newParentUpdateField = $this->leftAttribute;
183
            $oldParentUpdateField = $this->rightAttribute;
184
        } else {
185
            return ['error' => 'There are two nodes with same "left" value. This should not be.'];
186
        }
187
        $siblingsCondition = [
188
            'and',
189
            ['>=', $this->leftAttribute, $leftFrom],
190
            ['<=', $this->rightAttribute, $rightTo]
191
        ];
192
        $this->applyRootCondition($siblingsCondition);
193
        $db = Yii::$app->getDb();
194
        $transaction = $db->beginTransaction();
195
        $oldParentDepth = $oldParent->{$this->depthAttribute};
196
        $newParentDepth = $this->parent->{$this->depthAttribute};
197
        if ($newParentDepth < $oldParentDepth) {
198
            $depthOperator = '-';
199
            $depthDelta = $oldParentDepth - $newParentDepth;
200
        } else {
201
            $depthOperator = '+';
202
            $depthDelta = $newParentDepth - $oldParentDepth;
203
        }
204
        $commonParentsCondition = [
205
            'and',
206
            ['<', $this->leftAttribute, $leftFrom],
207
            ['>', $this->rightAttribute, $rightTo]
208
        ];
209
        $this->applyRootCondition($commonParentsCondition);
210
        $commonParentsIds = $class::find()->select('id')->where($commonParentsCondition)->column();
211
        $commonCondition = [
212
            ['!=', $this->depthAttribute, 0],
213
            ['not in', 'id', $commonParentsIds],
214
        ];
215
        $this->applyRootCondition($commonCondition);
216
        $newParentCondition = array_merge([
217
            'and',
218
            ['<=', $this->leftAttribute, $this->parent->{$this->leftAttribute}],
219
            ['>=', $this->rightAttribute, $this->parent->{$this->rightAttribute}],
220
        ], $commonCondition);
221
        $oldParentsCondition = array_merge([
222
            'and',
223
            ['<', $this->leftAttribute, $this->node->{$this->leftAttribute}],
224
            ['>', $this->rightAttribute, $this->node->{$this->rightAttribute}],
225
        ], $commonCondition);
226
        try {
227
            //updating necessary node siblings
228
            $db->createCommand()->update(
229
                $class::tableName(),
230
                [
231
                    $this->leftAttribute => new Expression($this->leftAttribute . sprintf('%s%d', $siblingsOperator, $siblingsDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 134 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
232
                    $this->rightAttribute => new Expression($this->rightAttribute . sprintf('%s%d', $siblingsOperator, $siblingsDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 136 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
233
                ],
234
                $siblingsCondition
235
            )->execute();
236
            //updating old parents
237
            $db->createCommand()->update(
238
                $class::tableName(),
239
                [
240
                    //down - right
241
                    $oldParentUpdateField => new Expression($oldParentUpdateField . sprintf('%s%d', $parentOperator, $siblingsDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 134 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
242
                ],
243
                $oldParentsCondition
244
            )->execute();
245
            //updating new parents
246
            $db->createCommand()->update(
247
                $class::tableName(),
248
                [
249
                    //down - left
250
                    $newParentUpdateField => new Expression($newParentUpdateField . sprintf('%s%d', $parentOperator, $siblingsDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 134 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
251
                ],
252
                $newParentCondition
253
            )->execute();
254
            //updating node with children
255
            $db->createCommand()->update(
256
                $class::tableName(),
257
                [
258
                    $this->leftAttribute => new Expression($this->leftAttribute . sprintf('%s%d', $nodeOperator, $nodeDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 126 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
259
                    $this->rightAttribute => new Expression($this->rightAttribute . sprintf('%s%d', $nodeOperator, $nodeDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 128 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
260
                    $this->depthAttribute => new Expression($this->depthAttribute . sprintf('%s%d', $depthOperator, $depthDelta)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 130 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
261
                ],
262
                ['id' => $nodeChildren]
263
            )->execute();
264
            $transaction->commit();
265
        } catch (\Exception $e) {
266
            $transaction->rollBack();
267
            return ['error', $e->getMessage()];
268
        }
269
    }
270
271
    /**
272
     * Returns field set of rows to be modified while reordering
273
     *
274
     * @param array $ids
275
     * @return array|\yii\db\ActiveRecord[]
276
     */
277
    private function getLr($ids)
278
    {
279
        $class = $this->className;
280
        $lr = $class::find()
281
            ->select(['id', $this->leftAttribute, $this->rightAttribute])
282
            ->where(['id' => $ids])
283
            ->indexBy('id')
284
            ->asArray(true)
285
            ->all();
286
        return $lr;
287
    }
288
289
    /**
290
     * Returns count of records to be modified while reordering
291
     * @param array $condition
292
     * @return int|string
293
     */
294
    public function getCount($condition)
295
    {
296
        $class = $this->className;
297
        $count = $class::find()
298
            ->select(['id', $this->leftAttribute, $this->rightAttribute, $this->rootAttribute])
299
            ->where($condition)
300
            ->count();
301
        return $count;
302
    }
303
304
    private function getChildIds($condition)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
305
    {
306
        $class = $this->className;
307
        $count = $class::find()
308
            ->select('id')
309
            ->where($condition)
310
            ->column();
311
        return $count;
312
    }
313
314
    private function applyRootCondition(&$condition)
315
    {
316
        if (false !== $this->rootAttribute) {
317
            $condition[] = [$this->rootAttribute => $this->node->{$this->rootAttribute}];
318
        }
319
    }
320
}