Completed
Pull Request — master (#1)
by ARCANEDEV
04:12
created

TreeHelper::rebuildDictionary()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 31
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 31
ccs 18
cts 18
cp 1
rs 8.439
cc 5
eloc 20
nc 6
nop 5
crap 5
1
<?php namespace Arcanedev\LaravelNestedSet\Utilities;
2
3
use Arcanedev\LaravelNestedSet\Contracts\Nodeable;
4
use Arcanedev\LaravelNestedSet\NodeTrait;
5
use Illuminate\Database\Eloquent\ModelNotFoundException;
6
7
/**
8
 * Class     TreeHelper
9
 *
10
 * @package  Arcanedev\LaravelNestedSet\Utilities
11
 * @author   ARCANEDEV <[email protected]>
12
 */
13
class TreeHelper
14
{
15
    /* ------------------------------------------------------------------------------------------------
16
     |  Main Functions
17
     | ------------------------------------------------------------------------------------------------
18
     */
19
    /**
20
     * @param  array                                           $data
21
     * @param  array                                           $existing
22
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $model
23
     * @param  bool                                            $delete
24
     *
25
     * @return int
26
     */
27 12
    public static function rebuild(array $data, $existing, Nodeable $model, $delete = false)
28
    {
29 12
        $dictionary = [];
30
31 12
        self::rebuildDictionary($dictionary, $data, $existing, $model);
32
33 8
        if ( ! empty($existing)) {
34 8
            if ($delete) {
35 4
                $model->newScopedQuery()
36 4
                    ->whereIn($model->getKeyName(), array_keys($existing))
37 4
                    ->forceDelete();
38 3
            }
39
            else {
40 4
                foreach ($existing as $node) {
41
                    /** @var \Arcanedev\LaravelNestedSet\Contracts\Nodeable $node */
42 4
                    $dictionary[$node->getParentId()][] = $node;
43 3
                }
44
            }
45 6
        }
46
47 8
        return self::fixNodes($dictionary);
48
    }
49
50
    /**
51
     * Fix nodes.
52
     *
53
     * @param  array  $dictionary
54
     *
55
     * @return int
56
     */
57 12
    public static function fixNodes(array &$dictionary)
58
    {
59 12
        $fixed = 0;
60 12
        $cut   = self::reorderNodes($dictionary, $fixed);
61
62
        // Save nodes that have invalid parent as roots
63 12
        while ( ! empty($dictionary)) {
64 4
            $dictionary[null] = reset($dictionary);
65 4
            unset($dictionary[key($dictionary)]);
66
67 4
            $cut = self::reorderNodes($dictionary, $fixed, null, $cut);
68 3
        }
69
70 12
        return $fixed;
71
    }
72
73
    /* ------------------------------------------------------------------------------------------------
74
     |  Other Functions
75
     | ------------------------------------------------------------------------------------------------
76
     */
77
    /**
78
     * Rebuild the dictionary.
79
     *
80
     * @param  array                                           $dictionary
81
     * @param  array                                           $data
82
     * @param  array                                           $existing
83
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $model
84
     * @param  mixed                                           $parentId
85
     *
86
     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
87
     */
88 12
    protected static function rebuildDictionary(
89
        array &$dictionary,
90
        array $data,
91
        array &$existing,
92
        Nodeable &$model,
93
        $parentId = null
94
    ) {
95 12
        $keyName = $model->getKeyName();
96
97 12
        foreach ($data as $itemData) {
98 12
            if ( ! isset($itemData[$keyName])) {
99 8
                $node = $model->newInstance();
100
                // We will save it as raw node since tree will be fixed
101 8
                $node->rawNode(0, 0, $parentId);
102 6
            }
103 8
            elseif ( ! isset($existing[$key = $itemData[$keyName]])) {
104 4
                throw new ModelNotFoundException;
105
            }
106
            else {
107 4
                $node = $existing[$key];
108 4
                unset($existing[$key]);
109
            }
110
111 8
            $node->fill($itemData)->save();
112 8
            $dictionary[$parentId][] = $node;
113
114 8
            if (isset($itemData['children'])) {
115 5
                self::rebuildDictionary($dictionary, $itemData['children'], $existing, $model, $node->getKey());
116 3
            }
117 6
        }
118 8
    }
119
    /**
120
     * Reorder nodes.
121
     *
122
     * @param  array     $dictionary
123
     * @param  int       $fixed
124
     * @param  int|null  $parentId
125
     * @param  int       $cut
126
     *
127
     * @return int
128
     */
129 12
    protected static function reorderNodes(
130
        array &$dictionary,
131
        &$fixed,
132
        $parentId = null,
133
        $cut = 1
134
    ) {
135 12
        if ( ! isset($dictionary[$parentId])) {
136 12
            return $cut;
137
        }
138
139
        /** @var NodeTrait $model */
140 12
        foreach ($dictionary[$parentId] as $model) {
141 12
            $lft = $cut;
142 12
            $cut = self::reorderNodes($dictionary, $fixed, $model->getKey(), $cut + 1);
143 12
            $rgt = $cut;
144
145 12
            if ($model->rawNode($lft, $rgt, $parentId)->isDirty()) {
146 12
                $model->save();
147 12
                $fixed++;
148 9
            }
149
150 12
            ++$cut;
151 9
        }
152
153 12
        unset($dictionary[$parentId]);
154
155 12
        return $cut;
156
    }
157
}
158