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

QueryBuilderFlex::createForTable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 11
Ratio 100 %

Importance

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