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

Test Failed
Push — optimize-queries ( 9b3855 )
by Pedro
12:20
created

Query::take()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
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 boolean
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
     * Saves a query that will be returned if the same query is ran again.
74
     *
75
     * @param string $query
76
     * @param array $result
77
     * @return void
78
     */
79
    public function saveQuery(string $query, array $result)
80
    {
81
        $this->ranQueries[$query] = $result;
82
    }
83
84
    /**
85
     * This function is an alias of `addClause` but also adds the query as a constrain
86
     * in the `totalQuery` property.
87
     *
88
     * @param  \Closure|string  $function
89
     * @return self
90
     */
91
    public function addBaseClause($function)
92
    {
93
        if ($function instanceof \Closure) {
94
            $function($this->query);
95
            $function($this->totalQuery);
96
97
            return $this;
98
        }
99
        call_user_func_array([$this->query, $function], array_slice(func_get_args(), 1));
100
        call_user_func_array([$this->totalQuery, $function], array_slice(func_get_args(), 1));
101
102
        return $this;
103
    }
104
105
    /**
106
     * Use eager loading to reduce the number of queries on the table view.
107
     *
108
     * @param  array|string  $entities
109
     * @return \Illuminate\Database\Eloquent\Builder
110
     */
111
    public function with($entities)
112
    {
113
        return $this->query->with($entities);
114
    }
115
116
    /**
117
     * Order the results of the query in a certain way.
118
     *
119
     * @param  string  $field
120
     * @param  string  $order
121
     * @return \Illuminate\Database\Eloquent\Builder
122
     */
123
    public function orderBy($field, $order = 'asc')
124
    {
125
        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

125
        if ($this->/** @scrutinizer ignore-call */ getRequest()->has('order')) {
Loading history...
126
            return $this->query;
127
        }
128
129
        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...
130
    }
131
132
    /**
133
     * Order results of the query in a custom way.
134
     *
135
     * @param  array  $column  Column array with all attributes
136
     * @param  string  $column_direction  ASC or DESC
137
     * @return \Illuminate\Database\Eloquent\Builder
138
     */
139
    public function customOrderBy($column, $columnDirection = 'asc')
140
    {
141
        if (! isset($column['orderLogic'])) {
142
            return $this->query;
143
        }
144
145
        $orderLogic = $column['orderLogic'];
146
147
        if (is_callable($orderLogic)) {
148
            return $orderLogic($this->query, $column, $columnDirection);
149
        }
150
151
        return $this->query;
152
    }
153
154
    /**
155
     * Group the results of the query in a certain way.
156
     *
157
     * @param  string  $field
158
     * @return \Illuminate\Database\Eloquent\Builder
159
     */
160
    public function groupBy($field)
161
    {
162
        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...
163
    }
164
165
    /**
166
     * Limit the number of results in the query.
167
     *
168
     * @param  int  $number
169
     * @return \Illuminate\Database\Eloquent\Builder
170
     */
171
    public function limit($number)
172
    {
173
        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...
174
    }
175
176
    /**
177
     * Take a certain number of results from the query.
178
     *
179
     * @param  int  $number
180
     * @return \Illuminate\Database\Eloquent\Builder
181
     */
182
    public function take($number)
183
    {
184
        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...
185
    }
186
187
    /**
188
     * Start the result set from a certain number.
189
     *
190
     * @param  int  $number
191
     * @return \Illuminate\Database\Eloquent\Builder
192
     */
193
    public function skip($number)
194
    {
195
        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...
196
    }
197
198
    /**
199
     * Count the number of results.
200
     *
201
     * @return int
202
     */
203
    public function count()
204
    {
205
        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...
206
    }
207
208
    /**
209
     * Apply table prefix in the order clause if the query contains JOINS clauses.
210
     *
211
     * @param  string  $column_name
212
     * @param  string  $column_direction
213
     * @return \Illuminate\Database\Eloquent\Builder
214
     */
215
    public function orderByWithPrefix($column_name, $column_direction = 'ASC')
216
    {
217
        if ($this->query->getQuery()->joins !== null) {
218
            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...
219
        }
220
221
        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...
222
    }
223
224
    /**
225
     * Get the entries count from `totalQuery`.
226
     *
227
     * @return int
228
     */
229
    public function getTotalQueryCount()
230
    {
231
        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

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

270
        if (! $this->/** @scrutinizer ignore-call */ driverIsSql()) {
Loading history...
271
            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...
272
        }
273
274
        $crudQuery = $query->toBase()->clone();
275
276
        $modelTable = $this->model->getTable();
277
278
        // create an "outer" query, the one that is responsible to do the count of the "crud query".
279
        $outerQuery = $crudQuery->newQuery();
280
281
        // add the count query in the "outer" query.
282
        $outerQuery = $outerQuery->selectRaw('count(*) as total_rows');
283
284
        // Expression columns are hand-written by developers in ->selectRaw() and we can't exclude those statements reliably
285
        // so we just store them and re-use them in the sub-query too.
286
        $expressionColumns = [];
287
288
        foreach ($crudQuery->columns as $column) {
289
            if (! is_string($column) && is_a($column, 'Illuminate\Database\Query\Expression')) {
290
                $expressionColumns[] = $column;
291
            }
292
        }
293
        // add the subquery from where the "outer query" will count the results.
294
        // this subquery is the "main crud query" without some properties:
295
        // - columns : we manually select the "minimum" columns possible from database.
296
        // - orders/limit/offset because we want the "full query count" where orders don't matter and limit/offset would break the total count
297
        $subQuery = $crudQuery->cloneWithout(['columns', 'orders', 'limit', 'offset']);
298
299
        // re-set the previous query bindings
300
        $subQuery->setBindings($crudQuery->getRawBindings());
301
302
        // select only one column for the count
303
        $subQuery->select($modelTable.'.'.$this->model->getKeyName());
304
305
        // in case there are raw expressions we need to add them too.
306
        foreach ($expressionColumns as $expression) {
307
            $subQuery->selectRaw($expression);
308
        }
309
310
        $outerQuery = $outerQuery->fromSub($subQuery, str_replace('.', '_', $modelTable).'_aggregator');
311
312
        return $outerQuery->cursor()->first()->total_rows;
313
    }
314
}
315