Completed
Push — fm-support ( 624fa6...b0af51 )
by Konstantinos
09:12 queued 04:41
created

BaseModel::fetchIds()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4.0466

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 18
ccs 6
cts 7
cp 0.8571
rs 9.2
cc 4
eloc 9
nc 8
nop 4
crap 4.0466
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
    public function update($name, $value)
133 39
    {
134
        $this->db->execute("UPDATE " . static::TABLE . " SET `$name` = ? WHERE id = ?", array($value, $this->id));
135 39
    }
136 39
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
    public function delete()
145 2
    {
146
        $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 2
150
    /**
151
     * Permanently delete the object from the database
152
     */
153
    public function wipe()
154 39
    {
155
        $this->db->execute("DELETE FROM " . static::TABLE . " WHERE id = ?", array($this->id));
156 39
    }
157 39
158
    /**
159
     * Get an object's database ID
160
     * @return int The ID
161
     */
162
    public function getId()
163 39
    {
164
        return $this->id;
165 39
    }
166
167
    /**
168
     * See if an object is valid
169
     * @return bool
170
     */
171
    public function isValid()
172 39
    {
173
        return $this->valid;
174 39
    }
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
    protected static function chooseModelFromDatabase($id)
183 39
    {
184
        return new static($id);
185 39
    }
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
    protected static function fetchColumnValues($id)
194 39
    {
195
        $table = static::TABLE;
196 39
        $columns = static::getEagerColumns();
197 39
198
        $results = Database::getInstance()
199 39
            ->query("SELECT $columns FROM $table WHERE id = ? LIMIT 1", array($id));
200 39
201
        if (count($results) < 1) {
202 39
            return null;
203
        }
204
205
        return $results[0];
206 39
    }
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 1
        $table = (empty($table)) ? static::TABLE : $table;
220
        $db = Database::getInstance();
221 1
222 1
        $result = $db->query("SELECT COUNT($column) AS count FROM $table $additional_query",
223
            $params
224 1
        );
225
226
        return $result[0]['count'];
227
    }
228 1
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
    protected static function fetchIdFrom($value, $column)
236
    {
237
        $results = self::fetchIdsFrom($column, $value, false, "LIMIT 1");
238 4
239
        // Return the id or 0 if nothing was found
240 4
        return (isset($results[0])) ? $results[0] : 0;
241
    }
242
243 4
    /**
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
    protected static function fetchIds($additional_query = '', $params = array(), $table = "", $select = 'id')
254
    {
255
        $table = (empty($table)) ? static::TABLE : $table;
256
        $db = Database::getInstance();
257 39
258
        // If $select is an array, convert it into a comma-separated list that MySQL will accept
259 39
        if (is_array($select)) {
260 39
            $select = implode(",", $select);
261
        }
262
263 39
        $results = $db->query("SELECT $select FROM $table $additional_query", $params);
264
265
        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
            return array();
267 39
        }
268
269
        return array_column($results, 0);
270 39
    }
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 39
     * @param string|string[] $select           The name of the column(s) that the returned array should contain
279 39
     * @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 39
     *
282 39
     * @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
    protected static function fetchIdsFrom($column, $possible_values, $negate = false, $additional_query = "", $table = "", $select = 'id')
285 39
    {
286
        $question_marks = array();
287
        $negation = ($negate) ? "NOT" : "";
288
289
        if (!is_array($possible_values)) {
290
            $possible_values = array($possible_values);
291
        }
292
293
        foreach ($possible_values as $p) {
294
            $question_marks[] = '?';
295
        }
296
297
        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 39
                return array();
302
            } else {
303 39
                $conditionString = $additional_query;
304 39
            }
305 39
        } else {
306
            $conditionString = "WHERE $column $negation IN (" . implode(",", $question_marks) . ") $additional_query";
307 39
        }
308 7
309
        return self::fetchIds($conditionString, $possible_values, $table, $select);
310
    }
311 39
312 39
    /**
313 39
     * Get the MySQL columns that will be loaded as soon as the model is created
314
     *
315
     * @todo Make this protected
316 39
     * @return string The columns in a format readable by MySQL
317
     */
318
    public static function getEagerColumns()
319
    {
320
        return '*';
321
    }
322
323
    /**
324
     * Get the MySQL columns that will be loaded only when a corresponding
325 39
     * parameter of the model is requested
326
     *
327
     * This is done in order to reduce the time needed to load parameters that
328 39
     * 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 35
338
    /**
339 35
     * 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
    protected function lazyLoad($force = false)
357
    {
358
        if ((!$this->loaded || $force) && $this->valid) {
359
            $this->loaded = true;
360
361
            $columns = $this->getLazyColumns();
362
363
            if ($columns !== null) {
364
                $results = $this->db->query("SELECT $columns FROM {$this->table} WHERE id = ? LIMIT 1", array($this->id));
365
366
                if (count($results) < 1) {
367
                    throw new Exception("The model has mysteriously disappeared");
368
                }
369
370
                $this->assignLazyResult($results[0]);
371
            } else {
372
                $this->assignLazyResult(array());
373
            }
374
        }
375 39
376
        return $this;
377 39
    }
378 39
379
    /**
380 39
     * 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 39
     * @return static
383 39
     */
384
    public static function fetchFromSlug($slug)
385 39
    {
386
        return static::get((int) $slug);
387
    }
388
389 39
    /**
390
     * Creates a new entry in the database
391
     *
392
     * <code>
393
     * Model::create(array( 'author'=>15, 'content'=>"Lorem ipsum..."  ));
394
     * </code>
395 39
     *
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 1
    protected static function create($params, $now = null, $table = '')
404
    {
405 1
        $table = (empty($table)) ? static::TABLE : $table;
406
        $db = Database::getInstance();
407
408
        $columns = implode('`,`', array_keys($params));
409
        $columns = "`$columns`";
410
411
        $question_marks = str_repeat('?,', count($params));
412
        $question_marks = rtrim($question_marks, ','); // Remove last comma
413
414
        if ($now) {
415
            if (!is_array($now)) {
416
                // Convert $now to an array if it's a string
417
                $now = array($now);
418
            }
419
420
            foreach ($now as $column) {
421
                $columns .= ",$column";
422
                $question_marks .= ",UTC_TIMESTAMP()";
423 39
            }
424
        }
425 39
426 39
        $query = "INSERT into $table ($columns) VALUES ($question_marks)";
427
        $db->execute($query, array_values($params));
428 39
429 39
        return static::get($db->getInsertId());
430
    }
431 39
432 39
    /**
433
     * Fetch a model's data from the database again
434 39
     * @return static The new model
435 12
     */
436
    public function refresh()
437 9
    {
438
        self::__construct($this->id);
439
440 12
        if ($this->loaded) {
441 12
            // Load the lazy parameters of the model if they're loaded already
442 12
            $this->lazyLoad(true);
443
        }
444
445
        return $this;
446 39
    }
447 39
448
    /**
449 39
     * 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