diff --git a/AroMVC/Configuration.php b/AroMVC/Configuration.php index 3f92843..956e3f6 100644 --- a/AroMVC/Configuration.php +++ b/AroMVC/Configuration.php @@ -13,7 +13,7 @@ class Configuration { if(self::$data != null) return; - self::$data = parse_ini_string($data); + self::$data = parse_ini_string($data, true); } public static function section(string $name): ConfigSection { diff --git a/AroMVC/Database.php b/AroMVC/Database.php index 1503ba4..2bde838 100644 --- a/AroMVC/Database.php +++ b/AroMVC/Database.php @@ -10,6 +10,7 @@ class Database { /** @var \PDO */ private static $dbConn; private static $queries = []; + private static $batchQueue = []; public static function initialize() { self::$dbConn = new \PDO( @@ -17,16 +18,36 @@ class Database { conf::section(AMVC_CNF_DB)->value(AMVC_CNF_DB_USER), conf::section(AMVC_CNF_DB)->value(AMVC_CNF_DB_PASS) ); + + self::$dbConn->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); } - public static function select($columns): Selectable { - return new Selectable($columns); + public static function select($columns, $model = null): Selectable { + return new Selectable($columns, $model); + } + + public static function update($table): Modifiable { + return new Modifiable($table); + } + + public static function insert($table): Insertable { + return new Insertable($table); + } + + public static function delete($table): Deletable { + return new Deletable($table); + } + + public static function batchQuery(Queryable $query) { + self::getStatement($query->getRawQuery()); + $batchQueue[] = [self::hashQuery($query->getRawQuery()), $query->getParams()]; } public static function rawQuery(string $query, $params = null, int $type = AMVC_DB_FETCH_ROWS): array { $stmt = self::getStatement($query); foreach($params as $name => $param) $stmt->bindParam(":$name", $param); + $stmt->execute(); switch($type) { @@ -76,4 +97,18 @@ class Database { return md5($query); } + + protected static function updateDatabase() { + self::$dbConn->beginTransaction(); + + foreach(self::$batchQueue as $query) { + $stmt = self::getStatement($query[0]); + foreach($query[1] as $name => $param) + $stmt->bindValue(":$name", $param); + $stmt->execute(); + } + + self::$dbConn->commit(); + self::$batchQueue = []; + } } \ No newline at end of file diff --git a/AroMVC/Deletable.php b/AroMVC/Deletable.php new file mode 100644 index 0000000..4842b47 --- /dev/null +++ b/AroMVC/Deletable.php @@ -0,0 +1,6 @@ +table = $tableName; + } protected function get(string $name) { return $this->rawData[$name]; @@ -61,11 +68,15 @@ abstract class Model { $this->associations[strtolower($associationName)] = strtolower($rawName); } - protected function update() { + public static function select(): Selectable { + return db::select("*", new static)->from(self::$table); + } + + public function update() { } - protected function delete() { + public function delete() { } } \ No newline at end of file diff --git a/AroMVC/Modifiable.php b/AroMVC/Modifiable.php new file mode 100644 index 0000000..d1702a9 --- /dev/null +++ b/AroMVC/Modifiable.php @@ -0,0 +1,6 @@ +results = db::rawRowQuery($this->query, $this->params); } + public function getRawQuery() { + return $this->query; + } + + public function getParams() { + return $this->params; + } + /*protected function allowConditionals(bool $allow) { if($allow) $this->setFlag(AMVC_QRY_CNDLS); @@ -60,4 +68,9 @@ abstract class Queryable { protected function unlock(int $lock) { $this->locks &= ~$lock; } + + protected function testLocked() { + if($this->locks != 0) + throw new \Exception("Query is locked from previous step and cannot proceed (LOCKVAL $this->locks)"); + } } \ No newline at end of file diff --git a/AroMVC/Selectable.php b/AroMVC/Selectable.php index ac820c8..4244e48 100644 --- a/AroMVC/Selectable.php +++ b/AroMVC/Selectable.php @@ -12,9 +12,9 @@ define("AMVC_QRY_SEL_HAVING", 5); define("AMVC_QRY_SEL_ORDER", 6); define("AMVC_QRY_SEL_LIMIT", 7); -// TODO allow appending and rewriting of existing data in a query from any point - class Selectable extends Queryable { + /** @var null|\ReflectionClass */ + protected $model = null; protected $components = [ AMVC_QRY_SEL_COLS => [], AMVC_QRY_SEL_FROM => null, @@ -26,7 +26,22 @@ class Selectable extends Queryable { AMVC_QRY_SEL_LIMIT => null ]; - public function __construct($selection) { + protected function getModelReflection($model): \ReflectionClass { + $type = new \ReflectionClass($model); + if(!$type->isSubclassOf("\\AroMVC\\Core\\AroModel")) + throw new \Exception("Cannot instantiate non-model object."); + + return $type; + } + + protected function isEmpty(int $component): bool { + return count($this->components[$component]) == 0; + } + + public function __construct($selection, $model = null) { + if($model != null) + $this->model = $this->getModelReflection($model); + if(!is_array($selection)) $selection = [$selection]; @@ -34,10 +49,9 @@ class Selectable extends Queryable { } public function execute(): Selectable { - if($this->checkFlag(AMVC_QRY_SEL_NEEDON)) - throw new \Exception("JOIN clause declared in query with no matching ON or USING clause"); + $this->testLocked(); - if($this->checkFlag(AMVC_QRY_SEL_GROUP) && !$this->checkFlag(AMVC_QRY_SEL_ORDER)) { + /*if($this->checkFlag(AMVC_QRY_SEL_GROUP) && !$this->checkFlag(AMVC_QRY_SEL_ORDER)) { if($this->checkFlag(AMVC_QRY_SEL_HAVING)) { $having = array_search("HAVING", $this->query); array_splice($this->query, $having + 2, 0, ["ORDER BY", "null"]); @@ -45,103 +59,133 @@ class Selectable extends Queryable { $group = array_search("GROUP BY", $this->query); array_splice($this->query, $group + 2, 0, ["ORDER BY", "null"]); } + }*/ + + $query = [ + "SELECT", + implode(",", $this->components[AMVC_QRY_SEL_COLS]), + "FROM", + "`". $this->components[AMVC_QRY_SEL_FROM] ."`" + ]; + + foreach($this->components[AMVC_QRY_SEL_JOINS] as $join) + array_push($query, implode(" ", $join)); + + if(!$this->isEmpty(AMVC_QRY_SEL_WHERE)) { + $wheres = array_map(function($x) { + return "($x)"; + }, $this->components[AMVC_QRY_SEL_WHERE]); + array_push($query, "WHERE", implode(" AND ", $wheres)); } + if(!$this->isEmpty(AMVC_QRY_SEL_GROUP)) + array_push($query, "GROUP BY", implode(", ", $this->components[AMVC_QRY_SEL_GROUP])); + + if(!$this->isEmpty(AMVC_QRY_SEL_HAVING)) { + $haves = array_map(function($x) { + return "($x)"; + }, $this->components[AMVC_QRY_SEL_HAVING]); + array_push($query, "HAVING", implode(" AND ", $haves)); + } + + if(!$this->isEmpty(AMVC_QRY_SEL_ORDER)) + array_push($query, "ORDER BY", implode(", ", $this->components[AMVC_QRY_SEL_GROUP])); + + if($this->components[AMVC_QRY_SEL_LIMIT] != null) + array_push($query, "LIMIT", $this->components[AMVC_QRY_SEL_LIMIT[0]] .",". $this->components[AMVC_QRY_SEL_LIMIT[1]]); + + $this->query = implode(" ", $query); parent::execute(); return $this; } public function from(string $where): Selectable { + $this->testLocked(); if($this->components[AMVC_QRY_SEL_FROM] != null) throw new \Exception("Cannot redefine FROM clause after first definition"); - array_push($this->query, "FROM", $where); + $this->components[AMVC_QRY_SEL_FROM] = $where; + return $this; + } + + public function columns($columns): Selectable { + $this->testLocked(); + if(!is_array($columns)) + $columns = [$columns]; + + $this->components[AMVC_QRY_SEL_COLS] = $columns; return $this; } public function join(string $type, string $table): Selectable { - if($this->checkPast(AMVC_QRY_SEL_JOINS)) - throw new \Exception("Invalid JOIN clause, must proceed FROM clause"); - - $this->setFlag(AMVC_QRY_SEL_JOINS); - $this->setFlag(AMVC_QRY_SEL_NEEDON); - array_push($this->query, $type, $table); + $this->testLocked(); + $this->lock(AMVC_QRY_SEL_LCK_JOIN); + array_push($this->components[AMVC_QRY_SEL_JOINS], [$type, $table]); return $this; } public function on(string $condition): Selectable { - if(!$this->checkFlag(AMVC_QRY_SEL_NEEDON)) - throw new \Exception("Cannot declare ON clause without prior JOIN clause"); + if(!$this->checkLock(AMVC_QRY_SEL_LCK_JOIN)) + throw new \Exception("ON subclause can only follow a JOIN clause"); - $this->clearFlag(AMVC_QRY_SEL_NEEDON); - array_push($this->query, "ON", $condition); + $join = array_merge( + array_pop($this->components[AMVC_QRY_SEL_JOINS]), + ["ON", $condition] + ); + array_push($this->components[AMVC_QRY_SEL_JOINS], $join); + $this->unlock(AMVC_QRY_SEL_LCK_JOIN); return $this; } public function using($columns): Selectable { - if(!$this->checkFlag(AMVC_QRY_SEL_NEEDON)) - throw new \Exception("Cannot declare USING clause without prior JOIN clause"); + if(!$this->checkLock(AMVC_QRY_SEL_LCK_JOIN)) + throw new \Exception("USING subclause can only follow a JOIN clause"); if(is_array($columns)) $columns = implode(",", $columns); - $this->clearFlag(AMVC_QRY_SEL_NEEDON); - array_push($this->query, "USING", "({$columns})"); + $join = array_merge( + array_pop($this->components[AMVC_QRY_SEL_JOINS]), + ["USING", "($columns)"] + ); + array_push($this->components[AMVC_QRY_SEL_JOINS], $join); + $this->unlock(AMVC_QRY_SEL_LCK_JOIN); return $this; } public function where(string $condition): Selectable { - $this->checkFrom(); - if($this->checkForOrPast(AMVC_QRY_SEL_WHERE)) - throw new \Exception("Duplicate or misplaced WHERE clause in SELECT query"); - - $this->setFlag(AMVC_QRY_SEL_WHERE); - array_push($this->query, "WHERE", $condition); + $this->testLocked(); + array_push($this->components[AMVC_QRY_SEL_WHERE], $condition); return $this; } public function groupBy($columns): Selectable { - $this->checkFrom(); - if($this->checkForOrPast(AMVC_QRY_SEL_GROUP)) - throw new \Exception("Duplicate or misplaced GROUP BY clause in SELECT query"); - + $this->testLocked(); if(is_array($columns)) $columns = implode(",", $columns); - $this->setFlag(AMVC_QRY_SEL_GROUP); - array_push($this->query, "GROUP BY", $columns); + array_push($this->components[AMVC_QRY_SEL_GROUP], $columns); return $this; } public function having(string $condition): Selectable { - $this->checkFrom(); - if($this->checkForOrPast(AMVC_QRY_SEL_HAVING)) - throw new \Exception("Duplicate or misplaced HAVING clause in SELECT query"); - - array_push($this->query, "HAVING", $condition); + $this->testLocked(); + array_push($this->components[AMVC_QRY_SEL_HAVING], $condition); return $this; } public function orderBy($columns): Selectable { - $this->checkFrom(); - if($this->checkForOrPast(AMVC_QRY_SEL_ORDER)) - throw new \Exception("Duplicate or misplaced ORDER BY clause in SELECT query"); + $this->testLocked(); + if(!is_array($columns)) + $columns = [$columns]; - if(is_array($columns)) - $columns = implode(",", $columns); - - $this->setFlag(AMVC_QRY_SEL_ORDER); - array_push($this->query, "ORDER BY", $columns); + array_push($this->components[AMVC_QRY_SEL_ORDER], $columns); return $this; } public function limit(int $count, int $offset = 0): Selectable { - $this->checkFrom(); - if($this->checkForOrPast(AMVC_QRY_SEL_LIMIT)) - throw new \Exception("Duplicate or misplaced LIMIT clause in SELECT query"); - - $this->setFlag(AMVC_QRY_SEL_LIMIT); - array_push($this->query, "LIMIT", "$offset,$count"); + $this->testLocked(); + $this->components[AMVC_QRY_SEL_LIMIT] = [$offset, $count]; return $this; } @@ -152,15 +196,14 @@ class Selectable extends Queryable { return $this->results; } - public function asModels($obj) { + public function asModels($model = null) { if($this->results == null) throw new \Exception("Cannot return results from a query that has not executed."); - $type = new \ReflectionClass($obj); - if(!$type->isSubclassOf("\\AroMVC\\Core\\AroModel")) - throw new \Exception("Cannot instantiate non-model object."); + if($this->model == null || $model != null) + $this->model = $this->getModelReflection($model); foreach($this->results as $result) - yield $type->getMethod("withRow")->invoke(null, $result); + yield $this->model->getMethod("withRow")->invoke(null, $result); } } \ No newline at end of file diff --git a/Models/State.php b/Models/State.php index ebebfe3..5df8be7 100644 --- a/Models/State.php +++ b/Models/State.php @@ -3,6 +3,8 @@ namespace AroMVC\Models; use \AroMVC\Core\Model; class State extends Model { + protected static $table = "State"; + protected $id; protected $name; diff --git a/conf.ini b/conf.ini index 57d8b50..22c2b7b 100644 --- a/conf.ini +++ b/conf.ini @@ -1,4 +1,4 @@ [Database] -dsn = "mysql:host=localhost;dbname=fire" -username = "squidlord" +dsn = "mysql:host=localhost;dbname=test" +username = "root" password = "" \ No newline at end of file diff --git a/index.php b/index.php index 376e8d7..cebad37 100644 --- a/index.php +++ b/index.php @@ -1,5 +1,6 @@ from("Companies") - ->where("`name` = ?") - ->or("`id` = ?") + ->where("`name` = :name OR `id` = :cid") + ->params(["name" => "winco", "cid" => 20]) + ->join("LEFT JOIN", "Invoices") + ->using("id") ->execute() - ->asModels(new Company);*/ - - + ->asModels(new Company); \ No newline at end of file