Completed
Pull Request — master (#186)
by Vladimir
11:52 queued 07:48
created

QueryBuilderFlex::getModels()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 9.3142
c 0
b 0
f 0
cc 2
eloc 10
nc 2
nop 1
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
    /**
85
     * {@inheritdoc}
86
     */
87
    public function limit($limit)
88
    {
89
        $this->resultsPerPage = $limit;
90
91
        return parent::limit($limit);
92
    }
93
94
    //
95
    // QueryBuilderFlex unique functions
96
    //
97
98
    /**
99
     * Request that only non-deleted Models should be returned.
100
     *
101
     * @return static
102
     */
103
    public function active()
104
    {
105
        $type = $this->modelType;
106
        $column = $type::DELETED_COLUMN;
107
108
        if ($column === null) {
109
            @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...
110
                sprintf('The use of the status column is deprecated. Update the %s model to use the DELETED_* constants.', get_called_class()),
111
                E_USER_DEPRECATED
112
            );
113
114
            return $this->whereIn('status', $type::getActiveStatuses());
115
        }
116
117
        $stopPropagation = $type::getActiveModels($this);
118
119
        if ($stopPropagation) {
120
            return $this;
121
        }
122
123
        return $this->whereNot($column, '=', $type::DELETED_VALUE);
124
    }
125
126
    /**
127
     * An alias for QueryBuilder::getModels(), with fast fetching on by default and no return of results.
128
     *
129
     * @param  bool $fastFetch Whether to perform one query to load all the model data instead of fetching them one by
130
     *              one
131
     *
132
     * @throws \Pixie\Exception
133
     *
134
     * @return void
135
     */
136
    public function addToCache($fastFetch = true)
137
    {
138
        $this->getModels($fastFetch);
139
    }
140
141
    /**
142
     * Get the amount of pages this query would have.
143
     *
144
     * @return int
145
     */
146
    public function countPages()
147
    {
148
        return (int)ceil($this->count() / $this->resultsPerPage);
149
    }
150
151
    /**
152
     * Request that a specific model is not returned.
153
     *
154
     * @param  Model|int $model The ID or model you don't want to get
155
     *
156
     * @return static
157
     */
158
    public function except($model)
159
    {
160
        if ($model instanceof Model) {
161
            $model = $model->getId();
162
        }
163
164
        $this->whereNot('id', '=', $model);
165
166
        return $this;
167
    }
168
169
    /**
170
     * Only show results from a specific page.
171
     *
172
     * This method will automatically take care of the calculations for a correct OFFSET.
173
     *
174
     * @param  int|null $page The page number (or null to show all pages - counting starts from 0)
175
     *
176
     * @return static
177
     */
178
    public function fromPage($page)
179
    {
180
        if ($page === null) {
181
            $this->offset($page);
182
183
            return $this;
184
        }
185
186
        $page = intval($page);
187
        $page = ($page <= 0) ? 1 : $page;
188
189
        $this->offset((min($page, $this->countPages()) - 1) * $this->resultsPerPage);
190
191
        return $this;
192
    }
193
194
    /**
195
     * Perform the query and get the results as Models.
196
     *
197
     * @param  bool $fastFetch Whether to perform one query to load all the model data instead of fetching them one by
198
     *                         one (ignores cache)
199
     *
200
     * @throws \Pixie\Exception
201
     *
202
     * @return array
203
     */
204
    public function getModels($fastFetch = false)
205
    {
206
        /** @var Model $type */
207
        $type = $this->modelType;
208
209
        $this->select($type::getEagerColumnsList());
210
211
        $queryObject = $this->getQuery();
212
        $debug = new DatabaseQuery($queryObject->getSql(), $queryObject->getBindings());
213
214
        /** @var array $results */
215
        $results = $this->get();
216
217
        $debug->finish($results);
218
219
        if ($fastFetch) {
220
            return $type::createFromDatabaseResults($results);
221
        }
222
223
        return $type::arrayIdToModel(array_column($results, 'id'));
224
    }
225
226
    /**
227
     * Perform the query and get back the results in an array of names.
228
     *
229
     * @throws UnexpectedValueException When no name column has been specified
230
     *
231
     * @return string[] An array of the type $id => $name
232
     */
233
    public function getNames()
234
    {
235
        if (!$this->modelNameColumn) {
236
            throw new UnexpectedValueException(sprintf('The name column has not been specified for this query builder. Use %s::setNameColumn().', get_called_class()));
237
        }
238
239
        $this->select(['id', $this->modelNameColumn]);
240
241
        /** @var array $results */
242
        $results = $this->get();
243
244
        return array_column($results, $this->modelNameColumn, 'id');
245
    }
246
247
    /**
248
     * Set the model this QueryBuilder will be working this.
249
     *
250
     * This information is used for automatically retrieving table names, eager columns, and lazy columns for these
251
     * models.
252
     *
253
     * @param  string $modelType The FQN of the model this QueryBuilder will be working with
254
     *
255
     * @return $this
256
     */
257
    public function setModelType($modelType)
258
    {
259
        $this->modelType = $modelType;
260
261
        return $this;
262
    }
263
264
    /**
265
     * Set the column that'll be used as the human-friendly name of the model.
266
     *
267
     * @param string $columnName
268
     *
269
     * @return static
270
     */
271
    public function setNameColumn($columnName)
272
    {
273
        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...
274
            throw new LogicException(sprintf('Setting name columns is only supported in models implementing the "%s" interface.', NamedModel::class));
275
        }
276
277
        $this->modelNameColumn = $columnName;
278
279
        return $this;
280
    }
281
282
    /**
283
     * Make sure that Models invisible to a player are not returned.
284
     *
285
     * Note that this method does not take PermissionModel::canBeSeenBy() into
286
     * consideration for performance purposes, so you will have to override this
287
     * in your query builder if necessary.
288
     *
289
     * @param  Player $player      The player in question
290
     * @param  bool   $showDeleted Use false to hide deleted models even from admins
291
     *
292
     * @return static
293
     */
294
    public function visibleTo(Player $player, $showDeleted = false)
295
    {
296
        $type = $this->modelType;
297
298
        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...
299
            $player->hasPermission(constant("$type::EDIT_PERMISSION"))
300
        ) {
301
            // The player is an admin who can see the hidden models
302
            if (!$showDeleted) {
303
                $col = constant("$type::DELETED_COLUMN");
304
305
                if ($col !== null) {
306
                    $this->whereNot($col, '=', constant("$type::DELETED_VALUE"));
307
                }
308
            }
309
        } else {
310
            return $this->active();
311
        }
312
313
        return $this;
314
    }
315
}
316