Passed
Branch master (407c50)
by Jonas
02:53
created

HasGraphRelationshipScopes::getInitialQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 36
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 18
c 1
b 0
f 0
dl 0
loc 36
ccs 19
cts 19
cp 1
rs 9.6666
cc 1
nc 1
nop 4
crap 1
1
<?php
2
3
namespace Staudenmeir\LaravelAdjacencyList\Eloquent\Traits;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use RuntimeException;
7
use Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar;
8
9
trait HasGraphRelationshipScopes
10
{
11
    /**
12
     * Add a recursive expression for a custom subgraph to the query.
13
     *
14
     * @param \Illuminate\Database\Eloquent\Builder $query
15
     * @param callable $constraint
16
     * @param int|null $maxDepth
17
     * @return \Illuminate\Database\Eloquent\Builder
18
     */
19 8
    public function scopeSubgraph(Builder $query, callable $constraint, int $maxDepth = null): Builder
20
    {
21 8
        return $query->withRelationshipExpression('desc', $constraint, 0, null, $maxDepth);
22
    }
23
24
    /**
25
     * Limit the query by depth.
26
     *
27
     * @param \Illuminate\Database\Eloquent\Builder $query
28
     * @param mixed $operator
29
     * @param mixed|null $value
30
     * @return \Illuminate\Database\Eloquent\Builder
31
     */
32 16
    public function scopeWhereDepth(Builder $query, mixed $operator, mixed $value = null): Builder
33
    {
34 16
        $arguments = array_slice(func_get_args(), 1);
35
36 16
        return $query->where($this->getDepthName(), ...$arguments);
0 ignored issues
show
Bug introduced by
It seems like getDepthName() 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

36
        return $query->where($this->/** @scrutinizer ignore-call */ getDepthName(), ...$arguments);
Loading history...
37
    }
38
39
    /**
40
     * Order the query breadth-first.
41
     *
42
     * @param \Illuminate\Database\Eloquent\Builder $query
43
     * @return \Illuminate\Database\Eloquent\Builder
44
     */
45 4
    public function scopeBreadthFirst(Builder $query): Builder
46
    {
47 4
        return $query->orderBy($this->getDepthName());
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->orderBy($this->getDepthName()) could return the type Illuminate\Database\Query\Builder which is incompatible with the type-hinted return Illuminate\Database\Eloquent\Builder. Consider adding an additional type-check to rule them out.
Loading history...
48
    }
49
50
    /**
51
     * Order the query depth-first.
52
     *
53
     * @param \Illuminate\Database\Eloquent\Builder $query
54
     * @return \Illuminate\Database\Eloquent\Builder
55
     */
56 4
    public function scopeDepthFirst(Builder $query): Builder
57
    {
58 4
        $sql = $query->getExpressionGrammar()->compileOrderByPath();
59
60 4
        return $query->orderByRaw($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->orderByRaw($sql) could return the type Illuminate\Database\Query\Builder which is incompatible with the type-hinted return Illuminate\Database\Eloquent\Builder. Consider adding an additional type-check to rule them out.
Loading history...
61
    }
62
63
    /**
64
     * Add a recursive expression for the relationship to the query.
65
     *
66
     * @param \Illuminate\Database\Eloquent\Builder $query
67
     * @param string $direction
68
     * @param callable $constraint
69
     * @param int $initialDepth
70
     * @param string|null $from
71
     * @param int|null $maxDepth
72
     * @param string $union
73
     * @return \Illuminate\Database\Eloquent\Builder
74
     */
75 206
    public function scopeWithRelationshipExpression(
76
        Builder $query,
77
        string $direction,
78
        callable $constraint,
79
        int $initialDepth,
80
        string $from = null,
81
        int $maxDepth = null,
82
        string $union = 'unionAll'
83
    ): Builder {
84 206
        $from = $from ?: $this->getTable();
0 ignored issues
show
Bug introduced by
It seems like getTable() 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

84
        $from = $from ?: $this->/** @scrutinizer ignore-call */ getTable();
Loading history...
85
86 206
        $grammar = $query->getExpressionGrammar();
87
88 206
        $expression = $this->getInitialQuery($grammar, $constraint, $initialDepth, $from)
0 ignored issues
show
Bug introduced by
It seems like $grammar can also be of type Illuminate\Database\Eloquent\Builder; however, parameter $grammar of Staudenmeir\LaravelAdjac...opes::getInitialQuery() does only seem to accept Staudenmeir\LaravelAdjac...mmars\ExpressionGrammar, 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

88
        $expression = $this->getInitialQuery(/** @scrutinizer ignore-type */ $grammar, $constraint, $initialDepth, $from)
Loading history...
89 206
            ->$union(
90 206
                $this->getRecursiveQuery($grammar, $direction, $from, $maxDepth)
0 ignored issues
show
Bug introduced by
It seems like $grammar can also be of type Illuminate\Database\Eloquent\Builder; however, parameter $grammar of Staudenmeir\LaravelAdjac...es::getRecursiveQuery() does only seem to accept Staudenmeir\LaravelAdjac...mmars\ExpressionGrammar, 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

90
                $this->getRecursiveQuery(/** @scrutinizer ignore-type */ $grammar, $direction, $from, $maxDepth)
Loading history...
91
            );
92
93 206
        $name = $this->getExpressionName();
0 ignored issues
show
Bug introduced by
It seems like getExpressionName() 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

93
        /** @scrutinizer ignore-call */ 
94
        $name = $this->getExpressionName();
Loading history...
94
95 206
        $query->getModel()->setTable($name);
96
97 206
        return $query->withRecursiveExpression($name, $expression)->from($name);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->withRecur...xpression)->from($name) could return the type Illuminate\Database\Query\Builder which is incompatible with the type-hinted return Illuminate\Database\Eloquent\Builder. Consider adding an additional type-check to rule them out.
Loading history...
98
    }
99
100
    /**
101
     * Get the initial query for a relationship expression.
102
     *
103
     * @param \Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar $grammar
104
     * @param callable $constraint
105
     * @param int $initialDepth
106
     * @param array $pivotColumns
107
     * @param string $from
108
     * @return \Illuminate\Database\Eloquent\Builder $query
109
     */
110 206
    protected function getInitialQuery(
111
        ExpressionGrammar $grammar,
112
        callable $constraint,
113
        int $initialDepth,
114
        string $from
115
    ): Builder {
116 206
        $table = explode(' as ', $from)[1] ?? $from;
117
118 206
        $pivotTable = $this->getPivotTableName();
0 ignored issues
show
Bug introduced by
It seems like getPivotTableName() 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

118
        /** @scrutinizer ignore-call */ 
119
        $pivotTable = $this->getPivotTableName();
Loading history...
119
120 206
        $depth = $grammar->wrap(
0 ignored issues
show
Bug introduced by
The method wrap() does not exist on Staudenmeir\LaravelAdjac...mmars\ExpressionGrammar. Since it exists in all sub-types, consider adding an abstract or default implementation to Staudenmeir\LaravelAdjac...mmars\ExpressionGrammar. ( Ignorable by Annotation )

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

120
        /** @scrutinizer ignore-call */ 
121
        $depth = $grammar->wrap(
Loading history...
121 206
            $this->getDepthName()
122
        );
123
124 206
        $initialPath = $grammar->compileInitialPath(
125 206
            $this->getQualifiedLocalKeyName(),
0 ignored issues
show
Bug introduced by
It seems like getQualifiedLocalKeyName() 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

125
            $this->/** @scrutinizer ignore-call */ 
126
                   getQualifiedLocalKeyName(),
Loading history...
126 206
            $this->getPathName()
0 ignored issues
show
Bug introduced by
It seems like getPathName() 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

126
            $this->/** @scrutinizer ignore-call */ 
127
                   getPathName()
Loading history...
127
        );
128
129 206
        $query = $this->newModelQuery()
0 ignored issues
show
Bug introduced by
It seems like newModelQuery() 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

129
        $query = $this->/** @scrutinizer ignore-call */ newModelQuery()
Loading history...
130 206
            ->select("$table.*")
131 206
            ->selectRaw($initialDepth . ' as ' . $depth)
132 206
            ->selectRaw($initialPath)
133 206
            ->from($from);
134
135 206
        $this->addInitialQueryCustomPaths($query, $grammar);
136
137 206
        $this->addInitialQueryPivotColumns($query, $grammar, $pivotTable, $initialDepth);
138
139 206
        $this->addInitialQueryCycleDetection($query, $grammar);
140
141 206
        $this->addInitialQueryJoins($query, $pivotTable, $initialDepth);
142
143 206
        $constraint($query);
144
145 206
        return $query;
146
    }
147
148
    /**
149
     * Add custom paths to the initial query.
150
     *
151
     * @param \Illuminate\Database\Eloquent\Builder $query
152
     * @param \Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar $grammar
153
     * @return void
154
     */
155 206
    protected function addInitialQueryCustomPaths(Builder $query, ExpressionGrammar $grammar): void
156
    {
157 206
        foreach ($this->getCustomPaths() as $path) {
0 ignored issues
show
Bug introduced by
It seems like getCustomPaths() 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

157
        foreach ($this->/** @scrutinizer ignore-call */ getCustomPaths() as $path) {
Loading history...
158 206
            $query->selectRaw(
159 206
                $grammar->compileInitialPath(
160 206
                    $this->qualifyColumn($path['column']),
0 ignored issues
show
Bug introduced by
It seems like qualifyColumn() 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

160
                    $this->/** @scrutinizer ignore-call */ 
161
                           qualifyColumn($path['column']),
Loading history...
161 206
                    $path['name']
162
                )
163
            );
164
        }
165
    }
166
167
    /**
168
     * Add pivot columns to the initial query for a relationship expression.
169
     *
170
     * @param \Illuminate\Database\Eloquent\Builder $query
171
     * @param \Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar $grammar
172
     * @param string $pivotTable
173
     * @param int $initialDepth
174
     * @return void
175
     */
176 206
    protected function addInitialQueryPivotColumns(
177
        Builder $query,
178
        ExpressionGrammar $grammar,
179
        string $pivotTable,
180
        int $initialDepth
181
    ): void {
182 206
        $columns = [$this->getParentKeyName(), $this->getChildKeyName(), ...$this->getPivotColumns()];
0 ignored issues
show
Bug introduced by
It seems like getPivotColumns() 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

182
        $columns = [$this->getParentKeyName(), $this->getChildKeyName(), ...$this->/** @scrutinizer ignore-call */ getPivotColumns()];
Loading history...
Bug introduced by
It seems like getChildKeyName() 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

182
        $columns = [$this->getParentKeyName(), $this->/** @scrutinizer ignore-call */ getChildKeyName(), ...$this->getPivotColumns()];
Loading history...
Bug introduced by
It seems like getParentKeyName() 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

182
        $columns = [$this->/** @scrutinizer ignore-call */ getParentKeyName(), $this->getChildKeyName(), ...$this->getPivotColumns()];
Loading history...
183
184 206
        if ($initialDepth === 0) {
185 96
            if (!$query->getConnection()->isDoctrineAvailable()) {
186
                // @codeCoverageIgnoreStart
187
                throw new RuntimeException(
188
                    'This feature requires the doctrine/dbal package. Please run "composer require doctrine/dbal".'
189
                );
190
                // @codeCoverageIgnoreEnd
191
            }
192
193 96
            $localKeyType = $query->getConnection()->getSchemaBuilder()->getColumnType(
194 96
                (new $this())->getTable(),
195 96
                $this->getLocalKeyName()
0 ignored issues
show
Bug introduced by
It seems like getLocalKeyName() 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

195
                $this->/** @scrutinizer ignore-call */ 
196
                       getLocalKeyName()
Loading history...
196
            );
197 96
            foreach ($columns as $i => $column) {
198 96
                if ($i < 2) {
199 96
                    $type = $localKeyType;
200
                } else {
201 96
                    $type = $query->getConnection()->getSchemaBuilder()->getColumnType($pivotTable, $column);
202
                }
203
204 96
                $null = $grammar->compilePivotColumnNullValue($type);
0 ignored issues
show
Bug introduced by
It seems like $type can also be of type Illuminate\Database\ConnectionInterface and Illuminate\Database\Eloquent\Builder; however, parameter $type of Staudenmeir\LaravelAdjac...ePivotColumnNullValue() does only seem to accept string, 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

204
                $null = $grammar->compilePivotColumnNullValue(/** @scrutinizer ignore-type */ $type);
Loading history...
205
206 96
                $query->selectRaw("$null as " . $grammar->wrap("pivot_$column"));
207
            }
208
        } else {
209 122
            foreach ($columns as $column) {
210 122
                $query->addSelect("$pivotTable.$column as pivot_$column");
211
            }
212
        }
213
    }
214
215
    /**
216
     * Add cycle detection to the initial query for a relationship expression.
217
     *
218
     * @param \Illuminate\Database\Eloquent\Builder $query
219
     * @param \Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar $grammar
220
     * @return void
221
     */
222 206
    protected function addInitialQueryCycleDetection(Builder $query, ExpressionGrammar $grammar): void
223
    {
224 206
        if ($this->enableCycleDetection() && $this->includeCycleStart()) {
0 ignored issues
show
Bug introduced by
It seems like includeCycleStart() 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

224
        if ($this->enableCycleDetection() && $this->/** @scrutinizer ignore-call */ includeCycleStart()) {
Loading history...
Bug introduced by
It seems like enableCycleDetection() 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

224
        if ($this->/** @scrutinizer ignore-call */ enableCycleDetection() && $this->includeCycleStart()) {
Loading history...
225 38
            $query->selectRaw(
226 38
                'false as ' . $grammar->wrap($this->getCycleDetectionColumnName())
0 ignored issues
show
Bug introduced by
It seems like getCycleDetectionColumnName() 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

226
                'false as ' . $grammar->wrap($this->/** @scrutinizer ignore-call */ getCycleDetectionColumnName())
Loading history...
227
            );
228
        }
229
    }
230
231
    /**
232
     * Add join clauses to the initial query for a relationship expression.
233
     *
234
     * @param \Illuminate\Database\Eloquent\Builder $query
235
     * @param string $pivotTable
236
     * @param int $initialDepth
237
     * @return void
238
     */
239 206
    protected function addInitialQueryJoins(Builder $query, string $pivotTable, int $initialDepth): void
240
    {
241 206
        if ($initialDepth < 0) {
242 58
            $query->join(
243
                $pivotTable,
244 58
                $this->getQualifiedLocalKeyName(),
245
                '=',
246 58
                $this->getQualifiedParentKeyName()
0 ignored issues
show
Bug introduced by
It seems like getQualifiedParentKeyName() 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

246
                $this->/** @scrutinizer ignore-call */ 
247
                       getQualifiedParentKeyName()
Loading history...
247
            );
248 169
        } elseif ($initialDepth > 0) {
249 76
            $query->join(
250
                $pivotTable,
251 76
                $this->getQualifiedLocalKeyName(),
252
                '=',
253 76
                $this->getQualifiedChildKeyName()
0 ignored issues
show
Bug introduced by
It seems like getQualifiedChildKeyName() 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

253
                $this->/** @scrutinizer ignore-call */ 
254
                       getQualifiedChildKeyName()
Loading history...
254
            );
255
        }
256
    }
257
258
    /**
259
     * Get the recursive query for a relationship expression.
260
     *
261
     * @param \Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar $grammar
262
     * @param string $direction
263
     * @param string $from
264
     * @param int|null $maxDepth
265
     * @return \Illuminate\Database\Eloquent\Builder $query
266
     */
267 206
    protected function getRecursiveQuery(
268
        ExpressionGrammar $grammar,
269
        string $direction,
270
        string $from,
271
        int $maxDepth = null
272
    ): Builder {
273 206
        $name = $this->getExpressionName();
274
275 206
        $table = explode(' as ', $from)[1] ?? $from;
276
277 206
        $pivotTable = $this->getPivotTableName();
278
279 206
        $depth = $grammar->wrap($this->getDepthName());
280
281 206
        $joinColumns = [
282
            'asc' => [
283 206
                $name . '.' . $this->getLocalKeyName(),
284 206
                $this->getQualifiedChildKeyName(),
285
            ],
286
            'desc' => [
287 206
                $name . '.' . $this->getLocalKeyName(),
288 206
                $this->getQualifiedParentKeyName(),
289
            ],
290
        ];
291
292 206
        $recursiveDepth = $depth . ' ' . ($direction === 'asc' ? '-' : '+') . ' 1';
293
294 206
        $recursivePath = $grammar->compileRecursivePath(
295 206
            $this->getQualifiedLocalKeyName(),
296 206
            $this->getPathName()
297
        );
298
299 206
        $recursivePathBindings = $grammar->getRecursivePathBindings($this->getPathSeparator());
0 ignored issues
show
Bug introduced by
It seems like getPathSeparator() 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

299
        $recursivePathBindings = $grammar->getRecursivePathBindings($this->/** @scrutinizer ignore-call */ getPathSeparator());
Loading history...
300
301 206
        $query = $this->newModelQuery()
302 206
            ->select($table . '.*')
303 206
            ->selectRaw($recursiveDepth . ' as ' . $depth)
304 206
            ->selectRaw($recursivePath, $recursivePathBindings)
305 206
            ->from($from);
306
307 206
        $this->addRecursiveQueryCustomPaths($query, $grammar);
308
309 206
        $this->addRecursiveQueryPivotColumns($query, $pivotTable);
310
311 206
        $this->addRecursiveQueryCycleDetection($query, $grammar);
312
313 206
        $this->addRecursiveQueryJoinsAndConstraints($query, $pivotTable, $direction, $name, $joinColumns);
314
315 206
        if (!is_null($maxDepth)) {
316 4
            $query->where($this->getDepthName(), '<', $maxDepth);
317
        }
318
319 206
        return $query;
320
    }
321
322
    /**
323
     * Add customs path to the recursive query for a relationship expression.
324
     *
325
     * @param \Illuminate\Database\Eloquent\Builder $query
326
     * @param \Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar $grammar
327
     * @return void
328
     */
329 206
    protected function addRecursiveQueryCustomPaths(Builder $query, ExpressionGrammar $grammar): void
330
    {
331 206
        foreach ($this->getCustomPaths() as $path) {
332 206
            $query->selectRaw(
333 206
                $grammar->compileRecursivePath(
334 206
                    $this->qualifyColumn($path['column']),
335 206
                    $path['name']
336
                ),
337 206
                $grammar->getRecursivePathBindings($path['separator'])
338
            );
339
        }
340
    }
341
342
    /**
343
     * Add pivot columns to the recursive query for a relationship expression.
344
     *
345
     * @param \Illuminate\Database\Eloquent\Builder $query
346
     * @param string $pivotTable
347
     * @return void
348
     */
349 206
    protected function addRecursiveQueryPivotColumns(Builder $query, string $pivotTable): void
350
    {
351 206
        $columns = [$this->getParentKeyName(), $this->getChildKeyName(), ...$this->getPivotColumns()];
352
353 206
        foreach ($columns as $column) {
354 206
            $query->addSelect("$pivotTable.$column as pivot_$column");
355
        }
356
    }
357
358
    /**
359
     * Add cycle detection to the recursive query for a relationship expression.
360
     *
361
     * @param \Illuminate\Database\Eloquent\Builder $query
362
     * @param \Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar $grammar
363
     * @return void
364
     */
365 206
    protected function addRecursiveQueryCycleDetection(Builder $query, ExpressionGrammar $grammar): void
366
    {
367 206
        if ($this->enableCycleDetection()) {
368 76
            $sql = $grammar->compileCycleDetection(
369 76
                $this->getQualifiedLocalKeyName(),
370 76
                $this->getPathName()
371
            );
372 76
            $bindings = $grammar->getCycleDetectionBindings(
373 76
                $this->getPathSeparator()
374
            );
375
376
            // TODO[L10]: whereNot()
377 76
            if ($this->includeCycleStart()) {
378 38
                $query->selectRaw($sql, $bindings);
379
380 38
                $query->whereRaw(
381 38
                    'not ' . $grammar->wrap($this->getCycleDetectionColumnName())
382
                );
383
            } else {
384 38
                $query->whereRaw("not($sql)", $bindings);
385
            }
386
        }
387
    }
388
389
    /**
390
     * Add join and where clauses to the recursive query for a relationship expression.
391
     *
392
     * @param \Illuminate\Database\Eloquent\Builder $query
393
     * @param string $pivotTable
394
     * @param string $direction
395
     * @param string $name
396
     * @param array $joinColumns
397
     * @return void
398
     */
399 206
    protected function addRecursiveQueryJoinsAndConstraints(
400
        Builder $query,
401
        string $pivotTable,
402
        string $direction,
403
        string $name,
404
        array $joinColumns
405
    ): void {
406 206
        if ($direction === 'desc') {
407 131
            $query->join(
408
                $pivotTable,
409 131
                $this->getQualifiedLocalKeyName(),
410
                '=',
411 131
                $this->getQualifiedChildKeyName()
412
            );
413
        } else {
414 99
            $query->join(
415
                $pivotTable,
416 99
                $this->getQualifiedLocalKeyName(),
417
                '=',
418 99
                $this->getQualifiedParentKeyName()
419
            );
420
        }
421
422 206
        $query->join($name, $joinColumns[$direction][0], '=', $joinColumns[$direction][1]);
423
424 206
        if (static::$recursiveQueryConstraint) {
425 8
            (static::$recursiveQueryConstraint)($query);
426
        }
427
    }
428
}
429