. */ /** * Thrown in Model when a table row could not be found */ class ModelNotFoundException extends Exception { } /** * Thrown in Model when a column cannot be edited */ class ModelIllegalAccessException extends Exception { } /** * Thrown in Model when a call to __set() fails */ class ModelSetFailedException extends Exception { } /** * An abstract interface to a database table */ abstract class Model { /** * @var string $table The database table * @var string[] $protected_columns Columns that cannot be edited * @var string[] $fillable_columns Columns that can be edited * @var string $primary_key The table's primary key */ public $table = '', $protected_columns = ['id'], $fillable_columns = [], $primary_key = 'id'; /** * @var PDO $pdo A PDO instance for database communication * @var mixed[] $data The column values */ protected $pdo, $data; /** * Create a new instance * * @param PDO $pdo The PDO class, to access the database * @param int $id The id of the row to fetch * * @throws PDOException If something went wrong with the database * @throws ModelNotFoundException If the id could not be found */ public function __construct($pdo, $id) { $this->pdo = $pdo; $stmt = $this->pdo->prepare("SELECT * FROM `".$this->table()."` WHERE `{$this->primary_key}`=?"); $stmt->execute([$id]); if ($stmt->rowCount() == 0) { throw new ModelNotFoundException("The {$this->table} with id '$id' could not be found."); } $this->data = $stmt->fetch(PDO::FETCH_ASSOC); } /** * Set a column value * * @param string $key The column * @param mixed $value The value * * @throws PDOException Database error */ public function __set($key, $value) { if (!in_array($key, $this->fillable_columns)) { throw new ModelIllegalAccessException("Column $key cannot be edited."); } if ($this->data[$key] == $value) { return; } $stmt = $this->pdo->prepare("UPDATE `".$this->table()."` SET `$key`=? WHERE `{$this->primary_key}`=?"); $stmt->execute([ $this->mutator($key, $value), $this->data[$this->primary_key] ]); if ($stmt->rowCount() != 1) { throw new ModelEditFailedException(); } $this->data[$key] = $value; } /** * Get a column value * * @param string $key The column * * @return mixed The value */ public function __get($key) { return $this->accessor($key, $this->data[$key]); } /** * Post-__get() hook to modify the value * * @param string $key The column * @param string $value The value * * @return mixed The modified value (default: $value) */ protected function accessor($key, $value) { return $value; } /** * Pre-__set() hook to modify a value * * @param string $key The column * @param mixed $value The value * * @return string The modified value (default: $value casted to string) */ protected function mutator($key, $value) { return (string) $value; } /** * Delete the row * * @throws PDOException Database error * * @return bool True iff the row was really deleted */ public function delete() { $stmt = $this->pdo->prepare("DELETE FROM `{$this->table()}` WHERE `{$this->primary_key}`=?"); $stmt->execute([$this->data[$this->primary_key]]); return $stmt->rowCount() != 0; } /** * The actual table, after adding prefixes and the like * * @return string The database table */ private function table() { return Constants::db_prefix . $this->table; } }