Passed
Push — master ( c3d33f...4e5334 )
by Jonas
05:39
created

HasRecursiveRelationshipScopes::scopeHasParent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
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 20
    public function scopeTree(Builder $query, $maxDepth = null)
18
    {
19
        $constraint = function (Builder $query) {
20 20
            $query->isRoot();
21 20
        };
22
23 20
        return $query->treeOf($constraint, $maxDepth);
24
    }
25
26
    /**
27
     * 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 28
    public function scopeTreeOf(Builder $query, callable $constraint, $maxDepth = null)
35
    {
36 28
        return $query->withRelationshipExpression('desc', $constraint, 0, null, $maxDepth);
37
    }
38
39
    /**
40
     * Limit the query to models with children.
41
     *
42
     * @param \Illuminate\Database\Eloquent\Builder $query
43
     * @return \Illuminate\Database\Eloquent\Builder
44
     */
45 4
    public function scopeHasChildren(Builder $query)
46
    {
47 4
        $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 4
            ->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 4
            ->hasParent();
50
51 4
        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
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 12
    public function scopeHasParent(Builder $query)
61
    {
62 12
        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
65
    /**
66
     * Limit the query to leaf models.
67
     *
68
     * @param \Illuminate\Database\Eloquent\Builder $query
69
     * @return \Illuminate\Database\Eloquent\Builder
70
     */
71 4
    public function scopeIsLeaf(Builder $query)
72
    {
73 4
        $keys = (new static)->newQuery()
74 4
            ->select($this->getParentKeyName())
75 4
            ->hasParent();
76
77 4
        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
80
    /**
81
     * Limit the query to root models.
82
     *
83
     * @param \Illuminate\Database\Eloquent\Builder $query
84
     * @return \Illuminate\Database\Eloquent\Builder
85
     */
86 44
    public function scopeIsRoot(Builder $query)
87
    {
88 44
        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
91
    /**
92
     * Limit the query by depth.
93
     *
94
     * @param \Illuminate\Database\Eloquent\Builder $query
95
     * @param mixed $operator
96
     * @param mixed $value
97
     * @return \Illuminate\Database\Eloquent\Builder
98
     */
99 16
    public function scopeWhereDepth(Builder $query, $operator, $value = null)
100
    {
101 16
        $arguments = array_slice(func_get_args(), 1);
102
103 16
        return $query->where($this->getDepthName(), ...$arguments);
0 ignored issues
show
Bug introduced by
It seems like getDepthName() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

103
        return $query->where($this->/** @scrutinizer ignore-call */ getDepthName(), ...$arguments);
Loading history...
104
    }
105
106
    /**
107
     * Order the query breadth-first.
108
     *
109
     * @param \Illuminate\Database\Eloquent\Builder $query
110
     * @return \Illuminate\Database\Eloquent\Builder
111
     */
112 4
    public function scopeBreadthFirst(Builder $query)
113
    {
114 4
        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
117
    /**
118
     * Order the query depth-first.
119
     *
120
     * @param \Illuminate\Database\Eloquent\Builder $query
121
     * @return \Illuminate\Database\Eloquent\Builder
122
     */
123 8
    public function scopeDepthFirst(Builder $query)
124
    {
125 8
        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
     * Add a recursive expression for the relationship to the query.
130
     *
131
     * @param \Illuminate\Database\Eloquent\Builder $query
132
     * @param string $direction
133
     * @param callable $constraint
134
     * @param int $initialDepth
135
     * @param string|null $from
136
     * @param int|null $maxDepth
137
     * @return \Illuminate\Database\Eloquent\Builder
138
     */
139 166
    public function scopeWithRelationshipExpression(Builder $query, $direction, callable $constraint, $initialDepth, $from = null, $maxDepth = null)
140
    {
141 166
        $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
143 166
        $grammar = $query->getExpressionGrammar();
144
145 166
        $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 166
            ->unionAll(
147 166
                $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 166
        $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 166
        $query->getModel()->setTable($name);
153
154 166
        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
157
    /**
158
     * Get the initial query for a relationship expression.
159
     *
160
     * @param \Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar|\Illuminate\Database\Grammar $grammar
161
     * @param callable $constraint
162
     * @param int $initialDepth
163
     * @param string $from
164
     * @return \Illuminate\Database\Eloquent\Builder $query
165
     */
166 166
    protected function getInitialQuery(ExpressionGrammar $grammar, callable $constraint, $initialDepth, $from)
167
    {
168 166
        $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
170 166
        $initialPath = $grammar->compileInitialPath(
171 166
            $this->getLocalKeyName(),
172 166
            $this->getPathName()
173
        );
174
175 166
        $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 166
            ->select('*')
177 166
            ->selectRaw($initialDepth.' as '.$depth)
178 166
            ->selectRaw($initialPath)
179 166
            ->from($from);
180
181 166
        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 166
            $query->selectRaw(
183 166
                $grammar->compileInitialPath($path['column'], $path['name'])
184
            );
185
        }
186
187 166
        $constraint($query);
188
189 166
        return $query;
190
    }
191
192
    /**
193
     * Get the recursive query for a relationship expression.
194
     *
195
     * @param \Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar|\Illuminate\Database\Grammar $grammar
196
     * @param string $direction
197
     * @param string $from
198
     * @param int|null $maxDepth
199
     * @return \Illuminate\Database\Eloquent\Builder $query
200
     */
201 166
    protected function getRecursiveQuery(ExpressionGrammar $grammar, $direction, $from, $maxDepth = null)
202
    {
203 166
        $name = $this->getExpressionName();
204
205 166
        $table = explode(' as ', $from)[1] ?? $from;
206
207 166
        $depth = $grammar->wrap($this->getDepthName());
208
209 166
        $recursiveDepth = $grammar->wrap($this->getDepthName()).' '.($direction === 'asc' ? '-' : '+').' 1';
210
211 166
        $recursivePath = $grammar->compileRecursivePath(
212 166
            $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 166
            $this->getPathName()
214
        );
215
216 166
        $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 166
        $query = $this->newModelQuery()
219 166
            ->select($table.'.*')
220 166
            ->selectRaw($recursiveDepth.' as '.$depth)
221 166
            ->selectRaw($recursivePath, $recursivePathBindings)
222 166
            ->from($from);
223
224 166
        foreach ($this->getCustomPaths() as $path) {
225 166
            $query->selectRaw(
226 166
                $grammar->compileRecursivePath(
227 166
                    $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 166
                    $path['name']
229
                ),
230 166
                $grammar->getRecursivePathBindings($path['separator'])
231
            );
232
        }
233
234 166
        if ($direction === 'asc') {
235 84
            $first = $this->getParentKeyName();
236 84
            $second = $this->getQualifiedLocalKeyName();
237
        } else {
238 96
            $first = $this->getLocalKeyName();
239 96
            $second = $this->qualifyColumn($this->getParentKeyName());
240
        }
241
242 166
        $query->join($name, $name.'.'.$first, '=', $second);
243
244 166
        if (!is_null($maxDepth)) {
245 8
            $query->where($this->getDepthName(), '<', $maxDepth);
246
        }
247
248 166
        return $query;
249
    }
250
}
251