492 lines
11 KiB
PHP
Executable File
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));
|
|
}
|
|
|
|
}
|