Passed
Push — master ( dc387e...afaf99 )
by Jonas
02:08
created

getRecursiveQuery()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 56
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 34
dl 0
loc 56
ccs 40
cts 40
cp 1
rs 8.7537
c 0
b 0
f 0
cc 6
nc 12
nop 4
crap 6

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 Illuminate\Database\Query\JoinClause;
7
use Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar;
8
9
trait HasRecursiveRelationshipScopes
10
{
11
    /**
12
     * Add a recursive expression for the whole tree to the query.
13
     *
14
     * @param \Illuminate\Database\Eloquent\Builder $query
15
     * @param int|null $maxDepth
16
     * @return \Illuminate\Database\Eloquent\Builder
17
     */
18 53
    public function scopeTree(Builder $query, $maxDepth = null)
19
    {
20 53
        $constraint = function (Builder $query) {
21 53
            $query->isRoot();
22 53
        };
23
24 53
        return $query->treeOf($constraint, $maxDepth);
25
    }
26
27
    /**
28
     * Add a recursive expression for a custom tree to the query.
29
     *
30
     * @param \Illuminate\Database\Eloquent\Builder $query
31
     * @param callable $constraint
32
     * @param int|null $maxDepth
33
     * @return \Illuminate\Database\Eloquent\Builder
34
     */
35 63
    public function scopeTreeOf(Builder $query, callable $constraint, $maxDepth = null)
36
    {
37 63
        return $query->withRelationshipExpression('desc', $constraint, 0, null, $maxDepth);
38
    }
39
40
    /**
41
     * Limit the query to models with children.
42
     *
43
     * @param \Illuminate\Database\Eloquent\Builder $query
44
     * @return \Illuminate\Database\Eloquent\Builder
45
     */
46 5
    public function scopeHasChildren(Builder $query)
47
    {
48 5
        $keys = (new static())->newQuery()
0 ignored issues
show
Bug introduced by
It seems like newQuery() 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

48
        $keys = (new static())->/** @scrutinizer ignore-call */ newQuery()
Loading history...
49 5
            ->select($this->getParentKeyName())
0 ignored issues
show
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

49
            ->select($this->/** @scrutinizer ignore-call */ getParentKeyName())
Loading history...
50 5
            ->hasParent();
51
52 5
        return $query->whereIn($this->getLocalKeyName(), $keys);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->whereIn($...tLocalKeyName(), $keys) also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
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

52
        return $query->whereIn($this->/** @scrutinizer ignore-call */ getLocalKeyName(), $keys);
Loading history...
53
    }
54
55
    /**
56
     * Limit the query to models with a parent.
57
     *
58
     * @param \Illuminate\Database\Eloquent\Builder $query
59
     * @return \Illuminate\Database\Eloquent\Builder
60
     */
61 15
    public function scopeHasParent(Builder $query)
62
    {
63 15
        return $query->whereNotNull($this->getParentKeyName());
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->whereNotN...is->getParentKeyName()) also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
64
    }
65
66
    /**
67
     * Limit the query to leaf models.
68
     *
69
     * @param \Illuminate\Database\Eloquent\Builder $query
70
     * @return \Illuminate\Database\Eloquent\Builder
71
     */
72 5
    public function scopeIsLeaf(Builder $query)
73
    {
74 5
        $keys = (new static())->newQuery()
75 5
            ->select($this->getParentKeyName())
76 5
            ->hasParent();
77
78 5
        return $query->whereNotIn($this->getLocalKeyName(), $keys);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->whereNotI...tLocalKeyName(), $keys) also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
79
    }
80
81
    /**
82
     * Limit the query to root models.
83
     *
84
     * @param \Illuminate\Database\Eloquent\Builder $query
85
     * @return \Illuminate\Database\Eloquent\Builder
86
     */
87 83
    public function scopeIsRoot(Builder $query)
88
    {
89 83
        return $query->whereNull($this->getParentKeyName());
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->whereNull...is->getParentKeyName()) also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
90
    }
91
92
    /**
93
     * Limit the query by depth.
94
     *
95
     * @param \Illuminate\Database\Eloquent\Builder $query
96
     * @param mixed $operator
97
     * @param mixed|null $value
98
     * @return \Illuminate\Database\Eloquent\Builder
99
     */
100 40
    public function scopeWhereDepth(Builder $query, $operator, $value = null)
101
    {
102 40
        $arguments = array_slice(func_get_args(), 1);
103
104 40
        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

104
        return $query->where($this->/** @scrutinizer ignore-call */ getDepthName(), ...$arguments);
Loading history...
105
    }
106
107
    /**
108
     * Order the query breadth-first.
109
     *
110
     * @param \Illuminate\Database\Eloquent\Builder $query
111
     * @return \Illuminate\Database\Eloquent\Builder
112
     */
113 20
    public function scopeBreadthFirst(Builder $query)
114
    {
115 20
        return $query->orderBy($this->getDepthName());
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->orderBy($this->getDepthName()) also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
116
    }
117
118
    /**
119
     * Order the query depth-first.
120
     *
121
     * @param \Illuminate\Database\Eloquent\Builder $query
122
     * @return \Illuminate\Database\Eloquent\Builder
123
     */
124 18
    public function scopeDepthFirst(Builder $query)
125
    {
126 18
        $sql = $query->getExpressionGrammar()->compileOrderByPath();
127
128 18
        return $query->orderByRaw($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->orderByRaw($sql) also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
129
    }
130
131
    /**
132
     * Add a recursive expression for the relationship to the query.
133
     *
134
     * @param \Illuminate\Database\Eloquent\Builder $query
135
     * @param string $direction
136
     * @param callable $constraint
137
     * @param int $initialDepth
138
     * @param string|null $from
139
     * @param int|null $maxDepth
140
     * @return \Illuminate\Database\Eloquent\Builder
141
     */
142 429
    public function scopeWithRelationshipExpression(Builder $query, $direction, callable $constraint, $initialDepth, $from = null, $maxDepth = null)
143
    {
144 429
        $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

144
        $from = $from ?: $this->/** @scrutinizer ignore-call */ getTable();
Loading history...
145
146 429
        $grammar = $query->getExpressionGrammar();
147
148 429
        $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

148
        $expression = $this->getInitialQuery(/** @scrutinizer ignore-type */ $grammar, $constraint, $initialDepth, $from)
Loading history...
149 429
            ->unionAll(
150 429
                $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

150
                $this->getRecursiveQuery(/** @scrutinizer ignore-type */ $grammar, $direction, $from, $maxDepth)
Loading history...
151 429
            );
152
153 429
        $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

153
        /** @scrutinizer ignore-call */ 
154
        $name = $this->getExpressionName();
Loading history...
154
155 429
        $query->getModel()->setTable($name);
156
157 429
        return $query->withRecursiveExpression($name, $expression)->from($name);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->withRecur...xpression)->from($name) also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
158
    }
159
160
    /**
161
     * Get the initial query for a relationship expression.
162
     *
163
     * @param \Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar|\Illuminate\Database\Grammar $grammar
164
     * @param callable $constraint
165
     * @param int $initialDepth
166
     * @param string $from
167
     * @return \Illuminate\Database\Eloquent\Builder $query
168
     */
169 429
    protected function getInitialQuery(ExpressionGrammar $grammar, callable $constraint, $initialDepth, $from)
170
    {
171 429
        $depth = $grammar->wrap($this->getDepthName());
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

171
        /** @scrutinizer ignore-call */ 
172
        $depth = $grammar->wrap($this->getDepthName());
Loading history...
172
173 429
        $initialPath = $grammar->compileInitialPath(
174 429
            $this->getLocalKeyName(),
175 429
            $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

175
            $this->/** @scrutinizer ignore-call */ 
176
                   getPathName()
Loading history...
176 429
        );
177
178 429
        $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

178
        $query = $this->/** @scrutinizer ignore-call */ newModelQuery()
Loading history...
179 429
            ->select('*')
180 429
            ->selectRaw($initialDepth.' as '.$depth)
181 429
            ->selectRaw($initialPath)
182 429
            ->from($from);
183
184 429
        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

184
        foreach ($this->/** @scrutinizer ignore-call */ getCustomPaths() as $path) {
Loading history...
185 424
            $query->selectRaw(
186 424
                $grammar->compileInitialPath($path['column'], $path['name'])
187 424
            );
188
        }
189
190 429
        $constraint($query);
191
192 429
        return $query;
193
    }
194
195
    /**
196
     * Get the recursive query for a relationship expression.
197
     *
198
     * @param \Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar|\Illuminate\Database\Grammar $grammar
199
     * @param string $direction
200
     * @param string $from
201
     * @param int|null $maxDepth
202
     * @return \Illuminate\Database\Eloquent\Builder $query
203
     */
204 429
    protected function getRecursiveQuery(ExpressionGrammar $grammar, $direction, $from, $maxDepth = null)
205
    {
206 429
        $name = $this->getExpressionName();
207
208 429
        $table = explode(' as ', $from)[1] ?? $from;
209
210 429
        $depth = $grammar->wrap($this->getDepthName());
211
212 429
        $joinColumns = [
213 429
            'asc' => [
214 429
                $name.'.'.$this->getParentKeyName(),
215 429
                $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

215
                $this->/** @scrutinizer ignore-call */ 
216
                       getQualifiedLocalKeyName(),
Loading history...
216 429
            ],
217 429
            'desc' => [
218 429
                $name.'.'.$this->getLocalKeyName(),
219 429
                $this->qualifyColumn($this->getParentKeyName()),
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

219
                $this->/** @scrutinizer ignore-call */ 
220
                       qualifyColumn($this->getParentKeyName()),
Loading history...
220 429
            ],
221 429
        ];
222
223 429
        if ($direction === 'both') {
224 60
            $recursiveDepth = "$depth + (case when {$joinColumns['desc'][1]}={$joinColumns['desc'][0]} then 1 else -1 end)";
225
        } else {
226 375
            $recursiveDepth = $depth.' '.($direction === 'asc' ? '-' : '+').' 1';
227
        }
228
229 429
        $recursivePath = $grammar->compileRecursivePath(
230 429
            $this->getQualifiedLocalKeyName(),
231 429
            $this->getPathName()
232 429
        );
233
234 429
        $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

234
        $recursivePathBindings = $grammar->getRecursivePathBindings($this->/** @scrutinizer ignore-call */ getPathSeparator());
Loading history...
235
236 429
        $query = $this->newModelQuery()
237 429
            ->select($table.'.*')
238 429
            ->selectRaw($recursiveDepth.' as '.$depth)
239 429
            ->selectRaw($recursivePath, $recursivePathBindings)
240 429
            ->from($from);
241
242 429
        foreach ($this->getCustomPaths() as $path) {
243 424
            $query->selectRaw(
244 424
                $grammar->compileRecursivePath(
245 424
                    is_string($path['column']) ? $this->qualifyColumn($path['column']) : $path['column'],
246 424
                    $path['name'],
247 424
                    $path['reverse'] ?? false,
248 424
                ),
249 424
                $grammar->getRecursivePathBindings($path['separator'])
250 424
            );
251
        }
252
253 429
        $this->addRecursiveQueryJoinsAndConstraints($query, $direction, $name, $joinColumns);
254
255 429
        if (!is_null($maxDepth)) {
256 15
            $query->where($this->getDepthName(), '<', $maxDepth);
257
        }
258
259 429
        return $query;
260
    }
261
262
    /**
263
     * Add join and where clauses to the recursive query for a relationship expression.
264
     *
265
     * @param \Illuminate\Database\Eloquent\Builder $query
266
     * @param string $direction
267
     * @param string $name
268
     * @param array $joinColumns
269
     * @return void
270
     */
271 429
    protected function addRecursiveQueryJoinsAndConstraints(Builder $query, $direction, $name, array $joinColumns)
272
    {
273 429
        if ($direction === 'both') {
274 60
            $query->join($name, function (JoinClause $join) use ($joinColumns) {
275 60
                $join->on($joinColumns['asc'][0], '=', $joinColumns['asc'][1])
276 60
                    ->orOn($joinColumns['desc'][0], '=', $joinColumns['desc'][1]);
277 60
            });
278
279 60
            $depth = $this->getDepthName();
280
281 60
            $query->where(function (Builder  $query) use ($depth, $joinColumns) {
282 60
                $query->where($depth, '=', 0)
283 60
                    ->orWhere(function (Builder $query) use ($depth, $joinColumns) {
284 60
                        $query->whereColumn($joinColumns['asc'][0], '=', $joinColumns['asc'][1])
285 60
                            ->where($depth, '<', 0);
286 60
                    })
287 60
                    ->orWhere(function (Builder $query) use ($depth, $joinColumns) {
288 60
                        $query->whereColumn($joinColumns['desc'][0], '=', $joinColumns['desc'][1])
289 60
                            ->where($depth, '>', 0);
290 60
                    });
291 60
            });
292
        } else {
293 375
            $query->join($name, $joinColumns[$direction][0], '=', $joinColumns[$direction][1]);
294
        }
295
296 429
        if (static::$recursiveQueryConstraint) {
297 10
            (static::$recursiveQueryConstraint)($query);
298
        }
299
    }
300
}
301