NestedEntity   A
last analyzed

Complexity

Total Complexity 27

Size/Duplication

Total Lines 281
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 96.12%

Importance

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