Completed
Pull Request — master (#37)
by Şəhriyar
274:38 queued 267:45
created

NestedEntity   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 280
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 0%

Importance

Changes 3
Bugs 1 Features 0
Metric Value
wmc 27
lcom 1
cbo 3
dl 0
loc 280
ccs 0
cts 153
cp 0
rs 10
c 3
b 1
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
B insertIntoAtTheBeginning() 0 32 2
B insertIntoAtTheEnd() 0 32 2
A insertInto() 0 4 1
B prependTo() 0 32 2
B appendTo() 0 32 2
B remove() 0 35 3
C fetch() 0 40 15
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
    public function insertIntoAtTheBeginning($newEntityName, $referenceEntityId)
49
    {
50
        # Fetch reference entity
51
        $referenceEntity = \DB::table($this->table)->where('id', $referenceEntityId)->first();
52
        if (is_null($referenceEntity)) {
53
            throw new \InvalidArgumentException("Reference entity with id: " . $referenceEntityId . " not found!");
54
        }
55
56
        \DB::transaction(
57
            function () use ($newEntityName, $referenceEntity) {
58
                # Create new entity
59
                $newEntity = \DB::table($this->table);
60
61
                # Update ranges in preparation of insertion
62
                \DB::table($this->table)
63
                    ->where('right_range', '>', $referenceEntity->left_range)
64
                    ->update(['right_range' => \DB::raw('right_range + 2')]);
65
                \DB::table($this->table)
66
                    ->where('left_range', '>', $referenceEntity->left_range)
67
                    ->update(['left_range' => \DB::raw('left_range + 2')]);
68
69
                # Insert now
70
                return $newEntity->insert([
71
                    'name' => $newEntityName,
72
                    'left_range' => $referenceEntity->left_range + 1,
73
                    'right_range' => $referenceEntity->left_range + 2,
74
                    'created_at' => Carbon::now(),
75
                    'updated_at' => Carbon::now()
76
                ]);
77
            }
78
        );
79
    }
80
81
82
    /**
83
     * @param        $newEntityName
84
     * @param int    $referenceEntityId
85
     *
86
     * @return boolean
87
     * @throws \InvalidArgumentException
88
     */
89
    public function insertIntoAtTheEnd($newEntityName, $referenceEntityId)
90
    {
91
        # Fetch reference entity
92
        $referenceEntity = \DB::table($this->table)->where('id', $referenceEntityId)->first();
93
        if (is_null($referenceEntity)) {
94
            throw new \InvalidArgumentException("Reference entity with id: " . $referenceEntityId . " not found!");
95
        }
96
97
        \DB::transaction(
98
            function () use ($newEntityName, $referenceEntity) {
99
                # Create new entity
100
                $newEntity = \DB::table($this->table);
101
102
                # Update ranges in preparation of insertion
103
                \DB::table($this->table)
104
                    ->where('right_range', '>=', $referenceEntity->right_range)
105
                    ->update(['right_range' => \DB::raw('right_range + 2')]);
106
                \DB::table($this->table)
107
                    ->where('left_range', '>', $referenceEntity->right_range)
108
                    ->update(['left_range' => \DB::raw('left_range + 2')]);
109
110
                # Insert now
111
                return $newEntity->insert([
112
                    'name' => $newEntityName,
113
                    'left_range' => $referenceEntity->right_range,
114
                    'right_range' => $referenceEntity->right_range + 1,
115
                    'created_at' => Carbon::now(),
116
                    'updated_at' => Carbon::now()
117
                ]);
118
            }
119
        );
120
    }
121
122
123
    /**
124
     * Alias to insertIntoAtTheEnd()
125
     *
126
     * @param        $newEntityName
127
     * @param int    $referenceEntityId
128
     *
129
     * @return boolean
130
     * @throws \InvalidArgumentException
131
     */
132
    public function insertInto($newEntityName, $referenceEntityId)
133
    {
134
        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
    public function prependTo($newEntityName, $referenceEntityId)
146
    {
147
        # Fetch reference entity
148
        $referenceEntity = \DB::table($this->table)->where('id', $referenceEntityId)->first();
149
        if (is_null($referenceEntity)) {
150
            throw new \InvalidArgumentException("Reference entity with id: " . $referenceEntityId . " not found!");
151
        }
152
153
        \DB::transaction(
154
            function () use ($newEntityName, $referenceEntity) {
155
                # Create new entity
156
                $newEntity = \DB::table($this->table);
157
158
                # Update ranges in preparation of insertion
159
                \DB::table($this->table)
160
                    ->where('right_range', '>', $referenceEntity->left_range)
161
                    ->update(['right_range' => \DB::raw('right_range + 2')]);
162
                \DB::table($this->table)
163
                    ->where('left_range', '>=', $referenceEntity->left_range)
164
                    ->update(['left_range' => \DB::raw('left_range + 2')]);
165
166
                # Insert now
167
                return $newEntity->insert([
168
                    'name' => $newEntityName,
169
                    'left_range' => $referenceEntity->left_range,
170
                    'right_range' => $referenceEntity->right_range,
171
                    'created_at' => Carbon::now(),
172
                    'updated_at' => Carbon::now()
173
                ]);
174
            }
175
        );
176
    }
177
178
179
    /**
180
     * @param string $newEntityName
181
     * @param int    $referenceEntityId
182
     *
183
     * @return boolean
184
     * @throws \InvalidArgumentException
185
     */
186
    public function appendTo($newEntityName, $referenceEntityId)
187
    {
188
        # Fetch reference entity
189
        $referenceEntity = \DB::table($this->table)->where('id', $referenceEntityId)->first();
190
        if (is_null($referenceEntity)) {
191
            throw new \InvalidArgumentException("Reference entity with id: " . $referenceEntityId . " not found!");
192
        }
193
194
        \DB::transaction(
195
            function () use ($newEntityName, $referenceEntity) {
196
                # Create new entity
197
                $newEntity = \DB::table($this->table);
198
199
                # Update ranges in preparation of insertion
200
                \DB::table($this->table)
201
                    ->where('right_range', '>', $referenceEntity->right_range)
202
                    ->update(['right_range' => \DB::raw('right_range + 2')]);
203
                \DB::table($this->table)
204
                    ->where('left_range', '>', $referenceEntity->right_range)
205
                    ->update(['left_range' => \DB::raw('left_range + 2')]);
206
207
                # Insert now
208
                return $newEntity->insert([
209
                    'name' => $newEntityName,
210
                    'left_range' => $referenceEntity->right_range + 1,
211
                    'right_range' => $referenceEntity->right_range + 2,
212
                    'created_at' => Carbon::now(),
213
                    'updated_at' => Carbon::now()
214
                ]);
215
            }
216
        );
217
    }
218
219
220
    public function remove($id, $doSoftDelete = true)
221
    {
222
        # Round up delete-ables
223
        $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
        if (is_null($referenceEntity)) {
225
            throw new \InvalidArgumentException("Reference entity with id: " . $id . " not found!");
226
        }
227
        $completeListOfEntitiesToDeleteIncludingOrphans = \DB::table($this->table)
228
            ->where('left_range', '>=', $referenceEntity->left_range)
229
            ->where('left_range', '<=', $referenceEntity->right_range);
230
231
        # Perform either a soft-delete or hard-delete
232
        return \DB::transaction(
233
            function () use ($referenceEntity, $doSoftDelete, $completeListOfEntitiesToDeleteIncludingOrphans) {
234
                if ($doSoftDelete) {
235
                    # Soft delete
236
                    $removeResult = $completeListOfEntitiesToDeleteIncludingOrphans->update(['deleted_at' => Carbon::now()]);
237
                } else {
238
                    # Hard delete
239
                    $removeResult = $completeListOfEntitiesToDeleteIncludingOrphans->delete();
240
241
                    # Update ranges
242
                    \DB::table($this->table)
243
                        ->where('right_range', '>', $referenceEntity->right_range)
244
                        ->update(['right_range' => \DB::raw('right_range - ' . $referenceEntity->range_width)]);
245
                    \DB::table($this->table)
246
                        ->where('left_range', '>', $referenceEntity->right_range)
247
                        ->update(['left_range' => \DB::raw('left_range - ' . $referenceEntity->range_width)]);
248
                }
249
250
                return $removeResult;
251
            }
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
    public function fetch($flag = self::SELECT_ALL_WITH_MINIMUM_INFO, $id = null)
265
    {
266
        # Error scenarios
267
        if ($flag & self::SELECT_ALL_WITH_MINIMUM_INFO && ($flag & self::SELECT_WITH_DEPTH_INFO || $flag & self::SELECT_SINGLE_PATH_ONLY)) {
268
            throw new \InvalidArgumentException("SELECT_ALL_WITH_MINIMUM_INFO bit isn't compatible with other bits. Use it alone!");
269
        } elseif ($flag & self::SELECT_SINGLE_PATH_ONLY && empty($id)) {
270
            throw new \InvalidArgumentException("SELECT_SINGLE_PATH_ONLY requires leaf category ID!");
271
        } 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
        empty($id) && $id = 1;
277
        $nestedEntities = \DB::table($this->table . ' as node')
278
            ->select('node.id', 'node.name')
279
            ->leftJoin(
280
                $this->table . ' as parent',
281
                function (JoinClause $join) {
282
                    $join->on('node.left_range', '<=', 'parent.right_range')
283
                        ->on('node.left_range', '>=', 'parent.left_range');
284
                });
285
286
        # Scenario-1: Select'ing *single path only* with leaf node at the end of that path
287
        $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
        $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
        $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
        $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
        if ($flag == self::SELECT_LEAVES_ONLY && $id !== 1) {
298
            $parentEntity = \DB::table($this->table)->select('left_range', 'right_range')->where('id', $id)->first();
299
            $nestedEntities->whereBetween('left_range', [$parentEntity->left_range, $parentEntity->right_range]);
300
        }
301
302
        return $nestedEntities->get();
303
    }
304
}
305