Passed
Push — master ( 87d081...157357 )
by Jonas
04:03
created

addRecursiveQueryJoinsAndConstraints()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 15
c 1
b 0
f 0
dl 0
loc 27
ccs 17
cts 17
cp 1
rs 9.7666
cc 3
nc 4
nop 5
crap 3
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 28
    public function scopeWhereDepth(Builder $query, mixed $operator, mixed $value = null): Builder
33
    {
34 28
        $arguments = array_slice(func_get_args(), 1);
35
36 28
        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 5
    public function scopeBreadthFirst(Builder $query): Builder
46
    {
47 5
        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 5
    public function scopeDepthFirst(Builder $query): Builder
57
    {
58 5
        $sql = $query->getExpressionGrammar()->compileOrderByPath();
59
60 5
        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 270
    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 270
        $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 270
        $grammar = $query->getExpressionGrammar();
87
88 270
        $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 270
            ->$union(
90 270
                $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 270
            );
92
93 270
        $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 270
        $query->getModel()->setTable($name);
96
97 270
        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 270
    protected function getInitialQuery(
111
        ExpressionGrammar $grammar,
112
        callable $constraint,
113
        int $initialDepth,
114
        string $from
115
    ): Builder {
116 270
        $table = explode(' as ', $from)[1] ?? $from;
117
118 270
        $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 270
        $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 270
            $this->getDepthName()
122 270
        );
123
124 270
        $initialPath = $grammar->compileInitialPath(
125 270
            $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 270
            $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 270
        );
128
129 270
        $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 270
            ->select("$table.*")
131 270
            ->selectRaw($initialDepth . ' as ' . $depth)
132 270
            ->selectRaw($initialPath)
133 270
            ->from($from);
134
135 270
        $this->addInitialQueryCustomPaths($query, $grammar);
136
137 270
        $this->addInitialQueryPivotColumns($query, $grammar, $pivotTable, $initialDepth);
138
139 270
        $this->addInitialQueryCycleDetection($query, $grammar);
140
141 270
        $this->addInitialQueryJoins($query, $pivotTable, $initialDepth);
142
143 270
        $constraint($query);
144
145 270
        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 270
    protected function addInitialQueryCustomPaths(Builder $query, ExpressionGrammar $grammar): void
156
    {
157 270
        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 270
            $query->selectRaw(
159 270
                $grammar->compileInitialPath(
160 270
                    is_string($path['column']) ? $this->qualifyColumn($path['column']) : $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
                    is_string($path['column']) ? $this->/** @scrutinizer ignore-call */ qualifyColumn($path['column']) : $path['column'],
Loading history...
161 270
                    $path['name']
162 270
                )
163 270
            );
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 270
    protected function addInitialQueryPivotColumns(
177
        Builder $query,
178
        ExpressionGrammar $grammar,
179
        string $pivotTable,
180
        int $initialDepth
181
    ): void {
182 270
        $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 270
        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 96
            );
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
                $doctrineColumn = $query->getConnection()->getDoctrineColumn($pivotTable, $column);
205
206 96
                $null = $grammar->compilePivotColumnNullValue(
207 96
                    $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

207
                    /** @scrutinizer ignore-type */ $type,
Loading history...
208 96
                    $doctrineColumn->getPrecision(),
0 ignored issues
show
Bug introduced by
It seems like $doctrineColumn->getPrecision() can also be of type Illuminate\Database\ConnectionInterface and Illuminate\Database\Eloquent\Builder; however, parameter $precision of Staudenmeir\LaravelAdjac...ePivotColumnNullValue() does only seem to accept integer, 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

208
                    /** @scrutinizer ignore-type */ $doctrineColumn->getPrecision(),
Loading history...
209 96
                    $doctrineColumn->getScale()
0 ignored issues
show
Bug introduced by
It seems like $doctrineColumn->getScale() can also be of type Illuminate\Database\ConnectionInterface and Illuminate\Database\Eloquent\Builder; however, parameter $scale of Staudenmeir\LaravelAdjac...ePivotColumnNullValue() does only seem to accept integer, 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

209
                    /** @scrutinizer ignore-type */ $doctrineColumn->getScale()
Loading history...
210 96
                );
211
212 96
                $query->selectRaw("$null as " . $grammar->wrap("pivot_$column"));
213
            }
214
        } else {
215 186
            foreach ($columns as $column) {
216 186
                $query->addSelect("$pivotTable.$column as pivot_$column");
217
            }
218
        }
219
    }
220
221
    /**
222
     * Add cycle detection to the initial query for a relationship expression.
223
     *
224
     * @param \Illuminate\Database\Eloquent\Builder $query
225
     * @param \Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar $grammar
226
     * @return void
227
     */
228 270
    protected function addInitialQueryCycleDetection(Builder $query, ExpressionGrammar $grammar): void
229
    {
230 270
        if ($this->enableCycleDetection() && $this->includeCycleStart()) {
0 ignored issues
show
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

230
        if ($this->/** @scrutinizer ignore-call */ enableCycleDetection() && $this->includeCycleStart()) {
Loading history...
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

230
        if ($this->enableCycleDetection() && $this->/** @scrutinizer ignore-call */ includeCycleStart()) {
Loading history...
231 58
            $query->selectRaw(
232 58
                $grammar->compileCycleDetectionInitialSelect(
233 58
                    $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

233
                    $this->/** @scrutinizer ignore-call */ 
234
                           getCycleDetectionColumnName()
Loading history...
234 58
                )
235 58
            );
236
        }
237
    }
238
239
    /**
240
     * Add join clauses to the initial query for a relationship expression.
241
     *
242
     * @param \Illuminate\Database\Eloquent\Builder $query
243
     * @param string $pivotTable
244
     * @param int $initialDepth
245
     * @return void
246
     */
247 270
    protected function addInitialQueryJoins(Builder $query, string $pivotTable, int $initialDepth): void
248
    {
249 270
        if ($initialDepth < 0) {
250 87
            $query->join(
251 87
                $pivotTable,
252 87
                $this->getQualifiedLocalKeyName(),
253 87
                '=',
254 87
                $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

254
                $this->/** @scrutinizer ignore-call */ 
255
                       getQualifiedParentKeyName()
Loading history...
255 87
            );
256 204
        } elseif ($initialDepth > 0) {
257 111
            $query->join(
258 111
                $pivotTable,
259 111
                $this->getQualifiedLocalKeyName(),
260 111
                '=',
261 111
                $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

261
                $this->/** @scrutinizer ignore-call */ 
262
                       getQualifiedChildKeyName()
Loading history...
262 111
            );
263
        }
264
    }
265
266
    /**
267
     * Get the recursive query for a relationship expression.
268
     *
269
     * @param \Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar $grammar
270
     * @param string $direction
271
     * @param string $from
272
     * @param int|null $maxDepth
273
     * @return \Illuminate\Database\Eloquent\Builder $query
274
     */
275 270
    protected function getRecursiveQuery(
276
        ExpressionGrammar $grammar,
277
        string $direction,
278
        string $from,
279
        int $maxDepth = null
280
    ): Builder {
281 270
        $name = $this->getExpressionName();
282
283 270
        $table = explode(' as ', $from)[1] ?? $from;
284
285 270
        $pivotTable = $this->getPivotTableName();
286
287 270
        $depth = $grammar->wrap($this->getDepthName());
288
289 270
        $joinColumns = [
290 270
            'asc' => [
291 270
                $name . '.' . $this->getLocalKeyName(),
292 270
                $this->getQualifiedChildKeyName(),
293 270
            ],
294 270
            'desc' => [
295 270
                $name . '.' . $this->getLocalKeyName(),
296 270
                $this->getQualifiedParentKeyName(),
297 270
            ],
298 270
        ];
299
300 270
        $recursiveDepth = $depth . ' ' . ($direction === 'asc' ? '-' : '+') . ' 1';
301
302 270
        $recursivePath = $grammar->compileRecursivePath(
303 270
            $this->getQualifiedLocalKeyName(),
304 270
            $this->getPathName()
305 270
        );
306
307 270
        $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

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