Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Passed
Push — optimize-queries ( e16a2f...539379 )
by Pedro
15:02
created

Query::getQueryCount()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 1
Metric Value
cc 1
eloc 1
c 3
b 1
f 1
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace Backpack\CRUD\app\Library\CrudPanel\Traits;
4
5
use Illuminate\Database\Eloquent\Builder;
6
7
trait Query
8
{
9
    /** @var Builder */
10
    public $query;
11
12
    /** @var Builder */
13
    public $totalQuery;
14
15
    /** @var array */
16
    private $ranQueries;
17
18
    // ----------------
19
    // ADVANCED QUERIES
20
    // ----------------
21
22
    /**
23
     * Add another clause to the query (for ex, a WHERE clause).
24
     *
25
     * Examples:
26
     * $this->crud->addClause('active');
27
     * $this->crud->addClause('type', 'car');
28
     * $this->crud->addClause('where', 'name', '=', 'car');
29
     * $this->crud->addClause('whereName', 'car');
30
     * $this->crud->addClause('whereHas', 'posts', function($query) {
31
     *     $query->activePosts();
32
     * });
33
     *
34
     * @param  callable|string  $function
35
     * @return mixed
36
     */
37
    public function addClause($function)
38
    {
39
        if ($function instanceof \Closure) {
40
            $function($this->query);
41
42
            return $this->query;
43
        }
44
45
        return call_user_func_array([$this->query, $function], array_slice(func_get_args(), 1));
46
    }
47
48
    /**
49
     * Check if a query has been ran before.
50
     *
51
     * @param  string  $query
52
     * @return bool
53
     */
54
    public function hasQuery(string $query)
55
    {
56
        return isset($this->ranQueries[$query]);
57
    }
58
59
    /**
60
     * Get the previously ran query.
61
     *
62
     * @param  string  $query
63
     * @return void
64
     */
65
    public function getQuery(string $query)
66
    {
67
        abort_if(! $this->hasQuery($query), 500, 'Trying to get an unexisting query.');
68
69
        return $this->ranQueries[$query];
70
    }
71
72
    /**
73
     * Clears the query cache.
74
     *
75
     * @return void
76
     */
77
    public function flushQueryCache()
78
    {
79
        $this->ranQueries = [];
80
    }
81
82
    /**
83
     * Saves a query that will be returned if the same query is ran again.
84
     *
85
     * @param  string  $query
86
     * @param  array  $result
87
     * @return void
88
     */
89
    public function saveQuery(string $query, array $result)
90
    {
91
        $this->ranQueries[$query] = $result;
92
    }
93
94
    /**
95
     * This function is an alias of `addClause` but also adds the query as a constrain
96
     * in the `totalQuery` property.
97
     *
98
     * @param  \Closure|string  $function
99
     * @return self
100
     */
101
    public function addBaseClause($function)
102
    {
103
        if ($function instanceof \Closure) {
104
            $function($this->query);
105
            $function($this->totalQuery);
106
107
            return $this;
108
        }
109
        call_user_func_array([$this->query, $function], array_slice(func_get_args(), 1));
110
        call_user_func_array([$this->totalQuery, $function], array_slice(func_get_args(), 1));
111
112
        return $this;
113
    }
114
115
    /**
116
     * Use eager loading to reduce the number of queries on the table view.
117
     *
118
     * @param  array|string  $entities
119
     * @return \Illuminate\Database\Eloquent\Builder
120
     */
121
    public function with($entities)
122
    {
123
        return $this->query->with($entities);
124
    }
125
126
    /**
127
     * Order the results of the query in a certain way.
128
     *
129
     * @param  string  $field
130
     * @param  string  $order
131
     * @return \Illuminate\Database\Eloquent\Builder
132
     */
133
    public function orderBy($field, $order = 'asc')
134
    {
135
        if ($this->getRequest()->has('order')) {
0 ignored issues
show
Bug introduced by
It seems like getRequest() 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

135
        if ($this->/** @scrutinizer ignore-call */ getRequest()->has('order')) {
Loading history...
136
            return $this->query;
137
        }
138
139
        return $this->query->orderBy($field, $order);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->query->orderBy($field, $order) also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
140
    }
141
142
    /**
143
     * Order results of the query in a custom way.
144
     *
145
     * @param  array  $column  Column array with all attributes
146
     * @param  string  $column_direction  ASC or DESC
147
     * @return \Illuminate\Database\Eloquent\Builder
148
     */
149
    public function customOrderBy($column, $columnDirection = 'asc')
150
    {
151
        if (! isset($column['orderLogic'])) {
152
            return $this->query;
153
        }
154
155
        $orderLogic = $column['orderLogic'];
156
157
        if (is_callable($orderLogic)) {
158
            return $orderLogic($this->query, $column, $columnDirection);
159
        }
160
161
        return $this->query;
162
    }
163
164
    /**
165
     * Group the results of the query in a certain way.
166
     *
167
     * @param  string  $field
168
     * @return \Illuminate\Database\Eloquent\Builder
169
     */
170
    public function groupBy($field)
171
    {
172
        return $this->query->groupBy($field);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->query->groupBy($field) also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
173
    }
174
175
    /**
176
     * Limit the number of results in the query.
177
     *
178
     * @param  int  $number
179
     * @return \Illuminate\Database\Eloquent\Builder
180
     */
181
    public function limit($number)
182
    {
183
        return $this->query->limit($number);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->query->limit($number) also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
184
    }
185
186
    /**
187
     * Take a certain number of results from the query.
188
     *
189
     * @param  int  $number
190
     * @return \Illuminate\Database\Eloquent\Builder
191
     */
192
    public function take($number)
193
    {
194
        return $this->query->take($number);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->query->take($number) also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
195
    }
196
197
    /**
198
     * Start the result set from a certain number.
199
     *
200
     * @param  int  $number
201
     * @return \Illuminate\Database\Eloquent\Builder
202
     */
203
    public function skip($number)
204
    {
205
        return $this->query->skip($number);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->query->skip($number) also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
206
    }
207
208
    /**
209
     * Count the number of results.
210
     *
211
     * @return int
212
     */
213
    public function count()
214
    {
215
        return $this->query->count();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->query->count() also could return the type Illuminate\Database\Eloquent\Builder which is incompatible with the documented return type integer.
Loading history...
216
    }
217
218
    /**
219
     * Apply table prefix in the order clause if the query contains JOINS clauses.
220
     *
221
     * @param  string  $column_name
222
     * @param  string  $column_direction
223
     * @return \Illuminate\Database\Eloquent\Builder
224
     */
225
    public function orderByWithPrefix($column_name, $column_direction = 'ASC')
226
    {
227
        if ($this->query->getQuery()->joins !== null) {
228
            return $this->query->orderByRaw($this->model->getTableWithPrefix().'.'.$column_name.' '.$column_direction);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->query->ord... ' . $column_direction) also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
229
        }
230
231
        return $this->query->orderBy($column_name, $column_direction);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->query->ord...ame, $column_direction) also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
232
    }
233
234
    /**
235
     * Get the entries count from `totalQuery`.
236
     *
237
     * @return int
238
     */
239
    public function getTotalQueryCount()
240
    {
241
        if (! $this->getOperationSetting('showEntryCount')) {
0 ignored issues
show
Bug introduced by
It seems like getOperationSetting() 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

241
        if (! $this->/** @scrutinizer ignore-call */ getOperationSetting('showEntryCount')) {
Loading history...
242
            return 0;
243
        }
244
245
        return  $this->getOperationSetting('totalEntryCount') ??
246
                $this->getCountFromQuery($this->totalQuery);
247
    }
248
249
    /**
250
     * Get the entries count from the `query`.
251
     *
252
     * @return int
253
     */
254
    public function getQueryCount()
255
    {
256
        return $this->getCountFromQuery($this->query);
257
    }
258
259
    /**
260
     * Return the filtered query count or skip the counting when the `totalQuery` is the same as the filtered one.
261
     *
262
     * @return int|null
263
     */
264
    public function getFilteredQueryCount()
265
    {
266
        // check if the filtered query is different from total query, in case they are the same, skip the count
267
        $filteredQuery = $this->query->toBase()->cloneWithout(['orders', 'limit', 'offset']);
268
269
        return $filteredQuery->toSql() !== $this->totalQuery->toSql() ? $this->getQueryCount() : null;
270
    }
271
272
    /**
273
     * Do a separate query to get the total number of entries, in an optimized way.
274
     *
275
     * @param  Builder  $query
276
     * @return int
277
     */
278
    private function getCountFromQuery(Builder $query)
279
    {
280
        if (! $this->driverIsSql()) {
0 ignored issues
show
Bug introduced by
It seems like driverIsSql() 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

280
        if (! $this->/** @scrutinizer ignore-call */ driverIsSql()) {
Loading history...
281
            return $query->count();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->count() also could return the type Illuminate\Database\Eloquent\Builder which is incompatible with the documented return type integer.
Loading history...
282
        }
283
284
        $crudQuery = $query->toBase()->clone();
285
286
        $modelTable = $this->model->getTable();
287
288
        // create an "outer" query, the one that is responsible to do the count of the "crud query".
289
        $outerQuery = $crudQuery->newQuery();
290
291
        // add the count query in the "outer" query.
292
        $outerQuery = $outerQuery->selectRaw('count(*) as total_rows');
293
294
        // Expression columns are hand-written by developers in ->selectRaw() and we can't exclude those statements reliably
295
        // so we just store them and re-use them in the sub-query too.
296
        $expressionColumns = [];
297
298
        foreach ($crudQuery->columns as $column) {
299
            if (! is_string($column) && is_a($column, 'Illuminate\Database\Query\Expression')) {
300
                $expressionColumns[] = $column;
301
            }
302
        }
303
        // add the subquery from where the "outer query" will count the results.
304
        // this subquery is the "main crud query" without some properties:
305
        // - columns : we manually select the "minimum" columns possible from database.
306
        // - orders/limit/offset because we want the "full query count" where orders don't matter and limit/offset would break the total count
307
        $subQuery = $crudQuery->cloneWithout(['columns', 'orders', 'limit', 'offset']);
308
309
        // re-set the previous query bindings
310
        $subQuery->setBindings($crudQuery->getRawBindings());
311
312
        // select only one column for the count
313
        $subQuery->select($modelTable.'.'.$this->model->getKeyName());
314
315
        // in case there are raw expressions we need to add them too.
316
        foreach ($expressionColumns as $expression) {
317
            $subQuery->selectRaw($expression);
318
        }
319
320
        $outerQuery = $outerQuery->fromSub($subQuery, str_replace('.', '_', $modelTable).'_aggregator');
321
322
        return $outerQuery->cursor()->first()->total_rows;
323
    }
324
}
325