Files
decpdf.site/lib/Model.php
2026-01-18 00:53:18 +00:00

492 lines
11 KiB
PHP
Executable File

<?php
// Special data types
define("MODEL_SERIAL", 0x1000); // auto increment id
define("MODEL_OBJECT", 0x1001); // Link to another object type by id
define("MODEL_CREATED", 0x1002); // CREATED timestamp
define("MODEL_UPDATED", 0x1003); // UPDATED timestamp
// Numeric data types
define("MODEL_BIT", 0x2000);
define("MODEL_TINYINT", 0x2001);
define("MODEL_BOOL", 0x2002);
define("MODEL_SMALLINT", 0x2003);
define("MODEL_MEDIUMINT", 0x2004);
define("MODEL_INT", 0x2005);
define("MODEL_BIGINT", 0x2006);
define("MODEL_FLOAT", 0x2007);
define("MODEL_DOUBLE", 0x2008);
define("MODEL_DECIMAL", 0x2009);
// String data types
define("MODEL_CHAR", 0x3000);
define("MODEL_VARCHAR", 0x3001);
define("MODEL_BINARY", 0x3002);
define("MODEL_VARBINARY", 0x3003);
define("MODEL_TINYBLOB", 0x3004);
define("MODEL_TINYTEXT", 0x3005);
define("MODEL_TEXT", 0x3006);
define("MODEL_BLOB", 0x3007);
define("MODEL_MEDIUMTEXT", 0x3008);
define("MODEL_MEDIUMBLOB", 0x3009);
define("MODEL_LONGTEXT", 0x300A);
define("MODEL_LONGBLOB", 0x300B);
define("MODEL_ENUM", 0x300C);
define("MODEL_SET", 0x300D);
// Date types
define("MODEL_DATE", 0x4000);
define("MODEL_DATETIME", 0x4001);
define("MODEL_TIMESTAMP", 0x4002);
define("MODEL_TIME", 0x4003);
define("MODEL_YEAR", 0x4004);
define("MODEL_INDEX", 12);
define("MODEL_FULLTEXT", 13);
define("MODEL_UNIQUE", 14);
class Model implements JsonSerializable {
public $_fields = []; // List of fields in the DB table
public $_virgin = []; // Virgin, untouched data loaded from the DB
public $_data = []; // Current (modified) data as stored in the DB
public $_objects = []; // Objects constructed from the values in _data
public $_timeout = 1;
public $_valid = false;
public $_loaded = false;
public static $_cache = null;
static protected $_raw = false;
public function __construct($id = null, $deep = true) {
$this->_valid = false;
if ($id !== null) {
$this->_load_record($id);
$this->_loaded = $this->_valid;
} else {
$this->_get_fields();
$this->_loaded = false;
$this->_valid = true;
}
}
public static function cache_connect($host, $port, $weight = 50) {
if (Model::$_cache === null) {
Model::$_cache = new Memcached();
}
Model::$_cache->addServer($host, $port, $weight);
}
private function _load_record($id) {
$class = get_called_class();
if (property_exists($class, "table")) {
$v = get_class_vars($class);
$table = $v["table"];
} else {
$table = strtolower($class);
}
$r = DB::getInstance()->select($table, $id);
if ($r === false) {
$this->_valid = false;
return;
}
foreach ($r as $k=>$v) {
$this->_fields[] = $k;
$this->_virgin[$k] = $v;
}
if (property_exists($class, "transform")) {
foreach ($this->_fields as $k) {
if (array_key_exists($k, $this->transform)) {
$v = $this->transform[$k];
$f = "from_" . $v;
if (method_exists($this, $f)) {
$this->_data[$k] = $this->$f($this->_virgin[$k]);
} else {
$this->_data[$k] = $this->_virgin[$k];
}
} else {
$this->_data[$k] = $this->_virgin[$k];
}
}
} else {
foreach ($this->_fields as $k) {
$this->_data[$k] = $this->_virgin[$k];
}
}
$this->_valid = true;
}
public static function find($where = []) {
$class = get_called_class();
if (property_exists($class, "table")) {
$v = get_class_vars($class);
$table = $v["table"];
} else {
$table = strtolower($class);
}
return new Query($class, $table, $where);
}
private function _get_fields() {
$class = get_called_class();
if ($class == "Model") { // Light model being used for xfer
return;
}
if (property_exists($class, "table")) {
$v = get_class_vars($class);
$table = $v["table"];
} else {
$table = strtolower($class);
}
$this->_fields = [];
$q = DB::getInstance()->query("describe `" . $table . "`");
while ($r = DB::getInstance()->nextRecord($q)) {
$this->_fields[] = $r->Field;
$this->_data[$r->Field] = null;
}
$this->_valid = true;
}
public function from_json($data) {
$d = json_decode($data);
if ($d) return $d;
return new stdClass;
}
public function to_json($data) {
return json_encode($data);
}
public function valid() {
return $this->_valid;
}
public function loaded() {
return $this->_loaded;
}
public function save() {
Model::raw_on();
$class = get_called_class();
if (property_exists($class, "table")) {
$v = get_class_vars($class);
$table = $v["table"];
} else {
$table = strtolower($class);
}
$_save = [];
if (property_exists($class, "transform")) {
foreach ($this->_fields as $k) {
if (array_key_exists($k, $this->transform)) {
$v = $this->transform[$k];
$f = "to_" . $v;
if (method_exists($this, $f)) {
$_save[$k] = $this->$f(@$this->$k);
} else {
$v = __unref($this->$k);
if (is_string($v)) $v = trim($v);
$_save[$k] = $v;
}
} else {
$v = __unref($this->$k);
if (is_string($v)) $v = trim($v);
$_save[$k] = $v;
}
}
} else {
foreach ($this->_fields as $k) {
$v = __unref($this->$k);
if (is_string($v)) $v = trim($v);
$_save[$k] = $v;
}
}
$_update = [];
foreach ($_save as $k=>$v) {
if (array_key_exists($k, $this->_virgin)) {
if ($_save[$k] != $this->_virgin[$k]) {
$_update[$k] = $v;
$this->_virgin[$k] = $v;
}
} else {
$_update[$k] = $v;
$this->_virgin[$k] = $v;
}
}
$triggers = [];
if ($this->_loaded) {
if (count($_update) > 0) {
if (in_array("updated", $this->_fields)) {
$_update['updated'] = time();
}
DB::getInstance()->update($table, $this->id, $_update);
}
} else {
$_update = $this->_virgin;
if (in_array("created", $this->_fields)) {
$_update['created'] = time();
}
$this->id = DB::getInstance()->insert($table, $_update);
$this->_loaded = true;
}
foreach ($_update as $k=>$v) {
if (property_exists($class, "_triggers")) {
if (array_key_exists($k, $this->_triggers)) {
$triggers[$this->_triggers[$k]] = 1;
}
}
}
foreach ($triggers as $trigger=>$count) {
if ($trigger instanceof \Closure) {
$trigger->call($this);
continue;
}
if (is_array($trigger)) {
$c = $trigger[0];
$f = $trigger[1];
$c::$f();
continue;
}
if (is_string($trigger)) {
$this->$trigger();
continue;
}
}
Model::raw_off();
}
public static function raw_on() {
Model::$_raw = true;
}
public static function raw_off() {
Model::$_raw = false;
}
static public function raw() {
return Model::$_raw;
}
public function delete() {
$class = get_called_class();
if (property_exists($class, "table")) {
$v = get_class_vars($class);
$table = $v["table"];
} else {
$table = strtolower($class);
}
if (method_exists($this, "on_delete")) {
$this->on_delete();
}
DB::getInstance()->query("delete from `" . $table . "` where id=:id", ["id" => $this->_data["id"]]);
}
public function __toString() {
return "" . $this->_data["id"];
}
public function __toInt() {
return (int)$this->_data["id"];
}
public function jsonSerialize() : mixed {
$out = [];
foreach ($this->_fields as $f) {
$out[$f] = $this->_data["$f"];
}
foreach ($this->_fields as $f) {
if (array_key_exists($f, $this->_objects)) {
$out[$f] = $this->_objects["$f"];
}
}
if (property_exists($this, "_computed")) {
foreach ($this->_computed as $k=>$v) {
$out[$k] = $this->$v();
}
}
return $out;
}
private function __get_field_class($f) {
if (!property_exists($this, "_classes")) return null;
if (!array_key_exists($f, $this->_classes)) return null;
return $this->_classes[$f];
}
public function __get($k) {
if (property_exists($this, "_computed")) {
if (array_key_exists($k, $this->_computed)) {
$c = $this->cache_get($k);
if ($c) return $c;
$func = $this->_computed[$k];
$c = $this->$func();
$this->cache_set($k, $c);
return $c;
}
}
if (!in_array($k, $this->_fields)) {
return null;
}
$class = $this->__get_field_class($k);
if ($class != null) {
if (array_key_exists($k, $this->_objects)) return $this->_objects[$k];
}
return $this->_data[$k];
}
public function __set($key, $val) {
if (!in_array($key, $this->_fields)) return false;
$class = $this->__get_field_class($key);
if ($class != null) {
if ($val instanceof Model) {
if ($val instanceof $class) {
$this->_data[$val] = $val->id;
$this->_objects[$key] = $val;
return true;
}
throw new Exception('Class mismatch');
return false;
}
if (is_numeric($val)) {
$this->_data[$key] = $val;
$this->_objects[$key] = new $class($val);
return true;
}
$val = (int)$val;
$this->_data[$key] = $val;
$this->_objects[$key] = new $class($val);
return true;
}
$this->_data[$key] = $val;
unset($this->_objects[$key]);
return true;
}
public function __isset($key) {
if (!in_array($key, $this->_fields)) return false;
return true;
}
public function __unset($key) {
if (!in_array($key, $this->_fields)) return;
$this->_data[$key] = null;
unset($this->_objects[$key]);
}
public function load($key = null) {
if ($key === null) {
foreach ($this->_fields as $key) {
$this->load($key);
}
return true;
}
if (!in_array($key, $this->_fields)) {
return false;
}
$class = $this->__get_field_class($key);
if ($class == null) {
return false;
}
if ($this->_data[$key] === null) {
return false;
}
$ob = new $class($this->_data[$key]);
if (!$ob->valid()) return false;
$this->_objects[$key] = $ob;
return true;
}
private function cache_key($key) {
$class = get_called_class();
return sprintf("%s[%d]::%s", $class, $this->id, $key);
}
public function cache_set($key, $val) {
$key = $this->cache_key($key);
Model::$_cache->set($key, $val, $this->_timeout);
}
public function cache_get($key) {
$key = $this->cache_key($key);
return Model::$_cache->get($key);
}
public function cache_invalidate($key) {
$key = $this->cache_key($key);
Model::$_cache->delete($key);
}
public static function get_all_models() {
$res = [];
foreach (get_declared_classes() as $class) {
if (is_subclass_of($class, "Model")) {
$res[] = $class;
}
}
}
public function create_or_update_table() {
}
public function get_table_name() {
$class = get_called_class();
if (property_exists($class, "table")) {
$v = get_class_vars($class);
return($v["table"]);
}
return (strtolower($class));
}
}