1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
use BZIon\Debug\DatabaseQuery; |
4
|
|
|
use Pixie\QueryBuilder\QueryBuilderHandler; |
5
|
|
|
|
6
|
|
|
/** |
7
|
|
|
* The core query builder used across BZiON for creating and modifying queries for all of our entities. |
8
|
|
|
* |
9
|
|
|
* @since 0.11.0 |
10
|
|
|
*/ |
11
|
|
|
class QueryBuilderFlex extends QueryBuilderHandler |
12
|
|
|
{ |
13
|
|
|
/** @var string The column name of the column dedicated to storing the name of the model */ |
14
|
|
|
private $modelNameColumn; |
15
|
|
|
|
16
|
|
|
/** @var Model|string The FQN of the model object this QueryBuilder instance is for */ |
17
|
|
|
private $modelType = null; |
18
|
|
|
|
19
|
|
|
/** @var int The amount of results per page with regards to result pagination */ |
20
|
|
|
private $resultsPerPage; |
21
|
|
|
|
22
|
|
|
// |
23
|
|
|
// Factories |
24
|
|
|
// |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* Create a QueryBuilder instance for a specific table. |
28
|
|
|
* |
29
|
|
|
* @param string $tableName |
30
|
|
|
* |
31
|
|
|
* @throws Exception If there is no database connection configured. |
32
|
|
|
* |
33
|
|
|
* @return QueryBuilderFlex |
34
|
|
|
*/ |
35
|
|
View Code Duplication |
public static function createForTable($tableName) |
|
|
|
|
36
|
|
|
{ |
37
|
|
|
Database::getInstance(); |
38
|
|
|
|
39
|
|
|
$connection = Service::getQueryBuilderConnection(); |
40
|
|
|
$qbBase = new QueryBuilderFlex($connection); |
41
|
|
|
|
42
|
|
|
$queryBuilder = $qbBase->table($tableName); |
43
|
|
|
|
44
|
|
|
return $queryBuilder; |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Creeate a QueryBuilder instance to work with a Model. |
49
|
|
|
* |
50
|
|
|
* @param string $modelType The FQN for the model that |
51
|
|
|
* |
52
|
|
|
* @throws Exception If there is no database connection configured. |
53
|
|
|
* |
54
|
|
|
* @return QueryBuilderFlex |
55
|
|
|
*/ |
56
|
|
View Code Duplication |
public static function createForModel($modelType) |
|
|
|
|
57
|
|
|
{ |
58
|
|
|
Database::getInstance(); |
59
|
|
|
|
60
|
|
|
$connection = Service::getQueryBuilderConnection(); |
61
|
|
|
$qbBase = new QueryBuilderFlex($connection); |
62
|
|
|
|
63
|
|
|
$queryBuilder = $qbBase->table(constant("$modelType::TABLE")); |
64
|
|
|
$queryBuilder->setModelType($modelType); |
65
|
|
|
|
66
|
|
|
return $queryBuilder; |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
// |
70
|
|
|
// Overridden QueryBuilder Functions |
71
|
|
|
// |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* {@inheritdoc} |
75
|
|
|
*/ |
76
|
|
|
public function __construct(\Pixie\Connection $connection = null) |
77
|
|
|
{ |
78
|
|
|
parent::__construct($connection); |
79
|
|
|
|
80
|
|
|
$this->setFetchMode(PDO::FETCH_ASSOC); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* {@inheritdoc} |
85
|
|
|
*/ |
86
|
|
|
public function limit($limit) |
87
|
|
|
{ |
88
|
|
|
$this->resultsPerPage = $limit; |
89
|
|
|
|
90
|
|
|
return parent::limit($limit); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* {@inheritdoc} |
95
|
|
|
*/ |
96
|
|
|
protected function whereHandler($key, $operator = null, $value = null, $joiner = 'AND') |
97
|
|
|
{ |
98
|
|
|
if ($value instanceof BaseModel) { |
99
|
|
|
$value = $value->getId(); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
return parent::whereHandler($key, $operator, $value, $joiner); |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
// |
106
|
|
|
// QueryBuilderFlex unique functions |
107
|
|
|
// |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Request that only non-deleted Models should be returned. |
111
|
|
|
* |
112
|
|
|
* @return static |
113
|
|
|
*/ |
114
|
|
|
public function active() |
115
|
|
|
{ |
116
|
|
|
$type = $this->modelType; |
117
|
|
|
|
118
|
|
|
// Since it's a system model, values are always handled by BZiON core meaning there will always only be "active" |
119
|
|
|
// values in the database. |
120
|
|
|
if ($type::SYSTEM_MODEL) { |
121
|
|
|
return $this; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
$column = $type::DELETED_COLUMN; |
125
|
|
|
|
126
|
|
|
if ($column === null) { |
127
|
|
|
@trigger_error( |
|
|
|
|
128
|
|
|
sprintf('The use of the status column is deprecated. Update the %s model to use the DELETED_* constants.', get_called_class()), |
129
|
|
|
E_USER_DEPRECATED |
130
|
|
|
); |
131
|
|
|
|
132
|
|
|
return $this->whereIn('status', $type::getActiveStatuses()); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
$stopPropagation = $type::getActiveModels($this); |
136
|
|
|
|
137
|
|
|
if ($stopPropagation) { |
138
|
|
|
return $this; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
return $this->whereNot($column, '=', $type::DELETED_VALUE); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* An alias for QueryBuilder::getModels(), with fast fetching on by default and no return of results. |
146
|
|
|
* |
147
|
|
|
* @param bool $fastFetch Whether to perform one query to load all the model data instead of fetching them one by |
148
|
|
|
* one |
149
|
|
|
* |
150
|
|
|
* @throws \Pixie\Exception |
151
|
|
|
* |
152
|
|
|
* @return void |
153
|
|
|
*/ |
154
|
|
|
public function addToCache($fastFetch = true) |
155
|
|
|
{ |
156
|
|
|
$this->getModels($fastFetch); |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* Get the amount of pages this query would have. |
161
|
|
|
* |
162
|
|
|
* @return int |
163
|
|
|
*/ |
164
|
|
|
public function countPages() |
165
|
|
|
{ |
166
|
|
|
return (int)ceil($this->count() / $this->resultsPerPage); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* Request that a specific model is not returned. |
171
|
|
|
* |
172
|
|
|
* @param Model|int $model The ID or model you don't want to get |
173
|
|
|
* |
174
|
|
|
* @return static |
175
|
|
|
*/ |
176
|
|
|
public function except($model) |
177
|
|
|
{ |
178
|
|
|
if ($model instanceof Model) { |
179
|
|
|
$model = $model->getId(); |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
$this->whereNot('id', '=', $model); |
183
|
|
|
|
184
|
|
|
return $this; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* Only show results from a specific page. |
189
|
|
|
* |
190
|
|
|
* This method will automatically take care of the calculations for a correct OFFSET. |
191
|
|
|
* |
192
|
|
|
* @param int|null $page The page number (or null to show all pages - counting starts from 0) |
193
|
|
|
* |
194
|
|
|
* @return static |
195
|
|
|
*/ |
196
|
|
|
public function fromPage($page) |
197
|
|
|
{ |
198
|
|
|
if ($page === null) { |
199
|
|
|
$this->offset($page); |
200
|
|
|
|
201
|
|
|
return $this; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
$page = intval($page); |
205
|
|
|
$page = ($page <= 0) ? 1 : $page; |
206
|
|
|
|
207
|
|
|
$this->offset((min($page, $this->countPages()) - 1) * $this->resultsPerPage); |
208
|
|
|
|
209
|
|
|
return $this; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* Perform the query and get the results as Models. |
214
|
|
|
* |
215
|
|
|
* @param bool $fastFetch Whether to perform one query to load all the model data instead of fetching them one by |
216
|
|
|
* one (ignores cache) |
217
|
|
|
* |
218
|
|
|
* @throws \Pixie\Exception |
219
|
|
|
* |
220
|
|
|
* @return array |
221
|
|
|
*/ |
222
|
|
|
public function getModels($fastFetch = false) |
223
|
|
|
{ |
224
|
|
|
/** @var Model $type */ |
225
|
|
|
$type = $this->modelType; |
226
|
|
|
|
227
|
|
|
$this->select($type::getEagerColumnsList()); |
228
|
|
|
|
229
|
|
|
$queryObject = $this->getQuery(); |
230
|
|
|
$debug = new DatabaseQuery($queryObject->getSql(), $queryObject->getBindings()); |
231
|
|
|
|
232
|
|
|
/** @var array $results */ |
233
|
|
|
$results = $this->get(); |
234
|
|
|
|
235
|
|
|
$debug->finish($results); |
236
|
|
|
|
237
|
|
|
if ($fastFetch) { |
238
|
|
|
return $type::createFromDatabaseResults($results); |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
return $type::arrayIdToModel(array_column($results, 'id')); |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
/** |
245
|
|
|
* Perform the query and get back the results in an array of names. |
246
|
|
|
* |
247
|
|
|
* @throws \Pixie\Exception |
248
|
|
|
* @throws UnexpectedValueException When no name column has been specified |
249
|
|
|
* |
250
|
|
|
* @return string[] An array of the type $id => $name |
251
|
|
|
*/ |
252
|
|
|
public function getNames() |
253
|
|
|
{ |
254
|
|
|
if (!$this->modelNameColumn) { |
255
|
|
|
throw new UnexpectedValueException(sprintf('The name column has not been specified for this query builder. Use %s::setNameColumn().', get_called_class())); |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
$this->select(['id', $this->modelNameColumn]); |
259
|
|
|
|
260
|
|
|
$queryObject = $this->getQuery(); |
261
|
|
|
$debug = new DatabaseQuery($queryObject->getSql(), $queryObject->getBindings()); |
262
|
|
|
|
263
|
|
|
/** @var array $results */ |
264
|
|
|
$results = $this->get(); |
265
|
|
|
|
266
|
|
|
$debug->finish($results); |
267
|
|
|
|
268
|
|
|
return array_column($results, $this->modelNameColumn, 'id'); |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
/** |
272
|
|
|
* Set the model this QueryBuilder will be working this. |
273
|
|
|
* |
274
|
|
|
* This information is used for automatically retrieving table names, eager columns, and lazy columns for these |
275
|
|
|
* models. |
276
|
|
|
* |
277
|
|
|
* @param string $modelType The FQN of the model this QueryBuilder will be working with |
278
|
|
|
* |
279
|
|
|
* @return $this |
280
|
|
|
*/ |
281
|
|
|
public function setModelType($modelType) |
282
|
|
|
{ |
283
|
|
|
$this->modelType = $modelType; |
284
|
|
|
|
285
|
|
|
return $this; |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
/** |
289
|
|
|
* Set the column that'll be used as the human-friendly name of the model. |
290
|
|
|
* |
291
|
|
|
* @param string $columnName |
292
|
|
|
* |
293
|
|
|
* @return static |
294
|
|
|
*/ |
295
|
|
|
public function setNameColumn($columnName) |
296
|
|
|
{ |
297
|
|
|
if (!is_subclass_of($this->modelType, NamedModel::class)) { |
|
|
|
|
298
|
|
|
throw new LogicException(sprintf('Setting name columns is only supported in models implementing the "%s" interface.', NamedModel::class)); |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
$this->modelNameColumn = $columnName; |
302
|
|
|
|
303
|
|
|
return $this; |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
/** |
307
|
|
|
* Make sure that Models invisible to a player are not returned. |
308
|
|
|
* |
309
|
|
|
* Note that this method does not take PermissionModel::canBeSeenBy() into |
310
|
|
|
* consideration for performance purposes, so you will have to override this |
311
|
|
|
* in your query builder if necessary. |
312
|
|
|
* |
313
|
|
|
* @param Player $player The player in question |
314
|
|
|
* @param bool $showDeleted Use false to hide deleted models even from admins |
315
|
|
|
* |
316
|
|
|
* @return static |
317
|
|
|
*/ |
318
|
|
|
public function visibleTo(Player $player, $showDeleted = false) |
319
|
|
|
{ |
320
|
|
|
$type = $this->modelType; |
321
|
|
|
|
322
|
|
|
if (is_subclass_of($this->modelType, PermissionModel::class) && |
|
|
|
|
323
|
|
|
$player->hasPermission(constant("$type::EDIT_PERMISSION")) |
324
|
|
|
) { |
325
|
|
|
// The player is an admin who can see the hidden models |
326
|
|
|
if (!$showDeleted) { |
327
|
|
|
$col = constant("$type::DELETED_COLUMN"); |
328
|
|
|
|
329
|
|
|
if ($col !== null) { |
330
|
|
|
$this->whereNot($col, '=', constant("$type::DELETED_VALUE")); |
331
|
|
|
} |
332
|
|
|
} |
333
|
|
|
} else { |
334
|
|
|
return $this->active(); |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
return $this; |
338
|
|
|
} |
339
|
|
|
} |
340
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.