Laravel-Backpack /
CRUD
We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.
| 1 | <?php |
||||
| 2 | |||||
| 3 | namespace Backpack\CRUD\app\Library\CrudPanel\Traits; |
||||
| 4 | |||||
| 5 | use Illuminate\Contracts\Database\Query\Builder as QueryBuilder; |
||||
| 6 | use Illuminate\Database\Eloquent\Builder; |
||||
| 7 | |||||
| 8 | trait Query |
||||
| 9 | { |
||||
| 10 | /** @var Builder */ |
||||
| 11 | public $query; |
||||
| 12 | |||||
| 13 | /** @var Builder */ |
||||
| 14 | public $totalQuery; |
||||
| 15 | |||||
| 16 | // ---------------- |
||||
| 17 | // ADVANCED QUERIES |
||||
| 18 | // ---------------- |
||||
| 19 | |||||
| 20 | /** |
||||
| 21 | * Add another clause to the query (for ex, a WHERE clause). |
||||
| 22 | * |
||||
| 23 | * Examples: |
||||
| 24 | * $this->crud->addClause('active'); |
||||
| 25 | * $this->crud->addClause('type', 'car'); |
||||
| 26 | * $this->crud->addClause('where', 'name', '=', 'car'); |
||||
| 27 | * $this->crud->addClause('whereName', 'car'); |
||||
| 28 | * $this->crud->addClause('whereHas', 'posts', function($query) { |
||||
| 29 | * $query->activePosts(); |
||||
| 30 | * }); |
||||
| 31 | * |
||||
| 32 | * @param callable|string $function |
||||
| 33 | * @return mixed |
||||
| 34 | */ |
||||
| 35 | public function addClause($function) |
||||
| 36 | { |
||||
| 37 | if ($function instanceof \Closure) { |
||||
| 38 | $function($this->query); |
||||
| 39 | |||||
| 40 | return $this->query; |
||||
| 41 | } |
||||
| 42 | |||||
| 43 | return call_user_func_array([$this->query, $function], array_slice(func_get_args(), 1)); |
||||
| 44 | } |
||||
| 45 | |||||
| 46 | /** |
||||
| 47 | * This function is an alias of `addClause` but also adds the query as a constrain |
||||
| 48 | * in the `totalQuery` property. |
||||
| 49 | * |
||||
| 50 | * @param \Closure|string $function |
||||
| 51 | * @return self |
||||
| 52 | */ |
||||
| 53 | public function addBaseClause($function) |
||||
| 54 | { |
||||
| 55 | if ($function instanceof \Closure) { |
||||
| 56 | $function($this->query); |
||||
| 57 | $function($this->totalQuery); |
||||
| 58 | |||||
| 59 | return $this; |
||||
| 60 | } |
||||
| 61 | call_user_func_array([$this->query, $function], array_slice(func_get_args(), 1)); |
||||
| 62 | call_user_func_array([$this->totalQuery, $function], array_slice(func_get_args(), 1)); |
||||
| 63 | |||||
| 64 | return $this; |
||||
| 65 | } |
||||
| 66 | |||||
| 67 | public function setQuery(QueryBuilder $query) |
||||
| 68 | { |
||||
| 69 | $this->query = $query; |
||||
|
0 ignored issues
–
show
|
|||||
| 70 | |||||
| 71 | return $this; |
||||
| 72 | } |
||||
| 73 | |||||
| 74 | /** |
||||
| 75 | * Use eager loading to reduce the number of queries on the table view. |
||||
| 76 | * |
||||
| 77 | * @param array|string $entities |
||||
| 78 | * @return \Illuminate\Database\Eloquent\Builder |
||||
| 79 | */ |
||||
| 80 | public function with($entities) |
||||
| 81 | { |
||||
| 82 | return $this->query->with($entities); |
||||
| 83 | } |
||||
| 84 | |||||
| 85 | /** |
||||
| 86 | * Order the results of the query in a certain way. |
||||
| 87 | * |
||||
| 88 | * @param string $field |
||||
| 89 | * @param string $order |
||||
| 90 | * @return \Illuminate\Database\Eloquent\Builder |
||||
| 91 | */ |
||||
| 92 | public function orderBy($field, $order = 'asc') |
||||
| 93 | { |
||||
| 94 | if ($this->getRequest()->has('order')) { |
||||
|
0 ignored issues
–
show
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
Loading history...
|
|||||
| 95 | return $this->query; |
||||
| 96 | } |
||||
| 97 | |||||
| 98 | return $this->query->orderBy($field, $order); |
||||
|
0 ignored issues
–
show
|
|||||
| 99 | } |
||||
| 100 | |||||
| 101 | /** |
||||
| 102 | * Order results of the query in a custom way. |
||||
| 103 | * |
||||
| 104 | * @param array $column Column array with all attributes |
||||
| 105 | * @param string $column_direction ASC or DESC |
||||
| 106 | * @return \Illuminate\Database\Eloquent\Builder |
||||
| 107 | */ |
||||
| 108 | public function customOrderBy($column, $columnDirection = 'asc') |
||||
| 109 | { |
||||
| 110 | if (! isset($column['orderLogic'])) { |
||||
| 111 | return $this->query; |
||||
| 112 | } |
||||
| 113 | |||||
| 114 | $orderLogic = $column['orderLogic']; |
||||
| 115 | |||||
| 116 | if ($orderLogic instanceof \Closure) { |
||||
| 117 | return $orderLogic($this->query, $column, $columnDirection); |
||||
| 118 | } |
||||
| 119 | |||||
| 120 | return $this->query; |
||||
| 121 | } |
||||
| 122 | |||||
| 123 | /** |
||||
| 124 | * Group the results of the query in a certain way. |
||||
| 125 | * |
||||
| 126 | * @param string $field |
||||
| 127 | * @return \Illuminate\Database\Eloquent\Builder |
||||
| 128 | */ |
||||
| 129 | public function groupBy($field) |
||||
| 130 | { |
||||
| 131 | return $this->query->groupBy($field); |
||||
|
0 ignored issues
–
show
|
|||||
| 132 | } |
||||
| 133 | |||||
| 134 | /** |
||||
| 135 | * Limit the number of results in the query. |
||||
| 136 | * |
||||
| 137 | * @param int $number |
||||
| 138 | * @return \Illuminate\Database\Eloquent\Builder |
||||
| 139 | */ |
||||
| 140 | public function limit($number) |
||||
| 141 | { |
||||
| 142 | return $this->query->limit($number); |
||||
|
0 ignored issues
–
show
|
|||||
| 143 | } |
||||
| 144 | |||||
| 145 | /** |
||||
| 146 | * Take a certain number of results from the query. |
||||
| 147 | * |
||||
| 148 | * @param int $number |
||||
| 149 | * @return \Illuminate\Database\Eloquent\Builder |
||||
| 150 | */ |
||||
| 151 | public function take($number) |
||||
| 152 | { |
||||
| 153 | return $this->query->take($number); |
||||
|
0 ignored issues
–
show
|
|||||
| 154 | } |
||||
| 155 | |||||
| 156 | /** |
||||
| 157 | * Start the result set from a certain number. |
||||
| 158 | * |
||||
| 159 | * @param int $number |
||||
| 160 | * @return \Illuminate\Database\Eloquent\Builder |
||||
| 161 | */ |
||||
| 162 | public function skip($number) |
||||
| 163 | { |
||||
| 164 | return $this->query->skip($number); |
||||
|
0 ignored issues
–
show
|
|||||
| 165 | } |
||||
| 166 | |||||
| 167 | /** |
||||
| 168 | * Count the number of results. |
||||
| 169 | * |
||||
| 170 | * @return int |
||||
| 171 | */ |
||||
| 172 | public function count() |
||||
| 173 | { |
||||
| 174 | return $this->query->count(); |
||||
|
0 ignored issues
–
show
|
|||||
| 175 | } |
||||
| 176 | |||||
| 177 | /** |
||||
| 178 | * Apply table prefix in the order clause if the query contains JOINS clauses. |
||||
| 179 | * |
||||
| 180 | * @param string $column_name |
||||
| 181 | * @param string $column_direction |
||||
| 182 | * @return \Illuminate\Database\Eloquent\Builder |
||||
| 183 | */ |
||||
| 184 | public function orderByWithPrefix($column_name, $column_direction = 'asc') |
||||
| 185 | { |
||||
| 186 | $column_direction = strtolower($column_direction); |
||||
| 187 | |||||
| 188 | if ($this->query->getQuery()->joins !== null) { |
||||
| 189 | return $this->query->orderBy("{$this->model->getTableWithPrefix()}.{$column_name}", $column_direction); |
||||
|
0 ignored issues
–
show
|
|||||
| 190 | } |
||||
| 191 | |||||
| 192 | return $this->query->orderBy($column_name, $column_direction); |
||||
|
0 ignored issues
–
show
|
|||||
| 193 | } |
||||
| 194 | |||||
| 195 | /** |
||||
| 196 | * Get the entries count from `totalQuery`. |
||||
| 197 | * |
||||
| 198 | * @return int |
||||
| 199 | */ |
||||
| 200 | public function getTotalQueryCount() |
||||
| 201 | { |
||||
| 202 | if (! $this->getOperationSetting('showEntryCount')) { |
||||
|
0 ignored issues
–
show
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
Loading history...
|
|||||
| 203 | return 0; |
||||
| 204 | } |
||||
| 205 | |||||
| 206 | return $this->getOperationSetting('totalEntryCount') ?? |
||||
| 207 | $this->getCountFromQuery($this->totalQuery); |
||||
| 208 | } |
||||
| 209 | |||||
| 210 | /** |
||||
| 211 | * Get the entries count from the `query`. |
||||
| 212 | * |
||||
| 213 | * @return int |
||||
| 214 | */ |
||||
| 215 | public function getQueryCount() |
||||
| 216 | { |
||||
| 217 | return $this->getCountFromQuery($this->query); |
||||
| 218 | } |
||||
| 219 | |||||
| 220 | /** |
||||
| 221 | * Return the filtered query count or skip the counting when the `totalQuery` is the same as the filtered one. |
||||
| 222 | * |
||||
| 223 | * @return int|null |
||||
| 224 | */ |
||||
| 225 | public function getFilteredQueryCount() |
||||
| 226 | { |
||||
| 227 | // check if the filtered query is different from total query, in case they are the same, skip the count |
||||
| 228 | $filteredQuery = $this->query->toBase()->cloneWithout(['orders', 'limit', 'offset']); |
||||
| 229 | |||||
| 230 | return $filteredQuery->toSql() !== $this->totalQuery->toSql() ? $this->getQueryCount() : null; |
||||
| 231 | } |
||||
| 232 | |||||
| 233 | /** |
||||
| 234 | * Do a separate query to get the total number of entries, in an optimized way. |
||||
| 235 | * |
||||
| 236 | * @param Builder $query |
||||
| 237 | * @return int |
||||
| 238 | */ |
||||
| 239 | private function getCountFromQuery(Builder $query) |
||||
| 240 | { |
||||
| 241 | if (! $this->driverIsSql()) { |
||||
|
0 ignored issues
–
show
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
Loading history...
|
|||||
| 242 | return $query->count(); |
||||
|
0 ignored issues
–
show
|
|||||
| 243 | } |
||||
| 244 | |||||
| 245 | $crudQuery = $query->toBase()->clone(); |
||||
| 246 | |||||
| 247 | $modelTable = $this->model->getTable(); |
||||
| 248 | |||||
| 249 | // create an "outer" query, the one that is responsible to do the count of the "crud query". |
||||
| 250 | $outerQuery = $crudQuery->newQuery(); |
||||
| 251 | |||||
| 252 | // add the count query in the "outer" query. |
||||
| 253 | $outerQuery = $outerQuery->selectRaw('count(*) as total_rows'); |
||||
| 254 | |||||
| 255 | // Expression columns are hand-written by developers in ->selectRaw() and we can't exclude those statements reliably |
||||
| 256 | // so we just store them and re-use them in the sub-query too. |
||||
| 257 | $expressionColumns = []; |
||||
| 258 | |||||
| 259 | foreach ($crudQuery->columns ?? [] as $column) { |
||||
| 260 | if (! is_string($column) && is_a($column, 'Illuminate\Database\Query\Expression')) { |
||||
| 261 | $expressionColumns[] = $column; |
||||
| 262 | } |
||||
| 263 | } |
||||
| 264 | // add the subquery from where the "outer query" will count the results. |
||||
| 265 | // this subquery is the "main crud query" without some properties: |
||||
| 266 | // - columns : we manually select the "minimum" columns possible from database. |
||||
| 267 | // - orders/limit/offset because we want the "full query count" where orders don't matter and limit/offset would break the total count |
||||
| 268 | $subQuery = $crudQuery->cloneWithout(['columns', 'orders', 'limit', 'offset']); |
||||
| 269 | |||||
| 270 | // select minimum possible columns for the count |
||||
| 271 | $minimumColumns = ($crudQuery->groups || $crudQuery->havings) ? $modelTable.'.*' : $modelTable.'.'.$this->model->getKeyName(); |
||||
|
0 ignored issues
–
show
The expression
$crudQuery->groups of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
The expression
$crudQuery->havings of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
|
|||||
| 272 | $subQuery->select($minimumColumns); |
||||
| 273 | |||||
| 274 | // in case there are raw expressions we need to add them too. |
||||
| 275 | foreach ($expressionColumns as $expression) { |
||||
| 276 | $subQuery->selectRaw($expression); |
||||
| 277 | } |
||||
| 278 | |||||
| 279 | // re-set the previous query bindings |
||||
| 280 | //dump($crudQuery->getColumns(), get_class($crudQuery), get_class($subQuery)); |
||||
| 281 | foreach ($crudQuery->getRawBindings() as $type => $binding) { |
||||
| 282 | $subQuery->setBindings($binding, $type); |
||||
| 283 | } |
||||
| 284 | |||||
| 285 | $outerQuery = $outerQuery->fromSub($subQuery, str_replace('.', '_', $modelTable).'_aggregator'); |
||||
| 286 | |||||
| 287 | return $outerQuery->cursor()->first()->total_rows; |
||||
| 288 | } |
||||
| 289 | } |
||||
| 290 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.
Either this assignment is in error or an instanceof check should be added for that assignment.