Completed
Push — feature/player-list ( 9f06dd...8e5225 )
by Vladimir
04:50
created

BaseModel::formatColumns()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 3
cts 4
cp 0.75
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 2
crap 2.0625
1
<?php
2
/**
3
 * This file contains the skeleton for all of the database objects
4
 *
5
 * @package    BZiON\Models
6
 * @license    https://github.com/allejo/bzion/blob/master/LICENSE.md GNU General Public License Version 3
7
 */
8
9
/**
10
 * A base database object (e.g. A player or a team)
11
 * @package    BZiON\Models
12
 */
13
abstract class BaseModel implements ModelInterface
14
{
15
    /**
16
     * The Database ID of the object
17
     * @var int
18
     */
19
    protected $id;
20
21
    /**
22
     * The name of the database table used for queries
23
     * @var string
24
     */
25
    protected $table;
26
27
    /**
28
     * False if there isn't any row in the database representing
29
     * the requested object ID
30
     * @var bool
31
     */
32
    protected $valid;
33
34
    /**
35
     * The database variable used for queries
36
     * @var Database
37
     */
38
    protected $db;
39
40
    /**
41
     * Whether the lazy parameters of the model have been loaded
42
     * @var bool
43
     */
44
    protected $loaded = false;
45
46
    /**
47
     * The name of the database table used for queries
48
     * You can use this constant in static methods as such:
49
     * static::TABLE
50
     */
51
    const TABLE = "";
52
53
    /**
54
     * Get a Model based on its ID
55
     *
56
     * @param  int|static $id The ID of the object to look for, or the object
57
     *                        itself
58
     * @throws InvalidArgumentException If $id is an object of an incorrect type
59
     * @return static
60
     */
61 41
    public static function get($id)
62
    {
63 41
        if ($id instanceof static) {
64 2
            return $id;
65
        }
66
67 40
        if (is_object($id)) {
68
            // Throw an exception if $id is an object of the incorrect class
69 1
            throw new InvalidArgumentException("The object provided is not of the correct type");
70
        }
71
72 39
        $id = (int) $id;
73
74 39
        return static::chooseModelFromDatabase($id);
75
    }
76
77
    /**
78
     * Assign the MySQL result array to the individual properties of the model
79
     *
80
     * @param  array $result MySQL's result array
81
     * @return null
82
     */
83
    abstract protected function assignResult($result);
84
85
    /**
86
     * Fetch the columns of a model
87
     *
88
     * This method takes the ID of the object to look for and creates a
89
     * $this->db object which can be used to communicate with the database and
90
     * calls $this->assignResult() so that the child class can populate the
91
     * properties of the Model based on the database data
92
     *
93
     * If the $id is specified as 0, then an invalid object will be returned
94
     *
95
     * @param int $id The ID of the model
96
     * @param array|null $results The column values of the model, or NULL to
97
     *                            generate them using $this->fetchColumnValues()
98
     */
99 39
    protected function __construct($id, $results = null)
100
    {
101 39
        $this->db = Database::getInstance();
102 39
        $this->table = static::TABLE;
103
104 39
        if ($id == 0) {
105 39
            $this->valid = false;
106
107 39
            return;
108
        }
109
110 39
        $this->id = $id;
111
112 39
        if ($results == null) {
113 39
            $results = $this->fetchColumnValues($id);
114
        }
115
116 39
        if ($results === null) {
117
            $this->valid = false;
118
        } else {
119 39
            $this->valid = true;
120 39
            $this->assignResult($results);
121
        }
122 39
    }
123
124
    /**
125
     * Update a database field
126
     *
127
     * @param string $name  The name of the column
128
     * @param mixed  $value The value to set the column to
129
     *
130
     * @return void
131
     */
132 39
    public function update($name, $value)
133
    {
134 39
        $this->db->execute("UPDATE " . static::TABLE . " SET `$name` = ? WHERE id = ?", array($value, $this->id));
135 39
    }
136
137
    /**
138
     * Delete the object
139
     *
140
     * Please note that this does not delete the object entirely from the database,
141
     * it only hides it from users. You should overload this function if your object
142
     * does not have a 'status' column which can be set to 'deleted'.
143
     */
144 1
    public function delete()
145
    {
146 1
        $this->status = 'deleted';
0 ignored issues
show
Bug introduced by
The property status does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
147 1
        $this->update('status', 'deleted');
148 1
    }
149
150
    /**
151
     * Permanently delete the object from the database
152
     */
153 39
    public function wipe()
154
    {
155 39
        $this->db->execute("DELETE FROM " . static::TABLE . " WHERE id = ?", array($this->id));
156 39
    }
157
158
    /**
159
     * Get an object's database ID
160
     * @return int The ID
161
     */
162 39
    public function getId()
163
    {
164 39
        return $this->id;
165
    }
166
167
    /**
168
     * See if an object is valid
169
     * @return bool
170
     */
171 39
    public function isValid()
172
    {
173 39
        return $this->valid;
174
    }
175
176
    /**
177
     * Fetch a model based on its ID, useful for abstract model classes
178
     *
179
     * @param int $id The ID of the model
180
     * @return Model
181
     */
182 39
    protected static function chooseModelFromDatabase($id)
183
    {
184 39
        return new static($id);
185
    }
186
187
    /**
188
     * Query the database to get the eager column values for the Model
189
     *
190
     * @param $id int The ID of the model to fetch
191
     * @return array|null The results or null if a model wasn't found
192
     */
193 39
    protected static function fetchColumnValues($id)
194
    {
195 39
        $table = static::TABLE;
196 39
        $columns = static::getEagerColumns();
197
198 39
        $results = Database::getInstance()
199 39
            ->query("SELECT $columns FROM $table WHERE id = ? LIMIT 1", array($id));
200
201 39
        if (count($results) < 1) {
202
            return null;
203
        }
204
205 39
        return $results[0];
206
    }
207
208
    /**
209
     * Counts the elements of the database that match a specific query
210
     *
211
     * @param  string $additional_query The MySQL query string (e.g. `WHERE id = ?`)
212
     * @param  array  $params           The parameter values that will be passed to Database::query()
213
     * @param  string $table            The database table that will be searched, defaults to the model's table
214
     * @param  string $column           Only count the entries where `$column` is not `NULL` (or all if `$column` is `*`)
215
     * @return int
216
     */
217 1
    protected static function fetchCount($additional_query = '', $params = array(), $table = '', $column = '*')
218
    {
219 1
        $table = (empty($table)) ? static::TABLE : $table;
220 1
        $db = Database::getInstance();
221
222 1
        $result = $db->query("SELECT COUNT($column) AS count FROM $table $additional_query",
223 1
            $params
224
        );
225
226 1
        return $result[0]['count'];
227
    }
228
229
    /**
230
     * Gets the id of a database row which has a specific value on a column
231
     * @param  string $value  The value which the column should be equal to
232
     * @param  string $column The name of the database column
233
     * @return int    The ID of the object
234
     */
235 4
    protected static function fetchIdFrom($value, $column)
236
    {
237 4
        $results = self::fetchIdsFrom($column, $value, false, "LIMIT 1");
238
239
        // Return the id or 0 if nothing was found
240 4
        return (isset($results[0])) ? $results[0] : 0;
241
    }
242
243
    /**
244
     * Gets an array of object IDs from the database
245
     *
246
     * @param string          $additional_query Additional query snippet passed to the MySQL query after the SELECT statement (e.g. `WHERE id = ?`)
247
     * @param array           $params           The parameter values that will be passed to Database::query()
248
     * @param string          $table            The database table that will be searched
249
     * @param string|string[] $select           The column that will be returned
250
     *
251
     * @return int[] A list of values, if $select was only one column, or the return array of $db->query if it was more
252
     */
253 39
    protected static function fetchIds($additional_query = '', $params = array(), $table = "", $select = 'id')
254
    {
255 39
        $table = (empty($table)) ? static::TABLE : $table;
256 39
        $db = Database::getInstance();
257
258
        // If $select is an array, convert it into a comma-separated list that MySQL will accept
259 39
        if (is_array($select)) {
260
            $select = implode(",", $select);
261
        }
262
263 39
        $results = $db->query("SELECT $select FROM $table $additional_query", $params);
264
265 39
        if (!$results) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $results of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
266 39
            return array();
267
        }
268
269 39
        return array_column($results, 0);
270
    }
271
272
    /**
273
     * Gets an array of object IDs from the database that have a column equal to something else
274
     *
275
     * @param string          $column           The name of the column that should be tested
276
     * @param array|mixed     $possible_values  List of acceptable values
277
     * @param bool            $negate           Whether to search if the value of $column does NOT belong to the $possible_values array
278
     * @param string|string[] $select           The name of the column(s) that the returned array should contain
279
     * @param string          $additional_query Additional parameters to be passed to the MySQL query (e.g. `WHERE id = 5`)
280
     * @param string          $table            The database table which will be used for queries
281
     *
282
     * @return int[] A list of values, if $select was only one column, or the return array of $db->query if it was more
283
     */
284 39
    protected static function fetchIdsFrom($column, $possible_values, $negate = false, $additional_query = "", $table = "", $select = 'id')
285
    {
286 39
        $question_marks = array();
287 39
        $negation = ($negate) ? "NOT" : "";
288
289 39
        if (!is_array($possible_values)) {
290 7
            $possible_values = array($possible_values);
291
        }
292
293 39
        foreach ($possible_values as $p) {
294 39
            $question_marks[] = '?';
295
        }
296
297 39
        if (empty($possible_values)) {
298
            if (!$negate) {
299
                // There isn't any value that $column can have so
300
                // that it matches the criteria - return nothing.
301
                return array();
302
            } else {
303
                $conditionString = $additional_query;
304
            }
305
        } else {
306 39
            $conditionString = "WHERE $column $negation IN (" . implode(",", $question_marks) . ") $additional_query";
307
        }
308
309 39
        return self::fetchIds($conditionString, $possible_values, $table, $select);
310
    }
311
312
    /**
313
     * Get the MySQL columns that will be loaded as soon as the model is created
314
     *
315
     * @todo Make this protected
316
     *
317
     * @param string $prefix The prefix that'll be prefixed to column names
318
     *
319
     * @return string The columns in a format readable by MySQL
320
     */
321 35
    public static function getEagerColumns($prefix = null)
322
    {
323 35
        return self::formatColumns($prefix, ['*']);
324
    }
325
326
    /**
327
     * Get the MySQL columns that will be loaded only when a corresponding
328
     * parameter of the model is requested
329
     *
330
     * This is done in order to reduce the time needed to load parameters that
331
     * will not be requested (e.g player activation codes or permissions)
332
     *
333
     * @return string|null The columns in a format readable by MySQL or null to
334
     *                     fetch no columns at all
335
     */
336
    protected static function getLazyColumns()
337
    {
338
        throw new Exception("You need to specify a Model::getLazyColumns() method");
339
    }
340
341
    /**
342
     * Get a formatted string with a comma separated column list with table/alias prefixes if necessary.
343
     *
344
     * @param string|null $prefix  The table name or SQL alias to be prepend to these columns
345
     * @param array       $columns The columns to format
346
     *
347
     * @return string
348
     */
349 39
    protected static function formatColumns($prefix = null, $columns = ['*'])
350
    {
351 39
        if ($prefix === null) {
352 39
            return implode(',', $columns);
353
        }
354
355
        return (($prefix . '.') . implode(sprintf(',%s.', $prefix), $columns));
356
    }
357
358
    /**
359
     * Load all the parameters of the model that were not loaded during the first
360
     * fetch from the database
361
     *
362
     * @param  array $result MySQL's result set
363
     * @return void
364
     */
365
    protected function assignLazyResult($result)
366
    {
367
        throw new Exception("You need to specify a Model::lazyLoad() method");
368
    }
369
370
    /**
371
     * Load all the properties of the model that haven't been loaded yet
372
     *
373
     * @param  bool $force Whether to force a reload
374
     * @return self
375
     */
376 39
    protected function lazyLoad($force = false)
377
    {
378 39
        if ((!$this->loaded || $force) && $this->valid) {
379 39
            $this->loaded = true;
380
381 39
            $columns = $this->getLazyColumns();
382
383 39
            if ($columns !== null) {
384 39
                $results = $this->db->query("SELECT $columns FROM {$this->table} WHERE id = ? LIMIT 1", array($this->id));
385
386 39
                if (count($results) < 1) {
387
                    throw new Exception("The model has mysteriously disappeared");
388
                }
389
390 39
                $this->assignLazyResult($results[0]);
391
            } else {
392
                $this->assignLazyResult(array());
393
            }
394
        }
395
396 39
        return $this;
397
    }
398
399
    /**
400
     * Gets an entity from the supplied slug, which can either be an alias or an ID
401
     * @param  string|int $slug The object's slug
402
     * @return static
403
     */
404 1
    public static function fetchFromSlug($slug)
405
    {
406 1
        return static::get((int) $slug);
407
    }
408
409
    /**
410
     * Creates a new entry in the database
411
     *
412
     * <code>
413
     * Model::create(array( 'author'=>15, 'content'=>"Lorem ipsum..."  ));
414
     * </code>
415
     *
416
     * @param  array        $params An associative array, with the keys (columns) pointing to the
417
     *                              values you want to put on each
418
     * @param  array|string $now    Column(s) to update with the current timestamp
419
     * @param  string       $table  The table to perform the query on, defaults to the Model's
420
     *                              table
421
     * @return static       The new entry
422
     */
423 39
    protected static function create($params, $now = null, $table = '')
424
    {
425 39
        $table = (empty($table)) ? static::TABLE : $table;
426 39
        $db = Database::getInstance();
427
428 39
        $columns = implode('`,`', array_keys($params));
429 39
        $columns = "`$columns`";
430
431 39
        $question_marks = str_repeat('?,', count($params));
432 39
        $question_marks = rtrim($question_marks, ','); // Remove last comma
433
434 39
        if ($now) {
435 12
            if (!is_array($now)) {
436
                // Convert $now to an array if it's a string
437 9
                $now = array($now);
438
            }
439
440 12
            foreach ($now as $column) {
441 12
                $columns .= ",$column";
442 12
                $question_marks .= ",UTC_TIMESTAMP()";
443
            }
444
        }
445
446 39
        $query = "INSERT into $table ($columns) VALUES ($question_marks)";
447 39
        $db->execute($query, array_values($params));
448
449 39
        return static::get($db->getInsertId());
450
    }
451
452
    /**
453
     * Fetch a model's data from the database again
454
     * @return static The new model
455
     */
456
    public function refresh()
457
    {
458
        self::__construct($this->id);
459
460
        if ($this->loaded) {
461
            // Load the lazy parameters of the model if they're loaded already
462
            $this->lazyLoad(true);
463
        }
464
465
        return $this;
466
    }
467
468
    /**
469
     * Generate an invalid object
470
     *
471
     * <code>
472
     *     <?php
473
     *     $object = Team::invalid();
474
     *
475
     *     get_class($object); // Team
476
     *     $object->isValid(); // false
477
     * </code>
478
     * @return static
479
     */
480 1
    public static function invalid()
481
    {
482 1
        return new static(0);
483
    }
484
}
485