name])){ foreach(self::$belongs_to as $alias => $details){ self::$aliases[$this->name]['belongs_to'][$alias] = array_merge( [ 'model'=>$alias, 'foreign_key'=>$alias.$this->foreign_key_suffix ], $details ); } foreach(self::$has_one as $alias => $details){ self::$aliases[$this->name]['has_one'][$alias] = array_merge( [ 'model'=>$alias, 'foreign_key'=>$alias.$this->foreign_key_suffix ], $details ); } foreach(self::$has_many as $alias => $details){ self::$aliases[$this->name]['has_many'][$alias] = array_merge( [ 'model'=>$alias, 'foreign_key'=>$alias.$this->foreign_key_suffix, 'through'=> null ], $details ); } } // Clear relationship definitions to save memory self::$belongs_to = self::$has_one = self::$has_many =null; if(is_array($idOrData)){ foreach($idOrData as $key => $val){ $this->_data[$key] = $val; } }else{ $this->_data[self::$primary_key] = intval($idOrData); } } public function __destruct(){ $this->__sleep(); $this->_changed = []; $this->_data = []; $this->_related = []; } public function __get(string $name){ switch($name){ case 'name': $name=get_class($this); return substr($name, strrpos($name, '\\')+1); break; case 'id': return $this->get(self::$primary_key); break; default: throw new Exception('Unknown property '.$name); } } public function __set(string $name, $val){ switch($name){ case 'id':case 'name': throw new Exception('Property '.$name.' is read only'); break; default: throw new Exception('Unknown property '.$name); } } public function __call(string $name, array $args){ switch($name){ default: throw new Exception('Method '.$name.' not implemented'); } } public static function __callStatic(string $name, array $args){ throw new Exception('Static method '.$name.' not implemented'); } public function __sleep(){ $this->save(); } public function __wakeup(){ $this->reload(true); } public function __toString(){ return $this->name.'('.$this->id.')'; } public function __invoke(){ return $this->_data; } public function __clone(){ unset($this->$_data[self::$primary_key]); } // JsonSerializable public function jsonSerialize(){ return $this->_data; } // ArrayAccess public function offsetSet($key, $val){ $this->set($key, $val); } public function offsetExists($key){ return $this->has($key); } public function offsetUnset($key){ return $this->unset($key); } public function offsetGet($key){ return $this->get($key); } // Main API public static function bind(SQL $sql){ self::$sql = $sql; // @todo handle updating live instances } public static function query(...$args){ return self::$sql->query(...$args); } // Instance Api public function values($values){ foreach($values as $key => $val){ $this->set($key, $val); } return $this; } public function load(bool $force = false){ if(!$force && $this->dirty()){ throw new Exception('Cannot load, there are pending changes'); }else{ if(!is_null($this->id)){ $data = self::query( "select * " . "from {$this->name} ". "where ".self::$primary_key." = ?", 'i', $this->id )->assoc_result; if($data === false){ throw new Exception("{$this->name} with ".self::$primary_key." of {$this->id} does not exist"); } $this->_data = $data; } } return $this; } public function save(){ if($this->dirty()){ $data = []; $set = "set "; $types = ''; foreach($this->_changed as $key){ if(isset($this->_data[$key]) || is_null($this->_data[$key])){ $data[$key] = $this->_data[$key]; }else{ $set .= "{$key} = null"; } } foreach($data as $key => $val){ if(is_string($val)){ $types .= 's'; }elseif(is_int($val)){ $types .= 'i'; }elseif(is_double($val)){ $types .= 'd'; }else{ throw new Exception('Unknown data type'); } $set .= "{$key} = ? "; } if(!is_null($this->id) && !in_array(self::$primary_key, $this->_changed)){ $data = array_merge(array_values($data), [$this->id]); self::query( "update {$this->name} {$set} where ".self::primary_key." = ?", $types.'i', $data )->execute(); }else{ self::query( "insert {$this->name} {$set}" )->execute(); $this->_data[self::$primary_key] = self::$sql->insert_id; } } // Always fetch again from the database in case saving // forces something to change at the database level return $this->reload(true); } public function reload(bool $force = false){ if($force){ $this->_changed = []; } return $this->load($force); } public function clear(){ return $this->reload(true); } public function get($key){ return $this->_data[$key]; } public function set($key, $val){ if($key === self::$primary_key && !is_null($this->id)){ throw new Exception('You are not allowed to change the primary key'); } $this->_data[$key] = $val; $this->_changed = array_merge($this->_changed, [$key]); return $this; } public function unset($key){ unset($this->_data[$key]); return $this; } public function has($key){ return isset($this->_data[$key]); } public function dirty(string $key = null){ if(is_null($key)){ return count($this->_changed) > 0; }else{ return in_array($key, $this->_changed); } } } ?>