Passed
Push — master ( 537b6a...321beb )
by Jonas
07:34
created

HasRecursiveRelationshipScopes::getInitialQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 11
c 1
b 0
f 0
dl 0
loc 18
ccs 12
cts 12
cp 1
rs 9.9
cc 1
nc 1
nop 4
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 RuntimeException;
7
use Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar;
8
use Staudenmeir\LaravelAdjacencyList\Query\Grammars\MySqlGrammar;
9
use Staudenmeir\LaravelAdjacencyList\Query\Grammars\PostgresGrammar;
10
use Staudenmeir\LaravelAdjacencyList\Query\Grammars\SQLiteGrammar;
11
use Staudenmeir\LaravelAdjacencyList\Query\Grammars\SqlServerGrammar;
12
13
trait HasRecursiveRelationshipScopes
14
{
15
    /**
16
     * Add a recursive expression for the relationship's whole tree to the query.
17
     *
18
     * @param \Illuminate\Database\Eloquent\Builder $query
19
     * @return \Illuminate\Database\Eloquent\Builder
20
     */
21 16
    public function scopeTree(Builder $query)
22
    {
23
        $constraint = function (Builder $query) {
24 16
            $query->isRoot();
25 16
        };
26
27 16
        return $query->withRelationshipExpression('desc', $constraint, 0);
28
    }
29
30
    /**
31
     * Limit the query to models with children.
32
     *
33
     * @param \Illuminate\Database\Eloquent\Builder $query
34
     * @return \Illuminate\Database\Eloquent\Builder
35
     */
36 4
    public function scopeHasChildren(Builder $query)
37
    {
38 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

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

39
            ->select($this->/** @scrutinizer ignore-call */ getParentKeyName())
Loading history...
40 4
            ->hasParent();
41
42 4
        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

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

94
        return $query->where($this->/** @scrutinizer ignore-call */ getDepthName(), ...$arguments);
Loading history...
95
    }
96
97
    /**
98
     * Order the query breadth-first.
99
     *
100
     * @param \Illuminate\Database\Eloquent\Builder $query
101
     * @return \Illuminate\Database\Eloquent\Builder
102
     */
103 4
    public function scopeBreadthFirst(Builder $query)
104
    {
105 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...
106
    }
107
108
    /**
109
     * Order the query depth-first.
110
     *
111
     * @param \Illuminate\Database\Eloquent\Builder $query
112
     * @return \Illuminate\Database\Eloquent\Builder
113
     */
114 8
    public function scopeDepthFirst(Builder $query)
115
    {
116 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

116
        return $query->orderBy($this->/** @scrutinizer ignore-call */ getPathName());
Loading history...
117
    }
118
119
    /**
120
     * Add a recursive expression for the relationship to the query.
121
     *
122
     * @param \Illuminate\Database\Eloquent\Builder $query
123
     * @param string $direction
124
     * @param callable $constraint
125
     * @param int $initialDepth
126
     * @param string|null $from
127
     * @return \Illuminate\Database\Eloquent\Builder
128
     */
129 128
    public function scopeWithRelationshipExpression(Builder $query, $direction, callable $constraint, $initialDepth, $from = null)
130
    {
131 128
        $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

131
        $from = $from ?: $this->/** @scrutinizer ignore-call */ getTable();
Loading history...
132
133 128
        $grammar = $this->getExpressionGrammar($query);
134
135 128
        $expression = $this->getInitialQuery($grammar, $constraint, $initialDepth, $from)
136 128
            ->unionAll(
137 128
                $this->getRecursiveQuery($grammar, $direction, $from)
138
            );
139
140 128
        $name = $this->getExpressionName();
0 ignored issues
show
Bug introduced by
The method getExpressionName() does not exist on Staudenmeir\LaravelAdjac...rsiveRelationshipScopes. Did you maybe mean getExpressionGrammar()? ( Ignorable by Annotation )

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

140
        /** @scrutinizer ignore-call */ 
141
        $name = $this->getExpressionName();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
141
142 128
        $query->getModel()->setTable($name);
143
144 128
        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...
145
    }
146
147
    /**
148
     * Get the initial query for a relationship expression.
149
     *
150
     * @param \Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar|\Illuminate\Database\Grammar $grammar
151
     * @param callable $constraint
152
     * @param int $initialDepth
153
     * @param string $from
154
     * @return \Illuminate\Database\Eloquent\Builder $query
155
     */
156 128
    protected function getInitialQuery(ExpressionGrammar $grammar, callable $constraint, $initialDepth, $from)
157
    {
158 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

158
        /** @scrutinizer ignore-call */ 
159
        $depth = $grammar->wrap($this->getDepthName());
Loading history...
159
160 128
        $initialPath = $grammar->compileInitialPath(
161 128
            $this->getLocalKeyName(),
162 128
            $this->getPathName()
163
        );
164
165 128
        $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

165
        $query = $this->/** @scrutinizer ignore-call */ newModelQuery()
Loading history...
166 128
            ->select('*')
167 128
            ->selectRaw($initialDepth.' as '.$depth)
168 128
            ->selectRaw($initialPath)
169 128
            ->from($from);
170
171 128
        $constraint($query);
172
173 128
        return $query;
174
    }
175
176
    /**
177
     * Get the recursive query for a relationship expression.
178
     *
179
     * @param \Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar|\Illuminate\Database\Grammar $grammar
180
     * @param string $direction
181
     * @param string $from
182
     * @return \Illuminate\Database\Eloquent\Builder $query
183
     */
184 128
    protected function getRecursiveQuery(ExpressionGrammar $grammar, $direction, $from)
185
    {
186 128
        $name = $this->getExpressionName();
187
188 128
        $table = explode(' as ', $from)[1] ?? $from;
189
190 128
        $depth = $grammar->wrap($this->getDepthName());
191
192 128
        $recursiveDepth = $grammar->wrap($this->getDepthName()).' '.($direction === 'asc' ? '-' : '+').' 1';
193
194 128
        $recursivePath = $grammar->compileRecursivePath(
195 128
            $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

195
            $this->/** @scrutinizer ignore-call */ 
196
                   getQualifiedLocalKeyName(),
Loading history...
196 128
            $this->getPathName(),
197 128
            $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

197
            $this->/** @scrutinizer ignore-call */ 
198
                   getPathSeparator()
Loading history...
198
        );
199
200 128
        $query = $this->newModelQuery()
201 128
            ->select($table.'.*')
202 128
            ->selectRaw($recursiveDepth.' as '.$depth)
203 128
            ->selectRaw($recursivePath)
204 128
            ->from($from);
205
206 128
        if ($direction === 'asc') {
207 58
            $first = $this->getParentKeyName();
208 58
            $second = $this->getQualifiedLocalKeyName();
209
        } else {
210 82
            $first = $this->getLocalKeyName();
211 82
            $second = $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

211
            /** @scrutinizer ignore-call */ 
212
            $second = $this->qualifyColumn($this->getParentKeyName());
Loading history...
212
        }
213
214 128
        $query->join($name, $name.'.'.$first, '=', $second);
215
216 128
        return $query;
217
    }
218
219
    /**
220
     * Get the expression grammar.
221
     *
222
     * @param \Illuminate\Database\Eloquent\Builder $query
223
     * @return \Staudenmeir\LaravelAdjacencyList\Query\Grammars\ExpressionGrammar
224
     */
225 128
    protected function getExpressionGrammar(Builder $query)
226
    {
227 128
        $driver = $query->getConnection()->getDriverName();
228
229 128
        switch ($driver) {
230 128
            case 'mysql':
231 34
                return $query->getConnection()->withTablePrefix(new MySqlGrammar);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->getConnec...rammars\MySqlGrammar()) also could return the type Illuminate\Database\Conn...tabase\Eloquent\Builder which is incompatible with the documented return type Staudenmeir\LaravelAdjac...mmars\ExpressionGrammar.
Loading history...
232 94
            case 'pgsql':
233 34
                return $query->getConnection()->withTablePrefix(new PostgresGrammar);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->getConnec...mars\PostgresGrammar()) also could return the type Illuminate\Database\Conn...tabase\Eloquent\Builder which is incompatible with the documented return type Staudenmeir\LaravelAdjac...mmars\ExpressionGrammar.
Loading history...
234 60
            case 'sqlite':
235 34
                return $query->getConnection()->withTablePrefix(new SQLiteGrammar);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->getConnec...ammars\SQLiteGrammar()) also could return the type Illuminate\Database\Conn...tabase\Eloquent\Builder which is incompatible with the documented return type Staudenmeir\LaravelAdjac...mmars\ExpressionGrammar.
Loading history...
236 26
            case 'sqlsrv':
237 26
                return $query->getConnection()->withTablePrefix(new SqlServerGrammar);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->getConnec...ars\SqlServerGrammar()) also could return the type Illuminate\Database\Conn...tabase\Eloquent\Builder which is incompatible with the documented return type Staudenmeir\LaravelAdjac...mmars\ExpressionGrammar.
Loading history...
238
        }
239
240
        throw new RuntimeException('This database is not supported.'); // @codeCoverageIgnore
241
    }
242
}
243