Completed
Push — feature/pixie-port ( dee960...1c4371 )
by Vladimir
03:10
created

QueryBuilderFlex::active()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 29
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 14
nc 4
nop 0
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
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)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \NamedModel::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
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) &&
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \PermissionModel::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
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