Browse Source

* Allow domains to be mapped to another domain in an app
* Add ORM abstract (Relationships don't work right now
* If a header doesn't exist return null
* Move make_referenced to a static function on SQL
* update Query class to use more modern calls

Nathaniel van Diepen 7 năm trước cách đây
mục cha
commit
23424184d3
4 tập tin đã thay đổi với 264 bổ sung17 xóa
  1. 7 0
      app.class.php
  2. 238 0
      orm.abstract.class.php
  3. 1 1
      request.class.php
  4. 18 16
      sql.class.php

+ 7 - 0
app.class.php

@@ -101,6 +101,9 @@
 			// Domain routers
 			foreach($this->domains as $host => $router){
 				if($url['host'] == $host){
+					while(is_string($router)){
+						$router = $this->domains[$router];
+					}
 					$router->handle($url["path"], $req, $res, null, $onerror);
 					$handled = $handled || $router->handled;
 				}
@@ -141,6 +144,10 @@
 			$fn($this->domains[$host]);
 			return $this;
 		}
+		public function map_domain(string $host1, string $host2){
+			$this->domains[$host2] = $host1;
+			return $this;
+		}
 		public function onerror(Request $req, Response $res, $error){
 			$this->fire('error', $error);
 			$fn = $this->_onerror;

+ 238 - 0
orm.abstract.class.php

@@ -0,0 +1,238 @@
+<?php
+	require_once('sql.class.php');
+	abstract class ORM implements ArrayAccess, JsonSerializable {
+		// Model definition
+		protected static $primary_key = 'id';
+		protected static $foreign_key_suffix = '_id';
+		protected static $has_one = [];
+		protected static $has_many = [];
+		protected static $belongs_to = [];
+		// Data tracking
+		private $_data = [];
+		private $_changed = [];
+		private $_related = [];
+		private static $aliases = [];
+		private static $sql;
+		// Magic functions
+		public function __construct($idOrData){
+			if(empty(self::$aliases[$this->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 __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);
+			}
+		}
+		public function release(){
+			$this->__sleep();
+			$this->_changed = [];
+			$this->_data = [];
+		}
+	}
+?>

+ 1 - 1
request.class.php

@@ -32,7 +32,7 @@
 			}
 		}
 		public function header($name){
-			return $this->headers[$name];
+			return $this->headers[$name] ?? null;
 		}
 		public function json(){
 			return json_decode($this->body, true);

+ 18 - 16
sql.class.php

@@ -27,6 +27,9 @@
 				case 'error':
 					return $this->sql->error;
 				break;
+				case 'insert_id':
+					return $this->sql->insert_id;
+				break;
 			}
 		}
 		/**
@@ -38,10 +41,8 @@
 		* @param {Mixed} [bindings]* The bindings to use in the sql statement
 		* @return {Query} Returns the query object
 		*/
-		public function query(){
-			$reflect = new ReflectionClass('Query');
-			$args = array_merge([$this],func_get_args());
-			return $reflect->newInstanceArgs($args);
+		public function query(...$args){
+			return new Query(...array_merge([$this], $args));
 		}
 		public function escape($s){
 			return $this->sql->escape_string($s);
@@ -49,6 +50,13 @@
 		public function charset($charset){
 			return $this->sql->set_charset($charset);
 		}
+		public static function make_referenced(&$arr){
+			$refs = [];
+			foreach($arr as $key => $value){
+				$refs[$key] = &$arr[$key];
+			}
+			return $refs;
+		}
 	}
 	/**
 	* Query class. Returned by SQL::query()
@@ -59,13 +67,14 @@
 	class Query {
 		private $query;
 		private $sql;
-		public function __construct($sql,$source,$types=null){
-			$args = func_get_args();
-			$args = array_splice($args,2);
+		public function __construct($sql, $source, $types=null, ...$args){
+			$args = array_merge([$types], $args);
 			$this->sql = $sql();
-			$this->query = $sql()->prepare($source);
+			$this->query = $this->sql->prepare($source);
 			if(!is_null($types)){
-				call_user_func_array([$this->query, 'bind_param'], make_referenced($args)) or die($sql()->error);
+				if(!$this->query->bind_param(...SQL::make_referenced($args))){
+					throw new Exception("Unable to bind parameter {$this->query->error}");
+				}
 			}
 		}
 		public function __invoke(){
@@ -174,11 +183,4 @@
 			}
 		}
 	}
-	function make_referenced(&$arr){
-		$refs = [];
-		foreach($arr as $key => $value){
-			$refs[$key] = &$arr[$key];
-		}
-		return $refs;
-	}
 ?>