Completed
Pull Request — master (#186)
by Vladimir
05:50 queued 02:55
created

QueryBuilderFlex::visibleTo()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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