Completed
Pull Request — master (#45)
by Şəhriyar
19:44
created

NestedEntity::prependTo()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 32
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 2.0005

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 32
ccs 19
cts 20
cp 0.95
rs 8.8571
cc 2
eloc 19
nc 2
nop 2
crap 2.0005
1
<?php namespace App\Models;
2
3
use Carbon\Carbon;
4
use Illuminate\Database\Eloquent\SoftDeletes;
5
use Illuminate\Database\Query\JoinClause;
6
7
/**
8
 * App\Models\NestedEntity
9
 *
10
 * @property integer $id
11
 * @property string  $name
12
 * @property integer $left_range
13
 * @property integer $right_range
14
 * @property Carbon  $created_at
15
 * @property Carbon  $updated_at
16
 * @property integer $deleted_at
17
 * @method static \Illuminate\Database\Query\Builder|\App\Models\NestedEntity whereId($value)
18
 * @method static \Illuminate\Database\Query\Builder|\App\Models\NestedEntity whereName($value)
19
 * @method static \Illuminate\Database\Query\Builder|\App\Models\NestedEntity whereLeftRange($value)
20
 * @method static \Illuminate\Database\Query\Builder|\App\Models\NestedEntity whereRightRange($value)
21
 * @method static \Illuminate\Database\Query\Builder|\App\Models\NestedEntity whereCreatedAt($value)
22
 * @method static \Illuminate\Database\Query\Builder|\App\Models\NestedEntity whereUpdatedAt($value)
23
 * @method static \Illuminate\Database\Query\Builder|\App\Models\NestedEntity whereDeletedAt($value)
24
 */
25
class NestedEntity extends \Eloquent
26
{
27
    use SoftDeletes;
28
29
    protected $table = "nested_entities";
30
31
    protected $guarded = ["left_range", "right_range"];
32
33
    const SELECT_ALL_WITH_MINIMUM_INFO = 1;
34
35
    const SELECT_SINGLE_PATH_ONLY = 2;
36
37
    const SELECT_WITH_DEPTH_INFO = 4;
38
39
    const SELECT_LEAVES_ONLY = 8;
40
41
    /**
42
     * @param        $newEntityName
43
     * @param int    $referenceEntityId
44
     *
45
     * @return boolean
46
     * @throws \InvalidArgumentException
47
     */
48 2
    public function insertIntoAtTheBeginning($newEntityName, $referenceEntityId)
49
    {
50
        # Fetch reference entity
51 2
        $referenceEntity = \DB::table($this->table)->where('id', $referenceEntityId)->first();
52 2
        if (is_null($referenceEntity)) {
53 1
            throw new \InvalidArgumentException("Reference entity with id: " . $referenceEntityId . " not found!");
54
        }
55
56 1
        \DB::transaction(
57
            function () use ($newEntityName, $referenceEntity) {
58
                # Create new entity
59 1
                $newEntity = \DB::table($this->table);
60
61
                # Update ranges in preparation of insertion
62 1
                \DB::table($this->table)
63 1
                    ->where('right_range', '>', $referenceEntity->left_range)
64 1
                    ->update(['right_range' => \DB::raw('right_range + 2')]);
65 1
                \DB::table($this->table)
66 1
                    ->where('left_range', '>', $referenceEntity->left_range)
67 1
                    ->update(['left_range' => \DB::raw('left_range + 2')]);
68
69
                # Insert now
70 1
                return $newEntity->insert([
71 1
                    'name' => $newEntityName,
72 1
                    'left_range' => $referenceEntity->left_range + 1,
73 1
                    'right_range' => $referenceEntity->left_range + 2,
74 1
                    'created_at' => Carbon::now(),
75 1
                    'updated_at' => Carbon::now()
76
                ]);
77 1
            }
78
        );
79 1
    }
80
81
82
    /**
83
     * @param        $newEntityName
84
     * @param int    $referenceEntityId
85
     *
86
     * @return boolean
87
     * @throws \InvalidArgumentException
88
     */
89 1
    public function insertIntoAtTheEnd($newEntityName, $referenceEntityId)
90
    {
91
        # Fetch reference entity
92 1
        $referenceEntity = \DB::table($this->table)->where('id', $referenceEntityId)->first();
93 1
        if (is_null($referenceEntity)) {
94
            throw new \InvalidArgumentException("Reference entity with id: " . $referenceEntityId . " not found!");
95
        }
96
97 1
        \DB::transaction(
98
            function () use ($newEntityName, $referenceEntity) {
99
                # Create new entity
100 1
                $newEntity = \DB::table($this->table);
101
102
                # Update ranges in preparation of insertion
103 1
                \DB::table($this->table)
104 1
                    ->where('right_range', '>=', $referenceEntity->right_range)
105 1
                    ->update(['right_range' => \DB::raw('right_range + 2')]);
106 1
                \DB::table($this->table)
107 1
                    ->where('left_range', '>', $referenceEntity->right_range)
108 1
                    ->update(['left_range' => \DB::raw('left_range + 2')]);
109
110
                # Insert now
111 1
                return $newEntity->insert([
112 1
                    'name' => $newEntityName,
113 1
                    'left_range' => $referenceEntity->right_range,
114 1
                    'right_range' => $referenceEntity->right_range + 1,
115 1
                    'created_at' => Carbon::now(),
116 1
                    'updated_at' => Carbon::now()
117
                ]);
118 1
            }
119
        );
120 1
    }
121
122
123
    /**
124
     * Alias to insertIntoAtTheEnd()
125
     *
126
     * @param        $newEntityName
127
     * @param int    $referenceEntityId
128
     *
129
     * @return boolean
130
     * @throws \InvalidArgumentException
131
     */
132 1
    public function insertInto($newEntityName, $referenceEntityId)
133
    {
134 1
        return $this->insertIntoAtTheEnd($newEntityName, $referenceEntityId);
135
    }
136
137
138
    /**
139
     * @param string $newEntityName
140
     * @param int    $referenceEntityId
141
     *
142
     * @return boolean
143
     * @throws \InvalidArgumentException
144
     */
145 1
    public function prependTo($newEntityName, $referenceEntityId)
146
    {
147
        # Fetch reference entity
148 1
        $referenceEntity = \DB::table($this->table)->where('id', $referenceEntityId)->first();
149 1
        if (is_null($referenceEntity)) {
150
            throw new \InvalidArgumentException("Reference entity with id: " . $referenceEntityId . " not found!");
151
        }
152
153 1
        \DB::transaction(
154
            function () use ($newEntityName, $referenceEntity) {
155
                # Create new entity
156 1
                $newEntity = \DB::table($this->table);
157
158
                # Update ranges in preparation of insertion
159 1
                \DB::table($this->table)
160 1
                    ->where('right_range', '>', $referenceEntity->left_range)
161 1
                    ->update(['right_range' => \DB::raw('right_range + 2')]);
162 1
                \DB::table($this->table)
163 1
                    ->where('left_range', '>=', $referenceEntity->left_range)
164 1
                    ->update(['left_range' => \DB::raw('left_range + 2')]);
165
166
                # Insert now
167 1
                return $newEntity->insert([
168 1
                    'name' => $newEntityName,
169 1
                    'left_range' => $referenceEntity->left_range,
170 1
                    'right_range' => $referenceEntity->right_range,
171 1
                    'created_at' => Carbon::now(),
172 1
                    'updated_at' => Carbon::now()
173
                ]);
174 1
            }
175
        );
176 1
    }
177
178
179
    /**
180
     * @param string $newEntityName
181
     * @param int    $referenceEntityId
182
     *
183
     * @return boolean
184
     * @throws \InvalidArgumentException
185
     */
186 1
    public function appendTo($newEntityName, $referenceEntityId)
187
    {
188
        # Fetch reference entity
189 1
        $referenceEntity = \DB::table($this->table)->where('id', $referenceEntityId)->first();
190 1
        if (is_null($referenceEntity)) {
191
            throw new \InvalidArgumentException("Reference entity with id: " . $referenceEntityId . " not found!");
192
        }
193
194 1
        \DB::transaction(
195
            function () use ($newEntityName, $referenceEntity) {
196
                # Create new entity
197 1
                $newEntity = \DB::table($this->table);
198
199
                # Update ranges in preparation of insertion
200 1
                \DB::table($this->table)
201 1
                    ->where('right_range', '>', $referenceEntity->right_range)
202 1
                    ->update(['right_range' => \DB::raw('right_range + 2')]);
203 1
                \DB::table($this->table)
204 1
                    ->where('left_range', '>', $referenceEntity->right_range)
205 1
                    ->update(['left_range' => \DB::raw('left_range + 2')]);
206
207
                # Insert now
208 1
                return $newEntity->insert([
209 1
                    'name' => $newEntityName,
210 1
                    'left_range' => $referenceEntity->right_range + 1,
211 1
                    'right_range' => $referenceEntity->right_range + 2,
212 1
                    'created_at' => Carbon::now(),
213 1
                    'updated_at' => Carbon::now()
214
                ]);
215 1
            }
216
        );
217 1
    }
218
219
220 2
    public function remove($id, $doSoftDelete = true)
221
    {
222
        # Round up delete-ables
223 2
        $referenceEntity = \DB::table($this->table)->select('left_range', 'right_range', \DB::raw('right_range - left_range + 1 as range_width'))->where('id', $id)->first();
224 2
        if (is_null($referenceEntity)) {
225 1
            throw new \InvalidArgumentException("Reference entity with id: " . $id . " not found!");
226
        }
227 1
        $completeListOfEntitiesToDeleteIncludingOrphans = \DB::table($this->table)
228 1
            ->where('left_range', '>=', $referenceEntity->left_range)
229 1
            ->where('left_range', '<=', $referenceEntity->right_range);
230
231
        # Perform either a soft-delete or hard-delete
232 1
        return \DB::transaction(
233
            function () use ($referenceEntity, $doSoftDelete, $completeListOfEntitiesToDeleteIncludingOrphans) {
234 1
                if ($doSoftDelete) {
235
                    # Soft delete
236 1
                    $removeResult = $completeListOfEntitiesToDeleteIncludingOrphans->update(['deleted_at' => Carbon::now()]);
237
                } else {
238
                    # Hard delete
239 1
                    $removeResult = $completeListOfEntitiesToDeleteIncludingOrphans->delete();
240
241
                    # Update ranges
242 1
                    \DB::table($this->table)
243 1
                        ->where('right_range', '>', $referenceEntity->right_range)
244 1
                        ->update(['right_range' => \DB::raw('right_range - ' . $referenceEntity->range_width)]);
245 1
                    \DB::table($this->table)
246 1
                        ->where('left_range', '>', $referenceEntity->right_range)
247 1
                        ->update(['left_range' => \DB::raw('left_range - ' . $referenceEntity->range_width)]);
248
                }
249
250 1
                return $removeResult;
251 1
            }
252
        );
253
254
    }
255
256
257
    /**
258
     * @param  int    $flag Parameters of Select, which are defined bitwise (see self:SELECT__* constants)
259
     * @param  string $id   Path information: used only if anything path related is requested.
260
     *
261
     * @return array|static[]
262
     * @throws \InvalidArgumentException
263
     */
264 2
    public function fetch($flag = self::SELECT_ALL_WITH_MINIMUM_INFO, $id = null)
265
    {
266
        # Error scenarios
267 2
        if ($flag & self::SELECT_ALL_WITH_MINIMUM_INFO && ($flag & self::SELECT_WITH_DEPTH_INFO || $flag & self::SELECT_SINGLE_PATH_ONLY)) {
268 1
            throw new \InvalidArgumentException("SELECT_ALL_WITH_MINIMUM_INFO bit isn't compatible with other bits. Use it alone!");
269 1
        } elseif ($flag & self::SELECT_SINGLE_PATH_ONLY && empty($id)) {
270
            throw new \InvalidArgumentException("SELECT_SINGLE_PATH_ONLY requires leaf category ID!");
271 1
        } elseif ($flag & self::SELECT_SINGLE_PATH_ONLY && $flag & self::SELECT_WITH_DEPTH_INFO) {
272
            throw new \InvalidArgumentException("SELECT_SINGLE_PATH_ONLY bit isn't compatible with SELECT_WITH_DEPTH_INFO - their results are mutually restrictive from opposing ends!");
273
        }
274
275
        # Prelim
276 1
        empty($id) && $id = 1;
277 1
        $nestedEntities = \DB::table($this->table . ' as node')
278 1
            ->select('node.id', 'node.name')
279 1
            ->leftJoin(
280 1
                $this->table . ' as parent',
281 1
                function (JoinClause $join) {
282 1
                    $join->on('node.left_range', '<=', 'parent.right_range')
283 1
                        ->on('node.left_range', '>=', 'parent.left_range');
284 1
                });
285
286
        # Scenario-1: Select'ing *single path only* with leaf node at the end of that path
287 1
        $flag == self::SELECT_SINGLE_PATH_ONLY && $nestedEntities->select('parent.id', 'parent.name')->where('node.id', '=', $id)->orderBy('parent.left_range');
288
289
        # Scenario-2: Select'ing *descendents* of provided parent-entity, with the bare minumum
290 1
        $flag == self::SELECT_ALL_WITH_MINIMUM_INFO && $nestedEntities->where('parent.id', '=', $id)->orderBy('node.left_range');
291
292
        # Scenario-3: Select'ing *everything* with depth information
293 1
        $flag == self::SELECT_WITH_DEPTH_INFO && $nestedEntities->addSelect('node.name', \DB::raw('(COUNT(parent.name)-1) as depth'))->groupBy('node.id')->orderBy('node.left_range');
294
295
        # Scenario-4: Fetches leaves only
296 1
        $flag == self::SELECT_LEAVES_ONLY && $nestedEntities = \DB::table($this->table)->select('id', 'name')->where('right_range', '=', \DB::raw('left_range + 1'))->orderBy('left_range');
297 1
        if ($flag == self::SELECT_LEAVES_ONLY && $id !== 1) {
298 1
            $parentEntity = \DB::table($this->table)->select('left_range', 'right_range')->where('id', $id)->first();
299 1
            $nestedEntities->whereBetween('left_range', [$parentEntity->left_range, $parentEntity->right_range]);
300
        }
301
302 1
        return $nestedEntities->get();
303
    }
304
}
305