Passed
Pull Request — master (#177)
by
unknown
15:04
created

HasGraphRelationshipScopes::getRecursiveQuery()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 53
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 28
c 1
b 0
f 0
dl 0
loc 53
ccs 32
cts 32
cp 1
rs 9.472
cc 3
nc 4
nop 4
crap 3

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
                $precision = $query->getConnection()->getDoctrineColumn($pivotTable, $column)->getPrecision();
205
                $scale = $query->getConnection()->getDoctrineColumn($pivotTable, $column)->getScale();
206 96
                $null = $grammar->compilePivotColumnNullValue($type, $precision, $scale);
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

206
                $null = $grammar->compilePivotColumnNullValue(/** @scrutinizer ignore-type */ $type, $precision, $scale);
Loading history...
Bug introduced by
It seems like $scale 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

206
                $null = $grammar->compilePivotColumnNullValue($type, $precision, /** @scrutinizer ignore-type */ $scale);
Loading history...
Bug introduced by
It seems like $precision 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

206
                $null = $grammar->compilePivotColumnNullValue($type, /** @scrutinizer ignore-type */ $precision, $scale);
Loading history...
207
208
                $query->selectRaw("$null as " . $grammar->wrap("pivot_$column"));
209 186
            }
210 186
        } else {
211
            foreach ($columns as $column) {
212
                $query->addSelect("$pivotTable.$column as pivot_$column");
213
            }
214
        }
215
    }
216
217
    /**
218
     * Add cycle detection to the initial query for a relationship expression.
219
     *
220
     * @param \Illuminate\Database\Eloquent\Builder $query
221
     * @param \Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar $grammar
222 270
     * @return void
223
     */
224 270
    protected function addInitialQueryCycleDetection(Builder $query, ExpressionGrammar $grammar): void
225 58
    {
226 58
        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

226
        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

226
        if ($this->/** @scrutinizer ignore-call */ enableCycleDetection() && $this->includeCycleStart()) {
Loading history...
227 58
            $query->selectRaw(
228 58
                $grammar->compileCycleDetectionInitialSelect(
229 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

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

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

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

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