Completed
Pull Request — master (#186)
by Vladimir
05:51 queued 03:01
created

QueryBuilderFlex::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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