Passed
Push — refactor/improve-static-analys... ( bb4224...d678be )
by
unknown
07:03
created

extractNameAndAlias()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 4
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 8
ccs 2
cts 2
cp 1
crap 3
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace LaravelFreelancerNL\Aranguent\Eloquent\Concerns;
6
7
use Illuminate\Database\Eloquent\Builder;
8
use Illuminate\Database\Eloquent\Builder as IlluminateEloquentBuilder;
9
use Illuminate\Database\Query\Builder as IlluminateQueryBuilder;
10
use Illuminate\Database\Query\Expression;
11
use Illuminate\Support\Str;
12
13
trait QueriesAranguentRelationships
14
{
15
    /**
16
     * @param mixed $function
17
     * @param IlluminateQueryBuilder $query
18
     * @param string $alias
19
     * @return Expression
20
     */
21
    public function handleAggregateFunction(IlluminateQueryBuilder $query, mixed $function, string $alias): void
22
    {
23
        if ($function === null) {
24 1
            $query->limit(1);
25
26
            return;
27
        }
28
29
30
        if ($function === 'exists') {
31 1
            [$subquery] = $this->getQuery()->createSub($query);
0 ignored issues
show
Bug introduced by
It seems like getQuery() 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

31
            [$subquery] = $this->/** @scrutinizer ignore-call */ getQuery()->createSub($query);
Loading history...
32
33 1
            $expression = new Expression(sprintf('(COUNT(%s)) > 0 ? true : false ', $subquery));
34 1
35
            $this->getQuery()->set(
36 1
                $alias,
37
                $expression,
38
                'postIterationVariables'
39
            )
40
                ->addSelect($alias);
41
42
            return;
43
        }
44
45
46
        [$subquery] = $this->getQuery()->createSub($query);
47
48
        $this->getQuery()->set(
49
            $alias,
50
            new Expression(strtoupper($function) . '(' . $subquery . ')'),
51 137
            'postIterationVariables'
52
        );
53 137
        $this->addSelect($alias);
0 ignored issues
show
Bug introduced by
It seems like addSelect() 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

53
        $this->/** @scrutinizer ignore-call */ 
54
               addSelect($alias);
Loading history...
54
    }
55 137
56 137
    /**
57
     * @param array $segments
58
     * @param string $name
59 1
     * @return array
60 1
     */
61
    public function extractNameAndAlias(array $segments, string $name): array
62
    {
63 1
        $alias = null;
64
65 1
        if (count($segments) === 3 && Str::lower($segments[1]) === 'as') {
66
            [$name, $alias] = [$segments[0], $segments[2]];
67
        }
68
        return [$name, $alias];
69 1
    }
70
71 1
    /**
72
     * Add a sub-query count clause to this query.
73 1
     *
74
     * @param  \Illuminate\Database\Query\Builder  $query
75
     * @param  string  $operator
76
     * @param  int  $count
77 1
     * @param  string  $boolean
78
     * @return $this
79
     */
80
    protected function addWhereCountQuery(IlluminateQueryBuilder $query, $operator = '>=', $count = 1, $boolean = 'and')
81
    {
82 1
        [$subquery] = $this->getQuery()->createSub($query);
83 1
84
        return $this->where(
0 ignored issues
show
Bug introduced by
It seems like where() 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
        return $this->/** @scrutinizer ignore-call */ where(
Loading history...
85
            new Expression('LENGTH(' . $subquery . ')'),
86
            $operator,
87 1
            is_numeric($count) ? new Expression($count) : $count,
0 ignored issues
show
introduced by
The condition is_numeric($count) is always true.
Loading history...
88
            $boolean
89 1
        );
90
    }
91
92
    /**
93
     * Merge the where constraints from another query to the current query.
94 1
     *
95
     * @param IlluminateEloquentBuilder $from
96 1
     * @return IlluminateEloquentBuilder|static
97
     */
98
    public function mergeConstraintsFrom(Builder $from)
99
    {
100
        $whereBindings = $this->getQuery()->getBindings() ?? [];
101 1
102
        $wheres = $from->getQuery()->from !== $this->getQuery()->from
103
            ? $this->requalifyWhereTables(
0 ignored issues
show
Bug introduced by
It seems like requalifyWhereTables() 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
            ? $this->/** @scrutinizer ignore-call */ requalifyWhereTables(
Loading history...
104
                $from->getQuery()->wheres,
105
                $from->getQuery()->grammar->getValue($from->getQuery()->from),
106 1
                $this->getModel()->getTable()
0 ignored issues
show
Bug introduced by
It seems like getModel() 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

106
                $this->/** @scrutinizer ignore-call */ 
107
                       getModel()->getTable()
Loading history...
107 1
            ) : $from->getQuery()->wheres;
108
109
        // Here we have some other query that we want to merge the where constraints from. We will
110
        // copy over any where constraints on the query as well as remove any global scopes the
111 1
        // query might have removed. Then we will return ourselves with the finished merging.
112 1
        return $this->withoutGlobalScopes(
0 ignored issues
show
Bug introduced by
It seems like withoutGlobalScopes() 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

112
        return $this->/** @scrutinizer ignore-call */ withoutGlobalScopes(
Loading history...
113
            $from->removedScopes()
114
        )->mergeWheres(
115
            $wheres,
116
            $whereBindings
117 1
        );
118
    }
119
120 1
    /**
121
     * Add subselect queries to include an aggregate value for a relationship.
122 1
     *
123 1
     * @param  mixed  $relations
124 1
     * @param  string  $column
125
     * @param  string  $function
126 1
     * @return $this
127
     */
128
    public function withAggregate($relations, $column, $function = null)
129
    {
130
        if (empty($relations)) {
131 1
            return $this;
132
        }
133
134
        if (is_null($this->query->columns)) {
135
            $this->query->select([$this->query->from . '.*']);
136
        }
137
138
        $relations = is_array($relations) ? $relations : [$relations];
139
140
        foreach ($this->parseWithRelations($relations) as $name => $constraints) {
0 ignored issues
show
Bug introduced by
It seems like parseWithRelations() 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

140
        foreach ($this->/** @scrutinizer ignore-call */ parseWithRelations($relations) as $name => $constraints) {
Loading history...
141
            // First we will determine if the name has been aliased using an "as" clause on the name
142
            // and if it has we will extract the actual relationship name and the desired name of
143
            // the resulting column. This allows multiple aggregates on the same relationships.
144
            $segments = explode(' ', $name);
145
146
            [$name, $alias] = $this->extractNameAndAlias($segments, $name);
147
148
            $relation = $this->getRelationWithoutConstraints($name);
0 ignored issues
show
Bug introduced by
It seems like getRelationWithoutConstraints() 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

148
            /** @scrutinizer ignore-call */ 
149
            $relation = $this->getRelationWithoutConstraints($name);
Loading history...
149
150
            $expression = $column;
151
152
            if ($function) {
153
                $hashedColumn = $this->getRelationHashedColumn($column, $relation);
0 ignored issues
show
Bug introduced by
It seems like getRelationHashedColumn() 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
                $hashedColumn = $this->getRelationHashedColumn($column, $relation);
Loading history...
154
155
                $wrappedColumn = $this->getQuery()->getGrammar()->wrap(
156
                    $column === '*' ? $column : $relation->getRelated()->qualifyColumn($hashedColumn)
157
                );
158
159
                $expression = $function === 'exists' ? $wrappedColumn : sprintf('%s(%s)', $function, $wrappedColumn);
160
            }
161
162
            // Here, we will grab the relationship sub-query and prepare to add it to the main query
163
            // as a sub-select. First, we'll get the "has" query and use that to get the relation
164
            // sub-query. We'll format this relationship name and append this column if needed.
165
            $query = $relation->getRelationExistenceQuery(
166
                $relation->getRelated()->newQuery(),
167
                $this,
168
                new Expression($expression)
169
            )->setBindings([], 'select');
170
171
            $query->callScope($constraints);
172
173
            $query = $query->mergeConstraintsFrom($relation->getQuery())->toBase();
174
175
            // If the query contains certain elements like orderings / more than one column selected
176
            // then we will remove those elements from the query so that it will execute properly
177
            // when given to the database. Otherwise, we may receive SQL errors or poor syntax.
178
            $query->orders = null;
179
            $query->setBindings([], 'order');
180
181
            if (count($query->columns) > 1) {
182
                $query->columns = [$query->columns[0]];
183
                $query->bindings['select'] = [];
184
            }
185
186
            // Finally, we will make the proper column alias to the query and run this sub-select on
187
            // the query builder. Then, we will return the builder instance back to the developer
188
            // for further constraint chaining that needs to take place on the query as needed.
189
            $alias ??= Str::snake(
190
                preg_replace('/[^[:alnum:][:space:]_]/u', '', "$name $function $column")
191
            );
192
193
            $this->handleAggregateFunction($query, $function, $alias);
194
        }
195
196
        return $this;
197
    }
198
}
199