|
@@ -1,7 +1,9 @@
|
|
|
<?php
|
|
|
require_once('sql.class.php');
|
|
|
+ require_once('relationship.class.php');
|
|
|
abstract class ORM implements ArrayAccess, JsonSerializable {
|
|
|
// Model definition
|
|
|
+ protected static $table_name = null;
|
|
|
protected static $primary_key = 'id';
|
|
|
protected static $foreign_key_suffix = '_id';
|
|
|
protected static $has_one = [];
|
|
@@ -14,7 +16,6 @@
|
|
|
private static $aliases = [];
|
|
|
private static $instances = [];
|
|
|
private static $sql;
|
|
|
- protected static $name = null;
|
|
|
// Magic functions
|
|
|
private function __construct($idOrData){
|
|
|
if(!isset(self::$aliases[$this->name])){
|
|
@@ -22,7 +23,7 @@
|
|
|
self::$aliases[$this->name]['belongs_to'][$alias] = array_merge(
|
|
|
[
|
|
|
'model'=>$alias,
|
|
|
- 'foreign_key'=>$alias.static::$foreign_key_suffix
|
|
|
+ 'foreign_key'=>$this->name.static::foreign_key_suffix()
|
|
|
],
|
|
|
$details
|
|
|
);
|
|
@@ -31,7 +32,7 @@
|
|
|
self::$aliases[$this->name]['has_one'][$alias] = array_merge(
|
|
|
[
|
|
|
'model'=>$alias,
|
|
|
- 'foreign_key'=>$alias.static::$foreign_key_suffix
|
|
|
+ 'foreign_key'=>$alias.static::foreign_key_suffix()
|
|
|
],
|
|
|
$details
|
|
|
);
|
|
@@ -40,7 +41,7 @@
|
|
|
self::$aliases[$this->name]['has_many'][$alias] = array_merge(
|
|
|
[
|
|
|
'model'=>$alias,
|
|
|
- 'foreign_key'=>$alias.static::$foreign_key_suffix,
|
|
|
+ 'foreign_key'=>$this->name.static::foreign_key_suffix(),
|
|
|
'through'=> null
|
|
|
],
|
|
|
$details
|
|
@@ -50,7 +51,7 @@
|
|
|
// Clear relationship definitions to save memory
|
|
|
static::$belongs_to = static::$has_one = static::$has_many =null;
|
|
|
if(is_array($idOrData)){
|
|
|
- if(isset($idOrData[static::$primary_key]) && self::cached($idOrData[static::$primary_key])){
|
|
|
+ if(isset($idOrData[static::primary_key()]) && self::cached($idOrData[static::primary_key()])){
|
|
|
throw new Exception('Instance already cached');
|
|
|
}
|
|
|
foreach($idOrData as $key => $val){
|
|
@@ -60,7 +61,7 @@
|
|
|
if(self::cached($idOrData)){
|
|
|
throw new Exception('Instance already cached');
|
|
|
}
|
|
|
- $this->_data[static::$primary_key] = (int)$idOrData;
|
|
|
+ $this->_data[static::primary_key()] = (int)$idOrData;
|
|
|
}
|
|
|
self::$instances[] = $this;
|
|
|
}
|
|
@@ -77,10 +78,19 @@
|
|
|
public function __get(string $name){
|
|
|
switch($name){
|
|
|
case 'name':
|
|
|
- return static::name();
|
|
|
+ return static::table_name();
|
|
|
+ break;
|
|
|
+ case 'primary_key':
|
|
|
+ return static::primary_key();
|
|
|
+ break;
|
|
|
+ case 'foreign_key_suffix':
|
|
|
+ return static::foreign_key_suffix();
|
|
|
+ break;
|
|
|
+ case 'has_one':case 'has_many':case 'belongs_to':
|
|
|
+ return self::$aliases[$this->name][$name];
|
|
|
break;
|
|
|
case 'id':
|
|
|
- return $this->get(static::$primary_key);
|
|
|
+ return $this->get(static::primary_key());
|
|
|
break;
|
|
|
default:
|
|
|
throw new Exception('Unknown property '.$name);
|
|
@@ -108,7 +118,7 @@
|
|
|
return $this->_data;
|
|
|
}
|
|
|
public function __clone(){
|
|
|
- unset($this->$_data[static::$primary_key]);
|
|
|
+ unset($this->$_data[static::primary_key()]);
|
|
|
}
|
|
|
// JsonSerializable
|
|
|
public function jsonSerialize(){
|
|
@@ -128,12 +138,18 @@
|
|
|
return $this->get($key);
|
|
|
}
|
|
|
// Main API
|
|
|
- public static function name(){
|
|
|
- if(is_null(static::$name)){
|
|
|
+ public static function table_name(){
|
|
|
+ if(is_null(static::$table_name)){
|
|
|
$name = get_called_class();
|
|
|
- static::$name = substr($name, strrpos($name, '\\') + 1);
|
|
|
+ return substr($name, strrpos($name, '\\') + 1);
|
|
|
}
|
|
|
- return static::$name;
|
|
|
+ return static::$table_name;
|
|
|
+ }
|
|
|
+ public static function primary_key(){
|
|
|
+ return static::$primary_key;
|
|
|
+ }
|
|
|
+ public static function foreign_key_suffix(){
|
|
|
+ return static::$foreign_key_suffix;
|
|
|
}
|
|
|
public static function bind(SQL $sql){
|
|
|
self::$sql = $sql;
|
|
@@ -154,14 +170,14 @@
|
|
|
public static function exists(int $id){
|
|
|
return (int)self::query(
|
|
|
"select count(1) as count ".
|
|
|
- "from ".static::name().' '.
|
|
|
- "where ".static::$primary_key." = ?",
|
|
|
+ "from ".static::table_name().' '.
|
|
|
+ "where ".static::primary_key()." = ?",
|
|
|
'i',
|
|
|
$id
|
|
|
)->assoc_result["count"] > 0;
|
|
|
}
|
|
|
public static function cached_instance(int $id){
|
|
|
- $name = static::name();
|
|
|
+ $name = static::table_name();
|
|
|
if(isset(self::$instances[$name])){
|
|
|
$instances = array_filter(self::$instances[$name], function(&$instance){
|
|
|
return $instance->id === $id;
|
|
@@ -175,15 +191,15 @@
|
|
|
}
|
|
|
public static function delete(int $id){
|
|
|
$query = self::query(
|
|
|
- "delete from ".static::name().' '.
|
|
|
- "where ".static::$primary_key." = ?",
|
|
|
+ "delete from ".static::table_name().' '.
|
|
|
+ "where ".static::primary_key()." = ?",
|
|
|
'i',
|
|
|
$id
|
|
|
);
|
|
|
return $query->execute() && $query->affected_rows > 0;
|
|
|
}
|
|
|
public static function each_cached(callable $fn){
|
|
|
- $name = static::name();
|
|
|
+ $name = static::table_name();
|
|
|
if(self::$instances[$name]){
|
|
|
array_walk(self::$instances[$name], $fn);
|
|
|
}
|
|
@@ -215,8 +231,8 @@
|
|
|
$where = self::str_replace_first(' and ', ' ', $where);
|
|
|
}
|
|
|
self::query(
|
|
|
- "select ".static::$primary_key.' id '.
|
|
|
- "from ".static::name().
|
|
|
+ "select ".static::primary_key().' id '.
|
|
|
+ "from ".static::table_name().
|
|
|
$where.
|
|
|
$limit,
|
|
|
$types,
|
|
@@ -256,12 +272,12 @@
|
|
|
$data = self::query(
|
|
|
"select * " .
|
|
|
"from {$this->name} ".
|
|
|
- "where ".static::$primary_key." = ?",
|
|
|
+ "where ".static::primary_key()." = ?",
|
|
|
'i',
|
|
|
$this->id
|
|
|
)->assoc_result;
|
|
|
if($data === false){
|
|
|
- throw new Exception("{$this->name} with ".static::$primary_key." of {$this->id} does not exist");
|
|
|
+ throw new Exception("{$this->name} with ".static::primary_key()." of {$this->id} does not exist");
|
|
|
}
|
|
|
$this->_data = $data;
|
|
|
}
|
|
@@ -292,10 +308,10 @@
|
|
|
}
|
|
|
$set .= "{$key} = ? ";
|
|
|
}
|
|
|
- if(!is_null($this->id) && !in_array(static::$primary_key, $this->_changed)){
|
|
|
+ if(!is_null($this->id) && !in_array(static::primary_key(), $this->_changed)){
|
|
|
$data = array_merge(array_values($data), [$this->id]);
|
|
|
self::query(
|
|
|
- "update {$this->name} {$set} where ".static::$primary_key." = ?",
|
|
|
+ "update {$this->name} {$set} where ".static::primary_key()." = ?",
|
|
|
$types.'i',
|
|
|
$data
|
|
|
)->execute();
|
|
@@ -303,7 +319,10 @@
|
|
|
self::query(
|
|
|
"insert {$this->name} {$set}"
|
|
|
)->execute();
|
|
|
- $this->_data[static::$primary_key] = self::$sql->insert_id;
|
|
|
+ $this->_data[static::primary_key()] = self::$sql->insert_id;
|
|
|
+ }
|
|
|
+ foreach($this->_related as $related){
|
|
|
+ $related->save();
|
|
|
}
|
|
|
}
|
|
|
// Always fetch again from the database in case saving
|
|
@@ -323,7 +342,7 @@
|
|
|
return $this->_data[$key];
|
|
|
}
|
|
|
public function set($key, $val){
|
|
|
- if($key === static::$primary_key && !is_null($this->id)){
|
|
|
+ if($key === static::primary_key() && !is_null($this->id)){
|
|
|
throw new Exception('You are not allowed to change the primary key');
|
|
|
}
|
|
|
$this->_data[$key] = $val;
|
|
@@ -345,17 +364,35 @@
|
|
|
}
|
|
|
}
|
|
|
public function related(string $name){
|
|
|
- if(isset($this->_related[$name])){
|
|
|
- return $this->_related[$name];
|
|
|
- }else{
|
|
|
- $class = "Model\\{$name}";
|
|
|
- if(self::$aliases['belongs_to'][$name]){
|
|
|
- $this->_related[$name] = $class::fetch([static::$foreign_key => $this->id]);
|
|
|
- }elseif(self::$aliases['has_one'][$name]){
|
|
|
- $this->_related[$name] = $class::fetch([$this->name.static::$foreign_key_suffix => $this->id]);
|
|
|
- }elseif(self::$aliases['has_many'][$name]){
|
|
|
- //$this->_related[$name] = $class::fetch([$this->name.static::$foreign_key_suffix => $this->id]);
|
|
|
- throw new Exception("has_many relationships are not implemented yet");
|
|
|
+ if(!isset($this->_related[$name])){
|
|
|
+ $aliases = self::$aliases[$this->name];
|
|
|
+ if($aliases['belongs_to'][$name]){
|
|
|
+ $alias = $aliases['has_many'][$name];
|
|
|
+ $class = "Models\\{$alias['model']}";
|
|
|
+ $this->_related[$name] = $class::fetch([$alias['foreign_key'] => $this->id])[0];
|
|
|
+ }elseif($aliases['has_one'][$name]){
|
|
|
+ $alias = $aliases['has_many'][$name];
|
|
|
+ $class = "Models\\{$alias['model']}";
|
|
|
+ $this->_related[$name] = $class::instance($this[$alias['foreign_key']]);
|
|
|
+ }elseif($aliases['has_many'][$name]){
|
|
|
+ $alias = $aliases['has_many'][$name];
|
|
|
+ $class = "Models\\{$alias['model']}";
|
|
|
+ $sql = "select ";
|
|
|
+ if($alias['through']){
|
|
|
+ $sql .= $class::table_name().$class::foreign_key_suffix()." id from {$alias['through']} ";
|
|
|
+ }else{
|
|
|
+ $sql .= $class::primary_key()." id from {$alias['model']} ";
|
|
|
+ }
|
|
|
+ $sql .= "where ".$alias['foreign_key']." = ?";
|
|
|
+ $related = [];
|
|
|
+ self::query(
|
|
|
+ $sql,
|
|
|
+ 'i',
|
|
|
+ $this->id
|
|
|
+ )->each_assoc(function($row) use(&$related, $class){
|
|
|
+ $related[] = new $class($row['id']);
|
|
|
+ });
|
|
|
+ $this->_related[$name] = Relationship::from($this, $name, $alias, $related);
|
|
|
}
|
|
|
}
|
|
|
if(isset($this->_related[$name])){
|