Completed
Push — master ( c26f0f...c7af1e )
by Konstantinos
18:52
created

BaseModel::__construct()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4.0312

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 24
ccs 14
cts 16
cp 0.875
rs 8.6845
cc 4
eloc 14
nc 5
nop 2
crap 4.0312
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 40
    public static function get($id)
62
    {
63 40
        if ($id instanceof static) {
64 1
            return $id;
65
        }
66
67 39
        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 38
        $id = (int) $id;
73
74 38
        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 38
    protected function __construct($id, $results = null)
100
    {
101 38
        $this->db = Database::getInstance();
102 38
        $this->table = static::TABLE;
103
104 38
        if ($id == 0) {
105 1
            $this->valid = false;
106
107 1
            return;
108
        }
109
110 38
        $this->id = $id;
111
112 38
        if ($results == null) {
113 38
            $results = $this->fetchColumnValues($id);
114 38
        }
115
116 38
        if ($results === null) {
117
            $this->valid = false;
118
        } else {
119 38
            $this->valid = true;
120 38
            $this->assignResult($results);
121
        }
122 38
    }
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 38
    public function update($name, $value)
133
    {
134 38
        $this->db->execute("UPDATE " . static::TABLE . " SET `$name` = ? WHERE id = ?", array($value, $this->id));
135 38
    }
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 38
    public function wipe()
154
    {
155 38
        $this->db->execute("DELETE FROM " . static::TABLE . " WHERE id = ?", array($this->id));
156 38
    }
157
158
    /**
159
     * Get an object's database ID
160
     * @return int The ID
161
     */
162 38
    public function getId()
163
    {
164 38
        return $this->id;
165
    }
166
167
    /**
168
     * See if an object is valid
169
     * @return bool
170
     */
171 38
    public function isValid()
172
    {
173 38
        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 38
    protected static function chooseModelFromDatabase($id)
183
    {
184 38
        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 38
    protected static function fetchColumnValues($id)
194
    {
195 38
        $table = static::TABLE;
196 38
        $columns = static::getEagerColumns();
197
198 38
        $results = Database::getInstance()
199 38
            ->query("SELECT $columns FROM $table WHERE id = ? LIMIT 1", array($id));
200
201 38
        if (count($results) < 1) {
202
            return null;
203
        }
204
205 38
        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
    protected static function fetchCount($additional_query = '', $params = array(), $table = '', $column = '*')
218
    {
219
        $table = (empty($table)) ? static::TABLE : $table;
220
        $db = Database::getInstance();
221
222
        $result = $db->query("SELECT COUNT($column) AS count FROM $table $additional_query",
223
            $params
224
        );
225
226
        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 3
    protected static function fetchIdFrom($value, $column)
236
    {
237 3
        $results = self::fetchIdsFrom($column, $value, false, "LIMIT 1");
238
239
        // Return the id or 0 if nothing was found
240 3
        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 38
    protected static function fetchIds($additional_query = '', $params = array(), $table = "", $select = 'id')
254
    {
255 38
        $table = (empty($table)) ? static::TABLE : $table;
256 38
        $db = Database::getInstance();
257
258
        // If $select is an array, convert it into a comma-separated list that MySQL will accept
259 38
        if (is_array($select)) {
260
            $select = implode(",", $select);
261
        }
262
263 38
        $results = $db->query("SELECT $select FROM $table $additional_query", $params);
264
265 38
        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 38
            return array();
267
        }
268
269 38
        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 38
    protected static function fetchIdsFrom($column, $possible_values, $negate = false, $additional_query = "", $table = "", $select = 'id')
285
    {
286 38
        $question_marks = array();
287 38
        $negation = ($negate) ? "NOT" : "";
288
289 38
        if (!is_array($possible_values)) {
290 6
            $possible_values = array($possible_values);
291 6
        }
292
293 38
        foreach ($possible_values as $p) {
294 38
            $question_marks[] = '?';
295 38
        }
296
297 38
        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 38
            $conditionString = "WHERE $column $negation IN (" . implode(",", $question_marks) . ") $additional_query";
307
        }
308
309 38
        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 34
    public static function getEagerColumns()
319
    {
320 34
        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 38
    protected function lazyLoad($force = false)
357
    {
358 38
        if ((!$this->loaded || $force) && $this->valid) {
359 38
            $this->loaded = true;
360
361 38
            $columns = $this->getLazyColumns();
362
363 38
            if ($columns !== null) {
364 38
                $results = $this->db->query("SELECT $columns FROM {$this->table} WHERE id = ? LIMIT 1", array($this->id));
365
366 38
                if (count($results) < 1) {
367
                    throw new Exception("The model has mysteriously disappeared");
368
                }
369
370 38
                $this->assignLazyResult($results[0]);
371 38
            } else {
372
                $this->assignLazyResult(array());
373
            }
374 38
        }
375
376 38
        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
    public static function fetchFromSlug($slug)
385
    {
386
        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 38
    protected static function create($params, $now = null, $table = '')
404
    {
405 38
        $table = (empty($table)) ? static::TABLE : $table;
406 38
        $db = Database::getInstance();
407
408 38
        $columns = implode('`,`', array_keys($params));
409 38
        $columns = "`$columns`";
410
411 38
        $question_marks = str_repeat('?,', count($params));
412 38
        $question_marks = rtrim($question_marks, ','); // Remove last comma
413
414 38
        if ($now) {
415 11
            if (!is_array($now)) {
416
                // Convert $now to an array if it's a string
417 8
                $now = array($now);
418 8
            }
419
420 11
            foreach ($now as $column) {
421 11
                $columns .= ",$column";
422 11
                $question_marks .= ",UTC_TIMESTAMP()";
423 11
            }
424 11
        }
425
426 38
        $query = "INSERT into $table ($columns) VALUES ($question_marks)";
427 38
        $db->execute($query, array_values($params));
428
429 38
        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
    public static function invalid()
461
    {
462
        return new static(0);
463
    }
464
}
465