QueriesAranguentRelationships::withAggregate()   B
last analyzed

Complexity

Conditions 10
Paths 29

Size

Total Lines 72
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 10.0186

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 10
eloc 32
nc 29
nop 3
dl 0
loc 72
ccs 33
cts 35
cp 0.9429
crap 10.0186
rs 7.6666
c 2
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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));
0 ignored issues
show
Bug introduced by
sprintf('(COUNT(%s)) > 0...e : false ', $subquery) of type string is incompatible with the type Illuminate\Database\Query\TValue expected by parameter $value of Illuminate\Database\Quer...pression::__construct(). ( Ignorable by Annotation )

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

32
            $expression = new Expression(/** @scrutinizer ignore-type */ sprintf('(COUNT(%s)) > 0 ? true : false ', $subquery));
Loading history...
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 . ')'),
0 ignored issues
show
Bug introduced by
'LENGTH(' . $subquery . ')' of type string is incompatible with the type Illuminate\Database\Query\TValue expected by parameter $value of Illuminate\Database\Quer...pression::__construct(). ( Ignorable by Annotation )

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

85
            new Expression(/** @scrutinizer ignore-type */ 'LENGTH(' . $subquery . ')'),
Loading history...
86 1
            $operator,
87 1
            new Expression($count),
0 ignored issues
show
Bug introduced by
$count of type integer is incompatible with the type Illuminate\Database\Query\TValue expected by parameter $value of Illuminate\Database\Quer...pression::__construct(). ( Ignorable by Annotation )

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

87
            new Expression(/** @scrutinizer ignore-type */ $count),
Loading history...
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 25
    public function mergeConstraintsFrom(Builder $from)
99
    {
100 25
        $whereBindings = $this->getQuery()->getBindings();
101
102 25
        $wheres = $from->getQuery()->from !== $this->getQuery()->from
103 25
            ? $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 25
                $from->getQuery()->wheres,
105 25
                (string) $from->getQuery()->grammar->getValue($from->getQuery()->from),
106 25
                $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 25
            ) : $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 25
        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 25
            $from->removedScopes(),
114 25
        )->mergeWheres(
115 25
            $wheres,
116 25
            $whereBindings,
117 25
        );
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
     * @SuppressWarnings("PHPMD.CyclomaticComplexity")
129
     */
130 192
    public function withAggregate($relations, $column, $function = null)
131
    {
132 192
        if (empty($relations)) {
133 192
            return $this;
134
        }
135
136 2
        $table = (string) $this->query->grammar->getValue($this->query->from);
137
138 2
        if (empty($this->query->columns)) {
139 2
            $this->query->select([$table . '.*']);
140
        }
141
142 2
        $relations = is_array($relations) ? $relations : [$relations];
143
144 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

144
        foreach ($this->/** @scrutinizer ignore-call */ parseWithRelations($relations) as $name => $constraints) {
Loading history...
145
            // First we will determine if the name has been aliased using an "as" clause on the name
146
            // and if it has we will extract the actual relationship name and the desired name of
147
            // the resulting column. This allows multiple aggregates on the same relationships.
148 2
            $segments = explode(' ', $name);
149
150 2
            [$name, $alias] = $this->extractNameAndAlias($segments, $name);
151
152 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

152
            /** @scrutinizer ignore-call */ 
153
            $relation = $this->getRelationWithoutConstraints((string) $name);
Loading history...
153
154 2
            $expression = $column;
155
156 2
            if ($function) {
157 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

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