Browse Source

* Remove old sql code since pdo is much nicer
* Update ORM/Relationship to use PDO

Nathaniel van Diepen 7 years ago
parent
commit
ae46728aa8
8 changed files with 116 additions and 499 deletions
  1. 34 18
      ORM/relationship.class.php
  2. 14 9
      PDO/table.class.php
  3. 3 0
      PDO/transaction.class.php
  4. 0 95
      SQL/migration.abstract.class.php
  5. 0 168
      SQL/query.class.php
  6. 58 93
      orm.abstract.class.php
  7. 7 0
      pdo.class.php
  8. 0 116
      sql.class.php

+ 34 - 18
ORM/relationship.class.php

@@ -1,7 +1,7 @@
 <?php
 	namespace Juju\ORM;
 	require_once(realpath(dirname(__DIR__).'/Data/earray.class.php'));
-	use \Juju\{ORM, Data\EArray};
+	use \Juju\{Data\EArray, ORM};
 
 	class Relationship extends EArray {
 		private $model;
@@ -65,31 +65,47 @@
 				if(isset($model->has_many[$name])){
 					throw new \Exception("Invalid relationship {$name}");
 				}
+				$rem_args = [];
 				if(isset($alias['through'])){
-					$rem_sql = "delete from {$alias['through']} where {$alias['foreign_key']} = ? and ".$class::table_name().$class::foreign_key_suffix()." = ?";
-					$rem_args = [$model->id];
-					$add_sql = "insert into {$alias['through']} ({$alias['foreign_key']}, ".$class::table_name().$class::foreign_key_suffix().") values (?, ?)";
+					$rem = $class::prepare(
+						"delete from {$alias['through']} ".
+						"where {$alias['foreign_key']} = ".$class::quote($model->id).
+						" and ".$class::table_name().$class::foreign_key_suffix()." = ?"
+					);
+					$add = $class::prepare(
+						"insert into {$alias['through']} (".
+							"{$alias['foreign_key']}, ".
+							$class::table_name().$class::foreign_key_suffix().
+						") values (".
+							$class::quote($model->id).", ".
+							"?".
+						")"
+					);
+					$rem_args[] = $model->id;
 				}else{
-					$rem_sql = "update {$alias['model']} set {$alias['foreign_key']} = null where ".$class::primary_key()." = ?";
-					$rem_args = [];
-					$add_sql .= "update {$alias['model']} set {$alias['foreign_key']} = ? where ".$class::primary_key()." = ?";
+					$rem = $class::prepare(
+						"update {$alias['model']} ".
+						"set {$alias['foreign_key']} = null ".
+						"where ".$class::primary_key()." = ?"
+					);
+					$add = $class::prepare(
+						"update {$alias['model']} ".
+						"set {$alias['foreign_key']} = ".$class::quote($model->id).
+						" where ".$class::primary_key()." = ?"
+					);
 				}
 				$class = get_class($models);
 				foreach($this->removed as $item){
-					$class::query(
-						$rem_sql,
-						'ii',
-						...array_merge($rem_args, [$item->id])
-					)->execute();
+					$rem_args[1] = $item->id;
+					$query = $rem->execute($rem_args);
+					while($query->fetch()){}
+					$query->closeCursor();
 				}
 				$this->removed = [];
 				foreach($this->added as $item){
-					$class::query(
-						$add_sql,
-						'ii',
-						$model->id,
-						$item->id
-					)->execute();
+					$query = $add->execute([$item->id]);
+					while($query->fetch()){}
+					$query->closeCursor();
 				}
 				$this->added = [];
 			}

+ 14 - 9
PDO/table.class.php

@@ -62,6 +62,16 @@
 			}
 			return "$where";
 		}
+		public function stringSet(array $data){
+			$sets = '';
+			foreach($data as $name => $value){
+				$sets .= "`{$name}` = {$this->pdo->quote($value)},";
+			}
+			if(count($sets) > 0){
+				$sets = 'set '.substr($sets, 0, count($sets) - 2);
+			}
+			return $sets;
+		}
 		public function commit(){
 			$pdo = $this->pdo;
 			if(!$this->exists){
@@ -124,15 +134,10 @@
 			}
 		}
 		public function insert(array $data){
-			$pdo = $this->pdo;
-			$sets = '';
-			foreach($data as $name => $value){
-				$sets .= "`{$name}` = {$pdo->quote($value)},";
-			}
-			if(count($sets) > 0){
-				$sets = substr($sets, 0, count($sets) - 2);
-			}
-			return $pdo->exec("insert into `{$this->name}` set {$sets}");
+			return $this->pdo->exec("insert into `{$this->name}` {$this->stringSet($data)}");
+		}
+		public function update(array $data, array $filter = null){
+			return $this->pdo->exec("update `{$this->name} {$this->stringSet($data)} {$this->stringFilter($filter)}`");
 		}
 		public function delete(array $filter = null){
 			return $this->pdo->exec("delete from `{$this->name}` {$this->stringFilter($filter)}");

+ 3 - 0
PDO/transaction.class.php

@@ -72,6 +72,9 @@
 		public function quote(...$args){
 			return $this->pdo->quote(...$args);
 		}
+		public function lastInsertId(...$args){
+			return $this->pdo->lastInsertId(...$args);
+		}
 		public function getError(){
 			return $this->pdo->getError();
 		}

+ 0 - 95
SQL/migration.abstract.class.php

@@ -1,95 +0,0 @@
-<?php
-	namespace Juju\SQL;
-	require_once(realpath(dirname(__DIR__).'/sql.class.php'));
-	use Juju\{SQL, Settings};
-
-	abstract class Migration {
-		public abstract static function up();
-		public abstract static function down();
-		public abstract static function change();
-
-		const MIGRATE_UP = 'up';
-		const MIGRATE_DOWN = 'down';
-		private static $sql;
-
-		final public static function version(){
-			$name = get_called_class();
-			return substr($name, strrpos($name, '\\') + 1);
-		}
-		final public static function version_table(){
-			if(!class_exists("Juju\\Settings")){
-				throw new \Exception("Settings not loaded");
-			}
-			return Settings::get('db.versions');
-		}
-		final public static function installed(){
-			return self::$sql->query(
-				"select count(1) count ".
-				"from `".self::version_table()."` ".
-				"where version = ?",
-				's',
-				static::version()
-			)->assoc_result['count'] == 1;
-		}
-		final public static function migrations(){
-			$migrations = array_filter(get_declared_classes(), function($class){
-				return 0 === strpos($class, "Migration\\");
-			});
-			sort($migrations);
-			return $migrations;
-		}
-		final public static function migrate(string $direction, int $amount = 1){
-			if($amount < 1){
-				throw new \Exception("Migration amount must be a positive integer");
-			}
-			$table = self::version_table();
-			$sql = self::$sql;
-			if(count($sql->query("show tables like '{$table}'")->assoc_results) == 0){
-				$sql->query("CREATE TABLE `{$table}` (version varchar(100) NOT NULL, primary key (version))")->execute();
-			}
-			switch($direction){
-				case self::MIGRATE_UP:
-					foreach(self::migrations() as $migration){
-						if(!$migration::installed()){
-							$migration::up();
-							$sql->query(
-								"insert into `{$table}` set version = ?",
-								's',
-								$migration::version()
-							)->execute();
-							if(--$amount == 0){
-								break;
-							}
-						}
-					}
-				break;
-				case self::MIGRATE_DOWN:
-					foreach(array_reverse(self::migrations()) as $migration){
-						if($migration::installed()){
-							$migration::down();
-							$sql->query(
-								"delete from `{$table}` where version = ?",
-								's',
-								$migration::version()
-							)->execute();
-							if(--$amount == 0){
-								break;
-							}
-						}
-					}
-				break;
-				default:
-					throw new \Exception("Invalid migration direction '{$direction}'");
-			}
-		}
-		final public static function migrate_all(string $direction){
-			self::migrate($direction, count(self::migrations()));
-		}
-		final public static function bind(string $dsn){
-			self::$sql = SQL::FromDSN($dsn);
-		}
-		final public static function release(){
-			self::$sql = null;
-		}
-	}
-?>

+ 0 - 168
SQL/query.class.php

@@ -1,168 +0,0 @@
-<?php
-	namespace Juju\SQL;
-
-	/**
-	* Query class. Returned by \SQL::query()
-	*
-	* @class Query
-	* @constructor
-	*/
-	class Query {
-		private $query;
-		private $sql;
-		private $_result = false;
-		public function __construct($sql, $source, $types=null, ...$args){
-			$sql->queries[] = $this;
-			$args = array_merge([$types], $args);
-			$this->sql = $sql();
-			$this->query = $this->sql->stmt_init();
-			if(!$this->query->prepare($source)){
-				throw new \RuntimeException($this->query->error);
-			}
-			if(!is_null($types)){
-				if(!$this->query->bind_param(...\Juju\SQL::make_referenced($args))){
-					throw new \RuntimeException("Unable to bind parameter {$this->query->error}");
-				}
-			}
-		}
-		public function __destruct(){
-			if($this->_result){
-				$this->_result = false;
-				$this->query->free_result();
-			}
-			if($this->query){
-				$this->query->close();
-			}
-			$sql->queries = array_diff($sql->queries, [$this]);
-		}
-		public function __invoke(){
-			return $this->query;
-		}
-		public function execute(){
-			if($this->query){
-				$this->query->free_result();
-				$this->_result = false;
-				$this->query->reset();
-				$r = $this->query->execute();
-				$this->sql->commit();
-				return $r;
-			}else{
-				return false;
-			}
-		}
-		public function each_assoc(callable $fn){
-			if($this->query){
-				$r = $this->results;
-				while($row = $r->fetch_assoc()){
-					$fn($row);
-				}
-			}else{
-				return false;
-			}
-		}
-		public function each_num(callable $fn){
-			if($this->query){
-				$r = $this->results;
-				while($row = $r->fetch_num()){
-					$fn($row);
-				}
-			}else{
-				return false;
-			}
-		}
-		public function __get($name){
-			switch($name){
-				/**
-				* Returns the mysqli::results object for the
-				* query
-				* 
-				* @property results
-				* @type {mysqli::results}
-				* @public
-				*/
-				case 'results':
-					if(!$this->_result && $this->query){
-						$this->execute();
-						$this->_result = $this->query->get_result();
-						$this->query->close();
-					}
-					return $this->_result;
-				break;
-				/**
-				* Returns an associative array of the query resulsts
-				* 
-				* @property assoc_results
-				* @type {Array}
-				* @public
-				*/
-				/**
-				* Returns an associative array of the query resulsts
-				* 
-				* @property resulsts_assoc
-				* @type {Array}
-				* @public
-				*/
-				case 'assoc_results':case 'results_assoc':
-					if($this->query){
-						$a = [];
-						$r = $this->results;
-						while($row = $r->fetch_assoc()){
-							array_push($a,$row);
-						}
-						return $a;
-					}else{
-						return false;
-					}
-				break;
-				/**
-				* Returns a numbered array of the query results
-				* 
-				* @property num_results
-				* @type {Array}
-				* @public
-				*/
-				/**
-				* Returns a numbered array of the query results
-				* 
-				* @property resulsts_num
-				* @type {Array}
-				* @public
-				*/
-				case 'num_results':case 'results_num':
-					if($this->query){
-						$a = [];
-						$r = $this->results;
-						while($row = $r->fetch_num()){
-							array_push($a,$row);
-						}
-						return $a;
-					}else{
-						return false;
-					}
-				break;
-				case 'assoc_result':case 'result_assoc':
-					if($this->query){
-						$r = $this->results;
-						return $r?$r->fetch_assoc():false;
-					}else{
-						return false;
-					}
-				break;
-				case 'num_result':case 'result_num':
-					if($this->query){
-						$r = $this->results;
-						return $r?$r->fetch_num():false;
-					}else{
-						return false;
-					}
-				break;
-				case 'insert_id':
-					return $this->sql->insert_id;
-				break;
-				case 'affected_rows':
-					return $this->query->affected_rows;
-				break;
-			}
-		}
-	}
-?>

+ 58 - 93
orm.abstract.class.php

@@ -1,9 +1,9 @@
 <?php
 	namespace Juju;
-	require_once('sql.class.php');
+	require_once('pdo.class.php');
 	require_once('ORM/relationship.class.php');
 	require_once('Data/securestring.class.php');
-	use \Juju\{ORM\Relationship, Data\SecureString};
+	use \Juju\{Data\SecureString, ORM\Relationship, PDO};
 
 	abstract class ORM implements \ArrayAccess, \JsonSerializable {
 		// Model definition
@@ -19,7 +19,8 @@
 		private $_related = [];
 		private static $aliases = [];
 		private static $instances = [];
-		private static $sql;
+		private static $pdo;
+		private static $table;
 		// Magic functions
 		private function __construct($idOrData){
 			if(!isset(self::$aliases[$this->name])){
@@ -167,22 +168,32 @@
 		public static function foreign_key_suffix(){
 			return static::$foreign_key_suffix;
 		}
-		public static function bind($sql){
-			if(is_string($sql)){
-				$sql = SQL::FromDSN($sql);
+		public static function bind($pdo){
+			if(is_string($pdo)){
+				$pdo = PDO::from($pdo);
 			}
-			if($sql instanceof SecureString){
-				$sql = SQL::FromDSN((string)$sql);
+			if($pdo instanceof SecureString){
+				$pdo = PDO::from((string)$pdo);
 			}
-			if($sql instanceof SQL){
-				self::$sql = $sql;
+			if($pdo instanceof PDO){
+				self::$pdo = $pdo;
+				self::$table = $pdo->table(static::table_name());
 				// @todo handle updating live instances
 			}else{
-				throw new \Exception("Invalid argument. Must pass a DSN string or an SQL instance");
+				throw new \Exception("Invalid argument. Must pass a DSN string or a PDO instance");
 			}
 		}
 		public static function query(...$args){
-			return self::$sql->query(...$args);
+			return self::$pdo->query(...$args);
+		}
+		public static function exec(...$args){
+			return self::$pdo->exec(...$args);
+		}
+		public static function prepare(...$args){
+			return self::$pdo->prepare(...$args);
+		}
+		public static function quote(...$args){
+			return self::$pdo->quote(...$args);
 		}
 		public static function instance(int $id){
 			$instance = self::cached_instance($id);
@@ -194,13 +205,14 @@
 			return null;
 		}
 		public static function exists(int $id){
-			return (int)self::query(
+			$query = self::query(
 				"select count(1) as count ".
-				"from ".static::table_name().' '.
-				"where ".static::primary_key()." = ?",
-				'i',
-				$id
-			)->assoc_result["count"] > 0;
+				"from `".static::table_name().'` '.
+				"where `".static::primary_key()."` = ".self::quote($id)
+			);
+			$count = $query->fetch()['count'];
+			$query->closeCursor();
+			return (int)$count > 0;
 		}
 		public static function cached_instance(int $id){
 			$name = static::table_name();
@@ -216,13 +228,9 @@
 			return !is_null(self::cached_instance($id));
 		}
 		public static function delete(int $id){
-			$query = self::query(
-				"delete from ".static::table_name().' '.
-				"where ".static::primary_key()." = ?",
-				'i',
-				$id
-			);
-			return $query->execute() && $query->affected_rows > 0;
+			return self::$table->delete([
+				static::primary_key() => $id
+			]) > 0;
 		}
 		public static function each_cached(callable $fn){
 			$name = static::table_name();
@@ -231,41 +239,19 @@
 			}
 		}
 		public static function each_where(callable $fn, array $filter = null, int $start = null, int $amount = null){
-			$limit = ' ';
+			$limit = '';
 			if(!is_null($start) && !is_null($amount)){
-				$limit .= "limit {$start}, {$amount}";
+				$limit .= " limit {$start}, {$amount}";
 			}
-			$where = ' ';
-			$types = null;
-			$bindings = null;
-			if(!is_null($filter)){
-				$types = '';
-				$bindings = array();
-				foreach($filter as $key => $val){
-					if(is_string($val)){
-						$types .= 's';
-					}elseif(is_double($val)){
-						$types .= 'd';
-					}elseif(is_int($val)){
-						$types .= 'i';
-					}else{
-						throw new \Exception("Unknown data type");
-					}
-					$where .= 'and {$key} = ? ';
-					$bindings[] = $val;
-				}
-				$where = self::str_replace_first(' and ', ' ', $where);
-			}
-			self::query(
+			$query = self::query(
 				"select ".static::primary_key().' id '.
-				"from ".static::table_name().
-				$where.
-				$limit,
-				$types,
-				$bindings
-			)->each_assoc(function($row) use($fn){
+				"from ".static::table_name().' '.
+				self::$table->stringFilter($filter).
+				$limit
+			);
+			foreach($query->fetchAll() as $row){
 				$fn(self::instance((int)$row['id']));
-			});
+			}
 		}
 		public static function each(callable $fn, int $start = null, int $amount = null){
 			self::each_where($fn, null, $start, $amount);
@@ -300,13 +286,13 @@
 				throw new \Exception('Cannot load, there are pending changes');
 			}else{
 				if(!is_null($this->id)){
-					$data = self::query(
+					$query = self::query(
 						"select * " .
 						"from {$this->name} ".
-						"where ".static::primary_key()." = ?",
-						'i',
-						$this->id
-					)->assoc_result;
+						"where ".static::primary_key()." = ".self::quote($this->id)
+					);
+					$data = $query->fetch();
+					$query->closeCursor();
 					if($data === false){
 						throw new \Exception("{$this->name} with ".static::primary_key()." of {$this->id} does not exist");
 					}
@@ -318,39 +304,20 @@
 		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";
+						$data[$key] = null;
 					}
 				}
-				foreach($data as $key => $val){
-					if(is_string($val)){
-						$types .= 's';
-					}elseif(is_double($val)){
-						$types .= 'd';
-					}elseif(is_int($val)){
-						$types .= 'i';
-					}else{
-						throw new \Exception('Unknown data type');
-					}
-					$set .= "{$key} = ? ";
-				}
 				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()." = ?",
-						$types.'i',
-						$data
-					)->execute();
+					self::$table->update($data, [
+						static::primary_key() => $this->id
+					]);
 				}else{
-					self::query(
-						"insert {$this->name} {$set}"
-					)->execute();
-					$this->_data[static::primary_key()] = self::$sql->insert_id;
+					self::$table->insert($data);
+					$this->_data[static::primary_key()] = self::$pdo->lastInsertId();
 				}
 				foreach($this->_related as $related){
 					$related->save();
@@ -414,15 +381,13 @@
 					}else{
 						$sql .= $class::primary_key()." id from {$alias['model']} ";
 					}
-					$sql .= "where ".$alias['foreign_key']." = ?";
+					$sql .= "where ".$alias['foreign_key']." = ".self::quote($this->id);
 					$related = [];
-					self::query(
-						$sql,
-						'i',
-						$this->id
-					)->each_assoc(function($row) use(&$related, $class){
+					$query = self::query($sql);
+					foreach($query->fetchAll() as $row){
 						$related[] = new $class($row['id']);
-					});
+					}
+					$query->closeCursor();
 					$this->_related[$name] = Relationship::from($this, $name, $alias, $related);
 				}
 			}

+ 7 - 0
pdo.class.php

@@ -46,6 +46,7 @@
 			if($mysql){
 				$options[\PDO::MYSQL_ATTR_INIT_COMMAND] = "SET NAMES 'UTF8'";
 				$options[\PDO::MYSQL_ATTR_MULTI_STATEMENTS] = false;
+				$options[\PDO::MYSQL_ATTR_FOUND_ROWS] = true;
 			}
 			$pdo = new \PDO($dsn, $user, (string)$pass, $options);
 			$pdo->setAttribute(\PDO::ATTR_AUTOCOMMIT, true);
@@ -96,6 +97,9 @@
 			while($query->fetch() !== false){
 				$count++;
 			}
+			if($count == 0){
+				$count = $query->rowCount();
+			}
 			$query->closeCursor();
 			return $count;
 		}
@@ -119,6 +123,9 @@
 		public function getAttribute(...$args){
 			return $this->pdo->getAttribute(...$args);
 		}
+		public function lastInsertId(...$args){
+			return $this->pdo->lastInsertId(...$args);
+		}
 		public function getError(){
 			$error = $this->pdo->errorInfo();
 			return new \Exception($error[2], $error[1]);

+ 0 - 116
sql.class.php

@@ -1,116 +0,0 @@
-<?php
-	namespace Juju;
-	require_once('SQL/query.class.php');
-	require_once('Data/securestring.class.php');
-	use \Juju\{SQL\Query, Data\SecureString};
-
-	/**
-	* SQL class. Used for handling SQL connections
-	*
-	* @module sql
-	* @class SQL
-	* @constructor
-	*/
-	class SQL {
-		/**
-		* This is the mysqli connection beneath everything
-		* 
-		* @property sql
-		* @type {mysqli}
-		* @private
-		* @required
-		*/
-		private $guid;
-		private $sql;
-		public $queries = [];
-		private static $connections = [];
-		public static function FromDSN(string $dsnstring){
-			$dsnstring = explode(':', $dsnstring)[1];
-			$dsn = explode(';', $dsnstring);
-			$dsn = array_reduce($dsn, function($dsn, $item){
-				$item = explode('=', $item);
-				$dsn[$item[0]] = $item[1];
-				return $dsn;
-			});
-			if(!isset($dsn['host'])){
-				throw new \Exception("DSN '{$dsnstring}' missing host");
-			}
-			if(!isset($dsn['dbname'])){
-				throw new \Exception("DSN '{$dsnstring}' missing dbname");
-			}
-			if(!isset($dsn['user'])){
-				$dsn['user'] = $dsn['dbname'];
-			}
-			if(!isset($dsn['pass'])){
-				$dsn['pass'] = $dsn['user'];
-			}
-			$dsn['pass'] = SecureString::from($dsn['pass']);
-			return new SQL($dsn['host'], $dsn['user'], $dsn['pass'], $dsn['dbname']);
-		}
-		public function __construct($server,$user,$pass,$db){
-			$this->guid = uniqid();
-			$this->sql = new \mysqli('p:'.$server,$user,"{$pass}",$db);;
-			if($this->sql->connect_error){
-				throw new \Exception('Mysqli Connect Error (' . $mysqli->connect_errno . ') ' . $mysqli->connect_error);
-			}
-			self::$connections[] = $sql;
-		}
-		public function __destruct(){
-			$this->sql->rollback();
-			$this->sql->close();
-			foreach($this->queries as $query){
-				unset($query);
-			}
-			self::$connections = array_diff(self::$connections, [$this]);
-		}
-		public function __invoke(){
-			return $this->sql;
-		}
-		public function __get($name){
-			switch($name){
-				case 'error':
-					return $this->sql->error;
-				break;
-				case 'insert_id':
-					return $this->sql->insert_id;
-				break;
-			}
-		}
-		public function __toString(){
-			return $this->guid;
-		}
-		/**
-		* Returns a Query object based on inputs
-		*
-		* @method query
-		* @param {String} sql The sql expression to run
-		* @param {String=null} [types] A string containing all the types of arguments being passed
-		* @param {Mixed} [bindings]* The bindings to use in the sql statement
-		* @return {Query} Returns the query object
-		*/
-		public function query(...$args){
-			return new SQL\Query(...array_merge([$this], $args));
-		}
-		public function escape($s){
-			return $this->sql->escape_string($s);
-		}
-		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;
-		}
-		public static function shutdown(){
-			foreach(self::$connections as $sql){
-				unset($sql);
-			}
-		}
-	}
-	register_shutdown_function(function(){
-		SQL::shutdown();
-	});
-?>