Issues (413)

app/Traits/ModelTree.php (7 issues)

1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: sheldon
5
 * Date: 18-3-27
6
 * Time: 下午5:03.
7
 */
8
9
namespace Yeelight\Traits;
10
11
use Illuminate\Support\Facades\DB;
12
13
/**
14
 * Trait ModelTree
15
 *
16
 * @category Yeelight
17
 *
18
 * @package Yeelight\Traits
19
 *
20
 * @author Sheldon Lee <[email protected]>
21
 *
22
 * @license https://opensource.org/licenses/MIT MIT
23
 *
24
 * @link https://www.yeelight.com
25
 */
26
trait ModelTree
27
{
28
    /**
29
     * @var array
30
     */
31
    protected static $branchOrder = [];
32
33
    /**
34
     * @var string
35
     */
36
    protected $parentColumn = 'parent_id';
37
38
    /**
39
     * @var string
40
     */
41
    protected $titleColumn = 'title';
42
43
    /**
44
     * @var string
45
     */
46
    protected $orderColumn = 'order';
47
48
    /**
49
     * @var \Closure
50
     */
51
    protected $queryCallback;
52
53
    /**
54
     * Get children of current node.
55
     *
56
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
57
     */
58
    public function children()
59
    {
60
        return $this->hasMany(static::class, $this->parentColumn);
0 ignored issues
show
It seems like hasMany() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

60
        return $this->/** @scrutinizer ignore-call */ hasMany(static::class, $this->parentColumn);
Loading history...
61
    }
62
63
    /**
64
     * Get parent of current node.
65
     *
66
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
67
     */
68
    public function parent()
69
    {
70
        return $this->belongsTo(static::class, $this->parentColumn);
0 ignored issues
show
It seems like belongsTo() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

70
        return $this->/** @scrutinizer ignore-call */ belongsTo(static::class, $this->parentColumn);
Loading history...
71
    }
72
73
    /**
74
     * @return string
75
     */
76
    public function getParentColumn()
77
    {
78
        return $this->parentColumn;
79
    }
80
81
    /**
82
     * Set parent column.
83
     *
84
     * @param string $column
85
     */
86
    public function setParentColumn($column)
87
    {
88
        $this->parentColumn = $column;
89
    }
90
91
    /**
92
     * Get title column.
93
     *
94
     * @return string
95
     */
96
    public function getTitleColumn()
97
    {
98
        return $this->titleColumn;
99
    }
100
101
    /**
102
     * Set title column.
103
     *
104
     * @param string $column
105
     */
106
    public function setTitleColumn($column)
107
    {
108
        $this->titleColumn = $column;
109
    }
110
111
    /**
112
     * Get order column name.
113
     *
114
     * @return string
115
     */
116
    public function getOrderColumn()
117
    {
118
        return $this->orderColumn;
119
    }
120
121
    /**
122
     * Set order column.
123
     *
124
     * @param string $column
125
     */
126
    public function setOrderColumn($column)
127
    {
128
        $this->orderColumn = $column;
129
    }
130
131
    /**
132
     * Set query callback to model.
133
     *
134
     * @param \Closure|null $query
135
     *
136
     * @return $this
137
     */
138
    public function withQuery(\Closure $query = null)
139
    {
140
        $this->queryCallback = $query;
141
142
        return $this;
143
    }
144
145
    /**
146
     * Format data to tree like array.
147
     *
148
     * @return array
149
     */
150
    public function toTree()
151
    {
152
        return $this->buildNestedArray();
153
    }
154
155
    /**
156
     * Build Nested array.
157
     *
158
     * @param array $nodes
159
     * @param int   $parentId
160
     *
161
     * @return array
162
     */
163
    protected function buildNestedArray(array $nodes = [], $parentId = 0)
164
    {
165
        $branch = [];
166
167
        if (empty($nodes)) {
168
            $nodes = $this->allNodes();
169
        }
170
171
        foreach ($nodes as $node) {
172
            if ($node[$this->parentColumn] == $parentId) {
173
                $children = $this->buildNestedArray($nodes, $node[$this->getKeyName()]);
0 ignored issues
show
It seems like getKeyName() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

173
                $children = $this->buildNestedArray($nodes, $node[$this->/** @scrutinizer ignore-call */ getKeyName()]);
Loading history...
174
175
                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...
176
                    $node['children'] = $children;
177
                }
178
179
                $branch[] = $node;
180
            }
181
        }
182
183
        return $branch;
184
    }
185
186
    /**
187
     * Get all elements.
188
     *
189
     * @return mixed
190
     */
191
    public function allNodes()
192
    {
193
        $orderColumn = DB::getQueryGrammar()->wrap($this->orderColumn);
194
        $byOrder = $orderColumn.' = 0,'.$orderColumn;
195
196
        $self = new static();
197
198
        if ($this->queryCallback instanceof \Closure) {
0 ignored issues
show
$this->queryCallback is always a sub-type of Closure. If $this->queryCallback can have other possible types, add them to app/Traits/ModelTree.php:49.
Loading history...
199
            $self = call_user_func($this->queryCallback, $self);
200
        }
201
202
        return $self->orderByRaw($byOrder)->get()->toArray();
203
    }
204
205
    /**
206
     * Set the order of branches in the tree.
207
     *
208
     * @param array $order
209
     *
210
     * @return void
211
     */
212
    protected static function setBranchOrder(array $order)
213
    {
214
        static::$branchOrder = array_flip(array_flatten($order));
215
216
        static::$branchOrder = array_map(function ($item) {
217
            return ++$item;
218
        }, static::$branchOrder);
0 ignored issues
show
It seems like static::branchOrder can also be of type null; however, parameter $arr1 of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

218
        }, /** @scrutinizer ignore-type */ static::$branchOrder);
Loading history...
219
    }
220
221
    /**
222
     * Save tree order from a tree like array.
223
     *
224
     * @param array $tree
225
     * @param int   $parentId
226
     */
227
    public static function saveOrder($tree = [], $parentId = 0)
228
    {
229
        if (empty(static::$branchOrder)) {
230
            static::setBranchOrder($tree);
231
        }
232
233
        foreach ($tree as $branch) {
234
            $node = static::find($branch['id']);
235
236
            $node->{$node->getParentColumn()} = $parentId;
237
            $node->{$node->getOrderColumn()} = static::$branchOrder[$branch['id']];
238
            $node->save();
239
240
            if (isset($branch['children'])) {
241
                static::saveOrder($branch['children'], $branch['id']);
242
            }
243
        }
244
    }
245
246
    /**
247
     * Get options for Select field in form.
248
     *
249
     * @return \Illuminate\Support\Collection
250
     */
251
    public static function selectOptions()
252
    {
253
        $options = (new static())->buildSelectOptions();
254
255
        return collect($options)->prepend('Root', 0)->all();
256
    }
257
258
    /**
259
     * Build options of select field in form.
260
     *
261
     * @param array  $nodes
262
     * @param int    $parentId
263
     * @param string $prefix
264
     *
265
     * @return array
266
     */
267
    protected function buildSelectOptions(array $nodes = [], $parentId = 0, $prefix = '')
268
    {
269
        $prefix = $prefix ?: str_repeat('&nbsp;', 6);
270
271
        $options = [];
272
273
        if (empty($nodes)) {
274
            $nodes = $this->allNodes();
275
        }
276
277
        foreach ($nodes as $node) {
278
            $node[$this->titleColumn] = $prefix.'&nbsp;'.$node[$this->titleColumn];
279
            if ($node[$this->parentColumn] == $parentId) {
280
                $children = $this->buildSelectOptions($nodes, $node[$this->getKeyName()], $prefix.$prefix);
281
282
                $options[$node[$this->getKeyName()]] = $node[$this->titleColumn];
283
284
                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...
285
                    $options += $children;
286
                }
287
            }
288
        }
289
290
        return $options;
291
    }
292
293
    /**
294
     * {@inheritdoc}
295
     */
296
    protected static function boot()
297
    {
298
        parent::boot();
299
    }
300
}
301