Passed
Push — master ( 6ed66a...c3d33f )
by Jonas
06:02
created

r()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 11
c 1
b 0
f 0
dl 0
loc 16
ccs 11
cts 11
cp 1
rs 9.6111
cc 5
nc 5
nop 1
crap 5
1
<?php
2
3
namespace Staudenmeir\LaravelAdjacencyList\Eloquent;
4
5
use Illuminate\Database\Eloquent\Builder;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Staudenmeir\LaravelAdjacencyList\Eloquent\Builder. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
6
use Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar;
7
8
trait HasRecursiveRelationshipScopes
9
{
10
    /**
11
     * Add a recursive expression for the whole tree to the query.
12
     *
13
     * @param \Illuminate\Database\Eloquent\Builder $query
14
     * @param int|null $maxDepth
15
     * @return \Illuminate\Database\Eloquent\Builder
16
     */
17
    public function scopeTree(Builder $query, $maxDepth = null)
18
    {
19
        $constraint = function (Builder $query) {
20
            $query->isRoot();
21 16
        };
22
23
        return $query->treeOf($constraint, $maxDepth);
24 16
    }
25 16
26
    /**
27 16
     * Add a recursive expression for a custom tree to the query.
28
     *
29
     * @param \Illuminate\Database\Eloquent\Builder $query
30
     * @param callable $constraint
31
     * @param int|null $maxDepth
32
     * @return \Illuminate\Database\Eloquent\Builder
33
     */
34
    public function scopeTreeOf(Builder $query, callable $constraint, $maxDepth = null)
35
    {
36 4
        return $query->withRelationshipExpression('desc', $constraint, 0, null, $maxDepth);
37
    }
38 4
39 4
    /**
40 4
     * Limit the query to models with children.
41
     *
42 4
     * @param \Illuminate\Database\Eloquent\Builder $query
43
     * @return \Illuminate\Database\Eloquent\Builder
44
     */
45
    public function scopeHasChildren(Builder $query)
46
    {
47
        $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

47
        $keys = (new static)->/** @scrutinizer ignore-call */ newQuery()
Loading history...
48
            ->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

48
            ->select($this->/** @scrutinizer ignore-call */ getParentKeyName())
Loading history...
49
            ->hasParent();
50
51 12
        return $query->whereIn($this->getLocalKeyName(), $keys);
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

51
        return $query->whereIn($this->/** @scrutinizer ignore-call */ getLocalKeyName(), $keys);
Loading history...
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...
52
    }
53 12
54
    /**
55
     * Limit the query to models with a parent.
56
     *
57
     * @param \Illuminate\Database\Eloquent\Builder $query
58
     * @return \Illuminate\Database\Eloquent\Builder
59
     */
60
    public function scopeHasParent(Builder $query)
61
    {
62 4
        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...
63
    }
64 4
65 4
    /**
66 4
     * Limit the query to leaf models.
67
     *
68 4
     * @param \Illuminate\Database\Eloquent\Builder $query
69
     * @return \Illuminate\Database\Eloquent\Builder
70
     */
71
    public function scopeIsLeaf(Builder $query)
72
    {
73
        $keys = (new static)->newQuery()
74
            ->select($this->getParentKeyName())
75
            ->hasParent();
76
77 20
        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...
78
    }
79 20
80
    /**
81
     * Limit the query to root models.
82
     *
83
     * @param \Illuminate\Database\Eloquent\Builder $query
84
     * @return \Illuminate\Database\Eloquent\Builder
85
     */
86
    public function scopeIsRoot(Builder $query)
87
    {
88
        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...
89
    }
90 16
91
    /**
92 16
     * Limit the query by depth.
93
     *
94 16
     * @param \Illuminate\Database\Eloquent\Builder $query
95
     * @param mixed $operator
96
     * @param mixed $value
97
     * @return \Illuminate\Database\Eloquent\Builder
98
     */
99
    public function scopeWhereDepth(Builder $query, $operator, $value = null)
100
    {
101
        $arguments = array_slice(func_get_args(), 1);
102
103 4
        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

103
        return $query->where($this->/** @scrutinizer ignore-call */ getDepthName(), ...$arguments);
Loading history...
104
    }
105 4
106
    /**
107
     * Order the query breadth-first.
108
     *
109
     * @param \Illuminate\Database\Eloquent\Builder $query
110
     * @return \Illuminate\Database\Eloquent\Builder
111
     */
112
    public function scopeBreadthFirst(Builder $query)
113
    {
114 8
        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...
115
    }
116 8
117
    /**
118
     * Order the query depth-first.
119
     *
120
     * @param \Illuminate\Database\Eloquent\Builder $query
121
     * @return \Illuminate\Database\Eloquent\Builder
122
     */
123
    public function scopeDepthFirst(Builder $query)
124
    {
125
        return $query->orderBy($this->getPathName());
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->orderBy($this->getPathName()) 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 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

125
        return $query->orderBy($this->/** @scrutinizer ignore-call */ getPathName());
Loading history...
126
    }
127
128
    /**
129 128
     * Add a recursive expression for the relationship to the query.
130
     *
131 128
     * @param \Illuminate\Database\Eloquent\Builder $query
132
     * @param string $direction
133 128
     * @param callable $constraint
134
     * @param int $initialDepth
135 128
     * @param string|null $from
136 128
     * @param int|null $maxDepth
137 128
     * @return \Illuminate\Database\Eloquent\Builder
138
     */
139
    public function scopeWithRelationshipExpression(Builder $query, $direction, callable $constraint, $initialDepth, $from = null, $maxDepth = null)
140 128
    {
141
        $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

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

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

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

150
        /** @scrutinizer ignore-call */ 
151
        $name = $this->getExpressionName();
Loading history...
151
152
        $query->getModel()->setTable($name);
153
154
        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...
155
    }
156 128
157
    /**
158 128
     * Get the initial query for a relationship expression.
159
     *
160 128
     * @param \Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar|\Illuminate\Database\Grammar $grammar
161 128
     * @param callable $constraint
162 128
     * @param int $initialDepth
163
     * @param string $from
164
     * @return \Illuminate\Database\Eloquent\Builder $query
165 128
     */
166 128
    protected function getInitialQuery(ExpressionGrammar $grammar, callable $constraint, $initialDepth, $from)
167 128
    {
168 128
        $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

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

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

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

212
            $this->/** @scrutinizer ignore-call */ 
213
                   getQualifiedLocalKeyName(),
Loading history...
213
            $this->getPathName()
214 128
        );
215
216 128
        $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

216
        $recursivePathBindings = $grammar->getRecursivePathBindings($this->/** @scrutinizer ignore-call */ getPathSeparator());
Loading history...
217
218
        $query = $this->newModelQuery()
219
            ->select($table.'.*')
220
            ->selectRaw($recursiveDepth.' as '.$depth)
221
            ->selectRaw($recursivePath, $recursivePathBindings)
222
            ->from($from);
223
224
        foreach ($this->getCustomPaths() as $path) {
225 128
            $query->selectRaw(
226
                $grammar->compileRecursivePath(
227 128
                    $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

227
                    $this->/** @scrutinizer ignore-call */ 
228
                           qualifyColumn($path['column']),
Loading history...
228
                    $path['name']
229 128
                ),
230 128
                $grammar->getRecursivePathBindings($path['separator'])
231 34
            );
232 94
        }
233 34
234 60
        if ($direction === 'asc') {
235 34
            $first = $this->getParentKeyName();
236 26
            $second = $this->getQualifiedLocalKeyName();
237 26
        } else {
238
            $first = $this->getLocalKeyName();
239
            $second = $this->qualifyColumn($this->getParentKeyName());
240
        }
241
242
        $query->join($name, $name.'.'.$first, '=', $second);
243
244
        if (!is_null($maxDepth)) {
245
            $query->where($this->getDepthName(), '<', $maxDepth);
246
        }
247
248
        return $query;
249
    }
250
}
251