Passed
Push — refactor/improve-static-analys... ( efcf20...37f12c )
by Bas
03:04
created

QueriesAranguentRelationships   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 184
Duplicated Lines 0 %

Test Coverage

Coverage 93.9%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 72
c 2
b 0
f 0
dl 0
loc 184
ccs 77
cts 82
cp 0.939
rs 10
wmc 18

5 Methods

Rating   Name   Duplication   Size   Complexity  
A handleAggregateFunction() 0 34 3
A mergeConstraintsFrom() 0 19 2
B withAggregate() 0 69 9
A addWhereCountQuery() 0 9 1
A extractNameAndAlias() 0 8 3
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
     */
20 2
    public function handleAggregateFunction(IlluminateQueryBuilder $query, mixed $function, string $alias): void
21
    {
22 2
        if ($function === null) {
23
            $query->limit(1);
24
25
            return;
26
        }
27
28
29 2
        if ($function === 'exists') {
30 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

30
            [$subquery] = $this->/** @scrutinizer ignore-call */ getQuery()->createSub($query);
Loading history...
31
32 1
            $expression = new Expression(sprintf('(COUNT(%s)) > 0 ? true : false ', $subquery));
33
34 1
            $this->getQuery()->set(
35 1
                $alias,
36 1
                $expression,
37 1
                'postIterationVariables'
38 1
            )
39 1
                ->addSelect($alias);
40
41 1
            return;
42
        }
43
44
45 1
        [$subquery] = $this->getQuery()->createSub($query);
46
47 1
        $this->getQuery()->set(
48 1
            $alias,
49 1
            new Expression(strtoupper($function) . '(' . $subquery . ')'),
50 1
            'postIterationVariables'
51 1
        );
52
53 1
        $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
56
    /**
57
     * @param array<string> $segments
58
     * @param string $name
59
     * @return array<int, string|null>
60
     */
61 2
    public function extractNameAndAlias(array $segments, string $name): array
62
    {
63 2
        $alias = null;
64
65 2
        if (count($segments) === 3 && Str::lower($segments[1]) === 'as') {
66
            [$name, $alias] = [$segments[0], $segments[2]];
67
        }
68 2
        return [$name, $alias];
69
    }
70
71
    /**
72
     * Add a sub-query count clause to this query.
73
     *
74
     * @param  \Illuminate\Database\Query\Builder  $query
75
     * @param  string  $operator
76
     * @param  int  $count
77
     * @param  string  $boolean
78
     * @return self
79
     */
80 1
    protected function addWhereCountQuery(IlluminateQueryBuilder $query, $operator = '>=', $count = 1, $boolean = 'and')
81
    {
82 1
        [$subquery] = $this->getQuery()->createSub($query);
83
84 1
        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 1
            new Expression('LENGTH(' . $subquery . ')'),
86 1
            $operator,
87 1
            new Expression($count),
88 1
            $boolean
89 1
        );
90
    }
91
92
    /**
93
     * Merge the where constraints from another query to the current query.
94
     *
95
     * @param IlluminateEloquentBuilder $from
96
     * @return IlluminateEloquentBuilder|static
97
     */
98 7
    public function mergeConstraintsFrom(Builder $from)
99
    {
100 7
        $whereBindings = $this->getQuery()->getBindings();
101
102 7
        $wheres = $from->getQuery()->from !== $this->getQuery()->from
103 7
            ? $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 7
                $from->getQuery()->wheres,
105 7
                (string) $from->getQuery()->grammar->getValue($from->getQuery()->from),
106 7
                $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 7
            ) : $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
        // query might have removed. Then we will return ourselves with the finished merging.
112 7
        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 7
            $from->removedScopes()
114 7
        )->mergeWheres(
115 7
            $wheres,
116 7
            $whereBindings
117 7
        );
118
    }
119
120
    /**
121
     * Add subselect queries to include an aggregate value for a relationship.
122
     *
123
     * @param  mixed  $relations
124
     * @param  string  $column
125
     * @param  string  $function
126
     * @return $this
127
     */
128 87
    public function withAggregate($relations, $column, $function = null)
129
    {
130 87
        if (empty($relations)) {
131 87
            return $this;
132
        }
133
134 2
        if (empty($this->query->columns)) {
135 2
            $this->query->select([$this->query->from . '.*']);
136
        }
137
138 2
        $relations = is_array($relations) ? $relations : [$relations];
139
140 2
        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 2
            $segments = explode(' ', $name);
145
146 2
            [$name, $alias] = $this->extractNameAndAlias($segments, $name);
147
148 2
            $relation = $this->getRelationWithoutConstraints((string) $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((string) $name);
Loading history...
149
150 2
            $expression = $column;
151
152 2
            if ($function) {
153 2
                $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 2
                $wrappedColumn = $this->getQuery()->getGrammar()->wrap(
156 2
                    $column === '*' ? $column : $relation->getRelated()->qualifyColumn($hashedColumn)
157 2
                );
158
159 2
                $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 2
            $query = $relation->getRelationExistenceQuery(
166 2
                $relation->getRelated()->newQuery(),
167 2
                $this,
168 2
                new Expression($expression)
169 2
            )->setBindings([], 'select');
170
171 2
            $query->callScope($constraints);
172
173 2
            $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 2
            unset($query->orders);
179 2
            $query->setBindings([], 'order');
180
181 2
            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 2
            $alias = Str::snake(
190 2
                (string) preg_replace('/[^[:alnum:][:space:]_]/u', '', "$name $function $column")
191 2
            );
192
193 2
            $this->handleAggregateFunction($query, $function, $alias);
194
        }
195
196 2
        return $this;
197
    }
198
}
199