Completed
Push — master ( c7af1e...46d491 )
by Konstantinos
06:03 queued 02:12
created

BaseModel::__construct()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4.0058

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 24
ccs 13
cts 14
cp 0.9286
rs 8.6845
cc 4
eloc 14
nc 5
nop 2
crap 4.0058
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 2
            $this->valid = false;
106
107 2
            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 2
    public function delete()
145
    {
146 2
        $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 2
        $this->update('status', 'deleted');
148 2
    }
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
            $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
     * @return string The columns in a format readable by MySQL
317
     */
318 35
    public static function getEagerColumns()
319
    {
320 35
        return '*';
321
    }
322
323
    /**
324
     * Get the MySQL columns that will be loaded only when a corresponding
325
     * parameter of the model is requested
326
     *
327
     * This is done in order to reduce the time needed to load parameters that
328
     * will not be requested (e.g player activation codes or permissions)
329
     *
330
     * @return string|null The columns in a format readable by MySQL or null to
331
     *                     fetch no columns at all
332
     */
333
    protected static function getLazyColumns()
334
    {
335
        throw new Exception("You need to specify a Model::getLazyColumns() method");
336
    }
337
338
    /**
339
     * Load all the parameters of the model that were not loaded during the first
340
     * fetch from the database
341
     *
342
     * @param  array $result MySQL's result set
343
     * @return void
344
     */
345
    protected function assignLazyResult($result)
346
    {
347
        throw new Exception("You need to specify a Model::lazyLoad() method");
348
    }
349
350
    /**
351
     * Load all the properties of the model that haven't been loaded yet
352
     *
353
     * @param  bool $force Whether to force a reload
354
     * @return self
355
     */
356 39
    protected function lazyLoad($force = false)
357
    {
358 39
        if ((!$this->loaded || $force) && $this->valid) {
359 39
            $this->loaded = true;
360
361 39
            $columns = $this->getLazyColumns();
362
363 39
            if ($columns !== null) {
364 39
                $results = $this->db->query("SELECT $columns FROM {$this->table} WHERE id = ? LIMIT 1", array($this->id));
365
366 39
                if (count($results) < 1) {
367
                    throw new Exception("The model has mysteriously disappeared");
368
                }
369
370 39
                $this->assignLazyResult($results[0]);
371
            } else {
372
                $this->assignLazyResult(array());
373
            }
374
        }
375
376 39
        return $this;
377
    }
378
379
    /**
380
     * Gets an entity from the supplied slug, which can either be an alias or an ID
381
     * @param  string|int $slug The object's slug
382
     * @return static
383
     */
384 1
    public static function fetchFromSlug($slug)
385
    {
386 1
        return static::get((int) $slug);
387
    }
388
389
    /**
390
     * Creates a new entry in the database
391
     *
392
     * <code>
393
     * Model::create(array( 'author'=>15, 'content'=>"Lorem ipsum..."  ));
394
     * </code>
395
     *
396
     * @param  array        $params An associative array, with the keys (columns) pointing to the
397
     *                              values you want to put on each
398
     * @param  array|string $now    Column(s) to update with the current timestamp
399
     * @param  string       $table  The table to perform the query on, defaults to the Model's
400
     *                              table
401
     * @return static       The new entry
402
     */
403 39
    protected static function create($params, $now = null, $table = '')
404
    {
405 39
        $table = (empty($table)) ? static::TABLE : $table;
406 39
        $db = Database::getInstance();
407
408 39
        $columns = implode('`,`', array_keys($params));
409 39
        $columns = "`$columns`";
410
411 39
        $question_marks = str_repeat('?,', count($params));
412 39
        $question_marks = rtrim($question_marks, ','); // Remove last comma
413
414 39
        if ($now) {
415 12
            if (!is_array($now)) {
416
                // Convert $now to an array if it's a string
417 9
                $now = array($now);
418
            }
419
420 12
            foreach ($now as $column) {
421 12
                $columns .= ",$column";
422 12
                $question_marks .= ",UTC_TIMESTAMP()";
423
            }
424
        }
425
426 39
        $query = "INSERT into $table ($columns) VALUES ($question_marks)";
427 39
        $db->execute($query, array_values($params));
428
429 39
        return static::get($db->getInsertId());
430
    }
431
432
    /**
433
     * Fetch a model's data from the database again
434
     * @return static The new model
435
     */
436
    public function refresh()
437
    {
438
        self::__construct($this->id);
439
440
        if ($this->loaded) {
441
            // Load the lazy parameters of the model if they're loaded already
442
            $this->lazyLoad(true);
443
        }
444
445
        return $this;
446
    }
447
448
    /**
449
     * Generate an invalid object
450
     *
451
     * <code>
452
     *     <?php
453
     *     $object = Team::invalid();
454
     *
455
     *     get_class($object); // Team
456
     *     $object->isValid(); // false
457
     * </code>
458
     * @return static
459
     */
460 1
    public static function invalid()
461
    {
462 1
        return new static(0);
463
    }
464
}
465