-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNested.php
More file actions
296 lines (259 loc) · 6.57 KB
/
Nested.php
File metadata and controls
296 lines (259 loc) · 6.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
<?php
/**
* @package framework
* @copyright Copyright (c) 2005-2020 The Regents of the University of California.
* @license http://opensource.org/licenses/MIT MIT
*/
namespace Qubeshub\Database;
/**
* Database ORM class for implementing nested set records
*/
class Nested extends Relational
{
/**
* Scopes to limit the realm of the nested set functions
*
* @var array
**/
protected $scopes = [];
/**
* Updates all subsequent vars after new child insertion
*
* @param string $pos The position being updated, whether left or right
* @param int $base The base level after which values should be changed
* @param bool $add Whether or not we're adding or subtracted from existing
* @return $this
* @since 2.1.0
**/
private function updateTrailing($pos = 'lft', $base = 0, $add = true)
{
// Reposition new values of displaced items
$query = $this->getQuery();
$query->update($this->getTableName());
$query->set([
$pos => new Value\Raw($pos . ($add ? '+' : '-') . '2'),
]);
$query->where($pos, '>=', $base)
->where('id', '!=', $this->id)
->execute();
return $this;
}
/**
* Resolves the trailing left and right values for the new model
*
* @param int $base The base level after which values should be changed
* @return $this
* @since 2.1.0
**/
private function resolveTrailing($base, $add = true)
{
return $this->updateTrailing('lft', $base, $add)
->updateTrailing('rgt', $base, $add);
}
/**
* Establishes the model as a proper object as needed
*
* @param object|int $model The model to resolve
* @return $this
* @since 2.1.0
**/
private function establishIsModel(&$model)
{
// Turn model into an object if need be
if (!is_object($model))
{
$model = static::oneOrFail((int) $model);
}
return $this;
}
/**
* Sets the default scopes on the model
*
* @param object|int $parent The parent of the child being created
* @return $this
* @since 2.1.0
**/
private function establishBaseParametersFromParent($parent)
{
$this->set('parent_id', $parent->id);
$this->set('level', $parent->level + 1);
return $this->applyScopes($parent);
}
/**
* Applies the scopes of the given model to the current
*
* @param object|int $parent The parent from which to inherit
* @param string $method The way in which scopes are applied
* @return $this
* @since 2.1.0
**/
private function applyScopes($parent, $method = 'set')
{
// Inherit scopes from parent
foreach ($this->scopes as $scope)
{
$this->$method($scope, $parent->$scope);
}
return $this;
}
/**
* Applies the scopes of the given model to the current pending query
*
* @param object|int $parent The parent from which to inherit
* @return $this
* @since 2.1.0
**/
private function applyScopesWhere($parent)
{
return $this->applyScopes($parent, 'whereEquals');
}
/**
* Saves the current model to the database as the nth child of the given parent
*
* @param object|int $parent The parent of the child being created
* @return bool
* @since 2.1.0
**/
public function saveAsChildOf($parent)
{
$this->establishIsModel($parent)
->establishBaseParametersFromParent($parent);
// Compute the location where the item should reside
$this->set('lft', $parent->rgt);
$this->set('rgt', $parent->rgt + 1);
// Save
if (!$this->save())
{
return false;
}
// Reposition new values of displaced items
$this->resolveTrailing($parent->rgt);
return true;
}
/**
* Saves the current model to the database as the first child of the given parent
*
* @param object|int $parent The parent of the child being created
* @return bool
* @since 2.1.0
**/
public function saveAsFirstChildOf($parent)
{
$this->establishIsModel($parent)
->establishBaseParametersFromParent($parent);
// Compute the location where the item should reside
$this->set('lft', $parent->lft + 1);
$this->set('rgt', $parent->lft + 2);
// Save
if (!$this->save())
{
return false;
}
// Reposition new values of displaced items
$this->resolveTrailing($parent->lft + 1);
return true;
}
/**
* Saves the current model to the database as the last child of the given parent
*
* @param object|int $parent The parent of the child being created
* @return bool
* @since 2.1.0
**/
public function saveAsLastChildOf($parent)
{
return $this->saveAsChildOf($parent);
}
/**
* Saves a new root node element
*
* @return bool
* @since 2.1.0
**/
public function saveAsRoot()
{
// Compute the location where the item should reside
$this->set('parent_id', 0);
$this->set('level', 0);
$this->set('lft', 0);
$this->set('rgt', 1);
// Save
return $this->save();
}
/**
* Deletes a model, rearranging subordinate nodes as appropriate
*
* @return bool
* @since 2.1.0
**/
public function destroy()
{
if (!parent::destroy())
{
return false;
}
foreach ($this->getDescendants() as $descendant)
{
$descendant->destroy();
// We have to decrement our internal reference to right here
// so that we ultimately resolve trailing below based on the
// properly updated value, otherwise anything upstream of
// what we're destroying won't be properly updated
$this->rgt -= 2;
}
// Reposition new values of displaced items
$this->resolveTrailing($this->rgt, false);
return true;
}
/**
* Establishes the query for the immediate children of the current model
*
* @return array
* @since 2.1.0
**/
public function children()
{
return $this->descendants(1);
}
/**
* Grabs the immediate children of the current model
*
* @return array
* @since 2.1.0
**/
public function getChildren()
{
return $this->children()->rows();
}
/**
* Establishes the query for all of the descendants of the current model
*
* @param int $level The level to limit to
* @return array
* @since 2.1.0
**/
public function descendants($level = null)
{
$instance = self::blank();
$instance->where('level', '>', $this->level)
->order('lft', 'asc');
if (isset($level))
{
$instance->where('level', '<=', $this->level + $level);
}
return $instance->where('lft', '>', $this->lft)
->where('rgt', '<', $this->rgt)
->applyScopesWhere($this);
}
/**
* Grabs all of the descendants of the current model
*
* @param int $level The level to limit to
* @return array
* @since 2.1.0
**/
public function getDescendants($level = null)
{
return $this->descendants($level)->rows();
}
}