Completed
Push — master ( c8a423...a23ea0 )
by Song
02:21
created

src/Traits/ModelTree.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Encore\Admin\Traits;
4
5
use Illuminate\Database\Eloquent\Model;
6
use Illuminate\Support\Arr;
7
use Illuminate\Support\Facades\DB;
8
use Illuminate\Support\Facades\Request;
9
10
trait ModelTree
11
{
12
    /**
13
     * @var array
14
     */
15
    protected static $branchOrder = [];
16
17
    /**
18
     * @var string
19
     */
20
    protected $parentColumn = 'parent_id';
21
22
    /**
23
     * @var string
24
     */
25
    protected $titleColumn = 'title';
26
27
    /**
28
     * @var string
29
     */
30
    protected $orderColumn = 'order';
31
32
    /**
33
     * @var \Closure
34
     */
35
    protected $queryCallback;
36
37
    /**
38
     * Get children of current node.
39
     *
40
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
41
     */
42
    public function children()
43
    {
44
        return $this->hasMany(static::class, $this->parentColumn);
45
    }
46
47
    /**
48
     * Get parent of current node.
49
     *
50
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
51
     */
52
    public function parent()
53
    {
54
        return $this->belongsTo(static::class, $this->parentColumn);
55
    }
56
57
    /**
58
     * @return string
59
     */
60
    public function getParentColumn()
61
    {
62
        return $this->parentColumn;
63
    }
64
65
    /**
66
     * Set parent column.
67
     *
68
     * @param string $column
69
     */
70
    public function setParentColumn($column)
71
    {
72
        $this->parentColumn = $column;
73
    }
74
75
    /**
76
     * Get title column.
77
     *
78
     * @return string
79
     */
80
    public function getTitleColumn()
81
    {
82
        return $this->titleColumn;
83
    }
84
85
    /**
86
     * Set title column.
87
     *
88
     * @param string $column
89
     */
90
    public function setTitleColumn($column)
91
    {
92
        $this->titleColumn = $column;
93
    }
94
95
    /**
96
     * Get order column name.
97
     *
98
     * @return string
99
     */
100
    public function getOrderColumn()
101
    {
102
        return $this->orderColumn;
103
    }
104
105
    /**
106
     * Set order column.
107
     *
108
     * @param string $column
109
     */
110
    public function setOrderColumn($column)
111
    {
112
        $this->orderColumn = $column;
113
    }
114
115
    /**
116
     * Set query callback to model.
117
     *
118
     * @param \Closure|null $query
119
     *
120
     * @return $this
121
     */
122
    public function withQuery(\Closure $query = null)
123
    {
124
        $this->queryCallback = $query;
125
126
        return $this;
127
    }
128
129
    /**
130
     * Format data to tree like array.
131
     *
132
     * @return array
133
     */
134
    public function toTree()
135
    {
136
        return $this->buildNestedArray();
137
    }
138
139
    /**
140
     * Build Nested array.
141
     *
142
     * @param array $nodes
143
     * @param int   $parentId
144
     *
145
     * @return array
146
     */
147
    protected function buildNestedArray(array $nodes = [], $parentId = 0)
148
    {
149
        $branch = [];
150
151
        if (empty($nodes)) {
152
            $nodes = $this->allNodes();
153
        }
154
155
        foreach ($nodes as $node) {
156
            if ($node[$this->parentColumn] == $parentId) {
157
                $children = $this->buildNestedArray($nodes, $node[$this->getKeyName()]);
158
159
                if ($children) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $children 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...
160
                    $node['children'] = $children;
161
                }
162
163
                $branch[] = $node;
164
            }
165
        }
166
167
        return $branch;
168
    }
169
170
    /**
171
     * Get all elements.
172
     *
173
     * @return mixed
174
     */
175
    public function allNodes()
176
    {
177
        $orderColumn = DB::getQueryGrammar()->wrap($this->orderColumn);
178
        $byOrder = $orderColumn.' = 0,'.$orderColumn;
179
180
        $self = new static();
181
182
        if ($this->queryCallback instanceof \Closure) {
183
            $self = call_user_func($this->queryCallback, $self);
184
        }
185
186
        return $self->orderByRaw($byOrder)->get()->toArray();
187
    }
188
189
    /**
190
     * Set the order of branches in the tree.
191
     *
192
     * @param array $order
193
     *
194
     * @return void
195
     */
196
    protected static function setBranchOrder(array $order)
197
    {
198
        static::$branchOrder = array_flip(Arr::flatten($order));
199
200
        static::$branchOrder = array_map(function ($item) {
201
            return ++$item;
202
        }, static::$branchOrder);
203
    }
204
205
    /**
206
     * Save tree order from a tree like array.
207
     *
208
     * @param array $tree
209
     * @param int   $parentId
210
     */
211
    public static function saveOrder($tree = [], $parentId = 0)
212
    {
213
        if (empty(static::$branchOrder)) {
214
            static::setBranchOrder($tree);
215
        }
216
217
        foreach ($tree as $branch) {
218
            $node = static::find($branch['id']);
219
220
            $node->{$node->getParentColumn()} = $parentId;
221
            $node->{$node->getOrderColumn()} = static::$branchOrder[$branch['id']];
222
            $node->save();
223
224
            if (isset($branch['children'])) {
225
                static::saveOrder($branch['children'], $branch['id']);
226
            }
227
        }
228
    }
229
230
    /**
231
     * Get options for Select field in form.
232
     *
233
     * @param \Closure|null $closure
234
     * @param string        $rootText
235
     *
236
     * @return array
237
     */
238
    public static function selectOptions(\Closure $closure = null, $rootText = 'ROOT')
239
    {
240
        $options = (new static())->withQuery($closure)->buildSelectOptions();
241
242
        return collect($options)->prepend($rootText, 0)->all();
243
    }
244
245
    /**
246
     * Build options of select field in form.
247
     *
248
     * @param array  $nodes
249
     * @param int    $parentId
250
     * @param string $prefix
251
     * @param string $space
252
     *
253
     * @return array
254
     */
255
    protected function buildSelectOptions(array $nodes = [], $parentId = 0, $prefix = '', $space = '&nbsp;')
256
    {
257
        $prefix = $prefix ?: '┝'.$space;
258
259
        $options = [];
260
261
        if (empty($nodes)) {
262
            $nodes = $this->allNodes();
263
        }
264
265
        foreach ($nodes as $index => $node) {
266
            if ($node[$this->parentColumn] == $parentId) {
267
                $node[$this->titleColumn] = $prefix.$space.$node[$this->titleColumn];
268
269
                $childrenPrefix = str_replace('┝', str_repeat($space, 6), $prefix).'┝'.str_replace(['┝', $space], '', $prefix);
270
271
                $children = $this->buildSelectOptions($nodes, $node[$this->getKeyName()], $childrenPrefix);
272
273
                $options[$node[$this->getKeyName()]] = $node[$this->titleColumn];
274
275
                if ($children) {
276
                    $options += $children;
277
                }
278
            }
279
        }
280
281
        return $options;
282
    }
283
284
    /**
285
     * {@inheritdoc}
286
     */
287
    public function delete()
288
    {
289
        $this->where($this->parentColumn, $this->getKey())->delete();
290
291
        return parent::delete();
292
    }
293
294
    /**
295
     * {@inheritdoc}
296
     */
297
    protected static function boot()
298
    {
299
        parent::boot();
300
301
        static::saving(function (Model $branch) {
302
            $parentColumn = $branch->getParentColumn();
303
304
            if (Request::has($parentColumn) && Request::input($parentColumn) == $branch->getKey()) {
305
                throw new \Exception(trans('admin.parent_select_error'));
306
            }
307
308
            if (Request::has('_order')) {
309
                $order = Request::input('_order');
310
311
                Request::offsetUnset('_order');
312
313
                static::tree()->saveOrder($order);
314
315
                return false;
316
            }
317
318
            return $branch;
319
        });
320
    }
321
}
322