MAPG-43 initial classes for DB handling
This commit is contained in:
parent
b821d4a045
commit
17c0ec73df
114
src/Database/Mysql/Connection.php
Normal file
114
src/Database/Mysql/Connection.php
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<?php namespace MapGuesser\Database\Mysql;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Database\IConnection;
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use MapGuesser\Interfaces\Database\IStatement;
|
||||||
|
use mysqli;
|
||||||
|
|
||||||
|
class Connection implements IConnection
|
||||||
|
{
|
||||||
|
private mysqli $connection;
|
||||||
|
|
||||||
|
public function __construct(string $host, string $user, string $password, string $db, int $port = -1, string $socket = null)
|
||||||
|
{
|
||||||
|
if ($port < 0) {
|
||||||
|
$port = ini_get('mysqli.default_port');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($socket === null) {
|
||||||
|
$socket = ini_get('mysqli.default_socket');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->connection = new mysqli($host, $user, $password, $db, $port, $socket);
|
||||||
|
|
||||||
|
if ($this->connection->connect_error) {
|
||||||
|
throw new \Exception('Connection failed: ' . $this->connection->connect_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->connection->set_charset('utf8mb4')) {
|
||||||
|
throw new \Exception($this->connection->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->connection->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function startTransaction(): void
|
||||||
|
{
|
||||||
|
if (!$this->connection->autocommit(false)) {
|
||||||
|
throw new \Exception($this->connection->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function commit(): void
|
||||||
|
{
|
||||||
|
if (!$this->connection->commit() || !$this->connection->autocommit(true)) {
|
||||||
|
throw new \Exception($this->connection->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rollback(): void
|
||||||
|
{
|
||||||
|
if (!$this->connection->rollback() || !$this->connection->autocommit(true)) {
|
||||||
|
throw new \Exception($this->connection->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function query(string $query): ?IResultSet
|
||||||
|
{
|
||||||
|
if (!($result = $this->connection->query($query))) {
|
||||||
|
throw new \Exception($this->connection->error . '. Query: ' . $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result !== true) {
|
||||||
|
return new ResultSet($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function multiQuery(string $query): array
|
||||||
|
{
|
||||||
|
if (!$this->connection->multi_query($query)) {
|
||||||
|
throw new \Exception($this->connection->error . '. Query: ' . $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ret = [];
|
||||||
|
do {
|
||||||
|
if ($result = $this->connection->store_result()) {
|
||||||
|
$ret[] = new ResultSet($result);
|
||||||
|
} else {
|
||||||
|
$ret[] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->connection->more_results();
|
||||||
|
} while ($this->connection->next_result());
|
||||||
|
|
||||||
|
if ($this->connection->error) {
|
||||||
|
throw new \Exception($this->connection->error . '. Query: ' . $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function prepare(string $query): IStatement
|
||||||
|
{
|
||||||
|
if (!($stmt = $this->connection->prepare($query))) {
|
||||||
|
throw new \Exception($this->connection->error . '. Query: ' . $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Statement($stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function lastId(): int
|
||||||
|
{
|
||||||
|
return $this->connection->insert_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAffectedRows(): int
|
||||||
|
{
|
||||||
|
return $this->connection->affected_rows;
|
||||||
|
}
|
||||||
|
}
|
62
src/Database/Mysql/ResultSet.php
Normal file
62
src/Database/Mysql/ResultSet.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?php namespace MapGuesser\Database\Mysql;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use mysqli_result;
|
||||||
|
|
||||||
|
class ResultSet implements IResultSet
|
||||||
|
{
|
||||||
|
private mysqli_result $result;
|
||||||
|
|
||||||
|
public function __construct(mysqli_result $result)
|
||||||
|
{
|
||||||
|
$this->result = $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fetch(int $type = IResultSet::FETCH_ASSOC)
|
||||||
|
{
|
||||||
|
return $this->result->fetch_array($this->convertFetchType($type));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fetchAll(int $type = IResultSet::FETCH_ASSOC)
|
||||||
|
{
|
||||||
|
return $this->result->fetch_all($this->convertFetchType($type));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fetchOneColumn(string $valueName, string $keyName = null)
|
||||||
|
{
|
||||||
|
$array = [];
|
||||||
|
|
||||||
|
while ($r = $this->fetch(IResultSet::FETCH_ASSOC)) {
|
||||||
|
if (isset($keyName)) {
|
||||||
|
$array[$r[$keyName]] = $r[$valueName];
|
||||||
|
} else {
|
||||||
|
$array[] = $r[$valueName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function convertFetchType(int $type): int
|
||||||
|
{
|
||||||
|
switch ($type) {
|
||||||
|
case IResultSet::FETCH_ASSOC:
|
||||||
|
$internal_type = MYSQLI_ASSOC;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IResultSet::FETCH_BOTH:
|
||||||
|
$internal_type = MYSQLI_BOTH;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IResultSet::FETCH_NUM:
|
||||||
|
$internal_type = MYSQLI_NUM;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$internal_type = MYSQLI_BOTH;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $internal_type;
|
||||||
|
}
|
||||||
|
}
|
79
src/Database/Mysql/Statement.php
Normal file
79
src/Database/Mysql/Statement.php
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<?php namespace MapGuesser\Database\Mysql;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use MapGuesser\Interfaces\Database\IStatement;
|
||||||
|
use mysqli_stmt;
|
||||||
|
|
||||||
|
class Statement implements IStatement
|
||||||
|
{
|
||||||
|
private mysqli_stmt $stmt;
|
||||||
|
|
||||||
|
public function __construct(mysqli_stmt $stmt)
|
||||||
|
{
|
||||||
|
$this->stmt = $stmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->stmt->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(array $params = []): ?IResultSet
|
||||||
|
{
|
||||||
|
if ($params) {
|
||||||
|
$ref_params = [''];
|
||||||
|
|
||||||
|
foreach ($params as &$param) {
|
||||||
|
$type = gettype($param);
|
||||||
|
|
||||||
|
switch ($type) {
|
||||||
|
case 'integer':
|
||||||
|
case 'double':
|
||||||
|
case 'string':
|
||||||
|
$t = $type[0];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'NULL':
|
||||||
|
$t = 's';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'boolean':
|
||||||
|
$param = (string) (int) $param;
|
||||||
|
$t = 's';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'array':
|
||||||
|
$param = json_encode($param);
|
||||||
|
$t = 's';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($t)) {
|
||||||
|
throw new \Exception('Data type ' . $type . ' not supported!');
|
||||||
|
}
|
||||||
|
|
||||||
|
$ref_params[] = &$param;
|
||||||
|
$ref_params[0] .= $t;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!call_user_func_array([$this->stmt, 'bind_param'], $ref_params)) {
|
||||||
|
throw new \Exception($this->stmt->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->stmt->execute()) {
|
||||||
|
throw new \Exception($this->stmt->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result_set = $this->stmt->get_result()) {
|
||||||
|
return new ResultSet($result_set);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAffectedRows(): int
|
||||||
|
{
|
||||||
|
return $this->stmt->affected_rows;
|
||||||
|
}
|
||||||
|
}
|
157
src/Database/Query/Modify.php
Executable file
157
src/Database/Query/Modify.php
Executable file
@ -0,0 +1,157 @@
|
|||||||
|
<?php namespace MapGuesser\Database\Query;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Database\IConnection;
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use MapGuesser\Database\Utils;
|
||||||
|
|
||||||
|
class Modify
|
||||||
|
{
|
||||||
|
private IConnection $connection;
|
||||||
|
|
||||||
|
private string $table;
|
||||||
|
|
||||||
|
private string $idName = 'id';
|
||||||
|
|
||||||
|
private array $attributes = [];
|
||||||
|
|
||||||
|
private array $original = [];
|
||||||
|
|
||||||
|
private bool $autoIncrement = false;
|
||||||
|
|
||||||
|
public function __construct(IConnection $connection, string $table)
|
||||||
|
{
|
||||||
|
$this->connection = $connection;
|
||||||
|
$this->table = $table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setIdName(string $idName): Modify
|
||||||
|
{
|
||||||
|
$this->idName = $idName;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAutoIncrement(bool $autoIncrement = true): Modify
|
||||||
|
{
|
||||||
|
$this->autoIncrement = $autoIncrement;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fill(array $attributes): Modify
|
||||||
|
{
|
||||||
|
$this->attributes = array_merge($this->attributes, $attributes);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set(string $name, $value): Modify
|
||||||
|
{
|
||||||
|
$this->attributes[$name] = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setId($id): Modify
|
||||||
|
{
|
||||||
|
$this->attributes[$this->idName] = $id;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(): void
|
||||||
|
{
|
||||||
|
if (isset($this->attributes[$this->idName])) {
|
||||||
|
$this->update();
|
||||||
|
} else {
|
||||||
|
$this->insert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(): void
|
||||||
|
{
|
||||||
|
if (!isset($this->attributes[$this->idName])) {
|
||||||
|
throw new \Exception('No primary key specified!');
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = 'DELETE FROM ' . Utils::backtick($this->table) . ' WHERE ' . Utils::backtick($this->idName) . '=?';
|
||||||
|
|
||||||
|
$stmt = $this->connection->prepare($query);
|
||||||
|
$stmt->execute([$this->idName => $this->attributes[$this->idName]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function insert(): void
|
||||||
|
{
|
||||||
|
if (!$this->autoIncrement) {
|
||||||
|
$this->attributes[$this->idName] = $this->generateKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
$set = $this->generateColumnsWithBinding(array_keys($this->attributes));
|
||||||
|
|
||||||
|
$query = 'INSERT INTO ' . Utils::backtick($this->table) . ' SET ' . $set;
|
||||||
|
|
||||||
|
$stmt = $this->connection->prepare($query);
|
||||||
|
$stmt->execute($this->attributes);
|
||||||
|
|
||||||
|
if ($this->autoIncrement) {
|
||||||
|
$this->attributes[$this->idName] = $this->connection->lastId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function update(): void
|
||||||
|
{
|
||||||
|
$diff = $this->generateDiff();
|
||||||
|
|
||||||
|
if (count($diff) === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$set = $this->generateColumnsWithBinding(array_keys($diff));
|
||||||
|
|
||||||
|
$query = 'UPDATE ' . Utils::backtick($this->table) . ' SET ' . $set . ' WHERE ' . Utils::backtick($this->idName) . '=?';
|
||||||
|
|
||||||
|
$stmt = $this->connection->prepare($query);
|
||||||
|
$stmt->execute(array_merge($diff, [$this->idName => $this->attributes[$this->idName]]));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function readFromDB(array $columns): void
|
||||||
|
{
|
||||||
|
$select = (new Select($this->connection, $this->table))
|
||||||
|
->setIdName($this->idName)
|
||||||
|
->whereId($this->attributes[$this->idName])
|
||||||
|
->columns($columns);
|
||||||
|
|
||||||
|
$this->original = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateDiff(): array
|
||||||
|
{
|
||||||
|
$this->readFromDB(array_keys($this->attributes));
|
||||||
|
|
||||||
|
$diff = [];
|
||||||
|
|
||||||
|
foreach ($this->attributes as $name => $value) {
|
||||||
|
$original = $this->original[$name];
|
||||||
|
|
||||||
|
if ($original != $value) {
|
||||||
|
$diff[$name] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function generateColumnsWithBinding(array $columns): string
|
||||||
|
{
|
||||||
|
array_walk($columns, function(&$value, $key) {
|
||||||
|
$value = Utils::backtick($value) . '=?';
|
||||||
|
});
|
||||||
|
|
||||||
|
return implode(',', $columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateKey(): string
|
||||||
|
{
|
||||||
|
return substr(hash('sha256', serialize($this->attributes) . random_bytes(10) . microtime()), 0, 7);
|
||||||
|
}
|
||||||
|
}
|
402
src/Database/Query/Select.php
Normal file
402
src/Database/Query/Select.php
Normal file
@ -0,0 +1,402 @@
|
|||||||
|
<?php namespace MapGuesser\Database\Query;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use MapGuesser\Interfaces\Database\IConnection;
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use MapGuesser\Database\RawExpression;
|
||||||
|
use MapGuesser\Database\Utils;
|
||||||
|
|
||||||
|
class Select
|
||||||
|
{
|
||||||
|
const CONDITION_WHERE = 0;
|
||||||
|
|
||||||
|
const CONDITION_HAVING = 1;
|
||||||
|
|
||||||
|
private IConnection $connection;
|
||||||
|
|
||||||
|
private string $table;
|
||||||
|
|
||||||
|
private string $idName = 'id';
|
||||||
|
|
||||||
|
private array $tableAliases = [];
|
||||||
|
|
||||||
|
private array $joins = [];
|
||||||
|
|
||||||
|
private array $columns = [];
|
||||||
|
|
||||||
|
private array $conditions = [self::CONDITION_WHERE => [], self::CONDITION_HAVING => []];
|
||||||
|
|
||||||
|
private array $groups = [];
|
||||||
|
|
||||||
|
private array $orders = [];
|
||||||
|
|
||||||
|
private array $limit;
|
||||||
|
|
||||||
|
public function __construct(IConnection $connection, string $table)
|
||||||
|
{
|
||||||
|
$this->connection = $connection;
|
||||||
|
$this->table = $table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setIdName(string $idName): Select
|
||||||
|
{
|
||||||
|
$this->idName = $idName;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTableAliases(array $tableAliases): Select
|
||||||
|
{
|
||||||
|
$this->tableAliases = array_merge($this->tableAliases, $tableAliases);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function columns(array $columns): Select
|
||||||
|
{
|
||||||
|
$this->columns = array_merge($this->columns, $columns);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function innerJoin($table, $column1, string $relation, $column2): Select
|
||||||
|
{
|
||||||
|
$this->addJoin('INNER', $table, $column1, $relation, $column2);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function leftJoin($table, $column1, string $relation, $column2): Select
|
||||||
|
{
|
||||||
|
$this->addJoin('LEFT', $table, $column1, $relation, $column2);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function whereId($value): Select
|
||||||
|
{
|
||||||
|
$this->addWhereCondition('AND', $this->idName, '=', $value);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function where($column, string $relation = null, $value = null): Select
|
||||||
|
{
|
||||||
|
$this->addWhereCondition('AND', $column, $relation, $value);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function orWhere($column, string $relation = null, $value = null): Select
|
||||||
|
{
|
||||||
|
$this->addWhereCondition('OR', $column, $relation, $value);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function having($column, string $relation = null, $value = null): Select
|
||||||
|
{
|
||||||
|
$this->addHavingCondition('AND', $column, $relation, $value);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function orHaving($column, string $relation = null, $value = null): Select
|
||||||
|
{
|
||||||
|
$this->addHavingCondition('OR', $column, $relation, $value);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function groupBy($column): Select
|
||||||
|
{
|
||||||
|
$this->groups[] = $column;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function orderBy($column, string $type = 'asc'): Select
|
||||||
|
{
|
||||||
|
$this->orders[] = [$column, $type];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function limit(int $limit, int $offset = 0): Select
|
||||||
|
{
|
||||||
|
$this->limit = [$limit, $offset];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetLimit(): void
|
||||||
|
{
|
||||||
|
$this->limit = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paginate(int $page, int $itemsPerPage)
|
||||||
|
{
|
||||||
|
$this->limit($itemsPerPage, ($page - 1) * $itemsPerPage);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(): IResultSet
|
||||||
|
{
|
||||||
|
list($query, $params) = $this->generateQuery();
|
||||||
|
|
||||||
|
return $this->connection->prepare($query)->execute($params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
if (count($this->groups) > 0 || count($this->conditions[self::CONDITION_HAVING]) > 0) {
|
||||||
|
$orders = $this->orders;
|
||||||
|
|
||||||
|
$this->orders = [];
|
||||||
|
|
||||||
|
list($query, $params) = $this->generateQuery();
|
||||||
|
|
||||||
|
$result = $this->connection->prepare('SELECT COUNT(*) num_rows FROM (' . $query . ') x')
|
||||||
|
->execute($params)
|
||||||
|
->fetch(IResultSet::FETCH_NUM);
|
||||||
|
|
||||||
|
$this->orders = $orders;
|
||||||
|
|
||||||
|
return $result[0];
|
||||||
|
} else {
|
||||||
|
$columns = $this->columns;
|
||||||
|
$orders = $this->orders;
|
||||||
|
|
||||||
|
$this->columns = [new RawExpression('COUNT(*) num_rows')];
|
||||||
|
$this->orders = [];
|
||||||
|
|
||||||
|
list($query, $params) = $this->generateQuery();
|
||||||
|
|
||||||
|
$result = $this->connection->prepare($query)
|
||||||
|
->execute($params)
|
||||||
|
->fetch(IResultSet::FETCH_NUM);
|
||||||
|
|
||||||
|
$this->columns = $columns;
|
||||||
|
$this->orders = $orders;
|
||||||
|
|
||||||
|
return $result[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addJoin(string $type, $table, $column1, string $relation, $column2): void
|
||||||
|
{
|
||||||
|
$this->joins[] = [$type, $table, $column1, $relation, $column2];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addWhereCondition(string $logic, $column, string $relation, $value): void
|
||||||
|
{
|
||||||
|
$this->conditions[self::CONDITION_WHERE][] = [$logic, $column, $relation, $value];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addHavingCondition(string $logic, $column, string $relation, $value): void
|
||||||
|
{
|
||||||
|
$this->conditions[self::CONDITION_HAVING][] = [$logic, $column, $relation, $value];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateQuery(): array
|
||||||
|
{
|
||||||
|
$queryString = 'SELECT ' . $this->generateColumns() . ' FROM ' . $this->generateTable($this->table, true);
|
||||||
|
|
||||||
|
if (count($this->joins) > 0) {
|
||||||
|
$queryString .= ' ' . $this->generateJoins();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($this->conditions[self::CONDITION_WHERE]) > 0) {
|
||||||
|
list($wheres, $whereParams) = $this->generateConditions(self::CONDITION_WHERE);
|
||||||
|
|
||||||
|
$queryString .= ' WHERE ' . $wheres;
|
||||||
|
} else {
|
||||||
|
$whereParams = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($this->groups) > 0) {
|
||||||
|
$queryString .= ' GROUP BY ' . $this->generateGroupBy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($this->conditions[self::CONDITION_HAVING]) > 0) {
|
||||||
|
list($havings, $havingParams) = $this->generateConditions(self::CONDITION_HAVING);
|
||||||
|
|
||||||
|
$queryString .= ' HAVING ' . $havings;
|
||||||
|
} else {
|
||||||
|
$havingParams = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($this->orders) > 0) {
|
||||||
|
$queryString .= ' ORDER BY ' . $this->generateOrderBy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->limit)) {
|
||||||
|
$queryString .= ' LIMIT ' . $this->limit[1] . ', ' . $this->limit[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$queryString, array_merge($whereParams, $havingParams)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateTable($table, bool $defineAlias = false): string
|
||||||
|
{
|
||||||
|
if ($table instanceof RawExpression) {
|
||||||
|
return (string) $table;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->tableAliases[$table])) {
|
||||||
|
return ($defineAlias ? Utils::backtick($this->tableAliases[$table]) . ' ' . Utils::backtick($table) : Utils::backtick($table));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Utils::backtick($table);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateColumn($column): string
|
||||||
|
{
|
||||||
|
if ($column instanceof RawExpression) {
|
||||||
|
return (string) $column;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($column)) {
|
||||||
|
$out = '';
|
||||||
|
|
||||||
|
if ($column[0]) {
|
||||||
|
$out .= $this->generateTable($column[0]) . '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
$out .= Utils::backtick($column[1]);
|
||||||
|
|
||||||
|
if (!empty($column[2])) {
|
||||||
|
$out .= ' ' . Utils::backtick($column[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
} else {
|
||||||
|
return Utils::backtick($column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateColumns(): string
|
||||||
|
{
|
||||||
|
$columns = $this->columns;
|
||||||
|
|
||||||
|
array_walk($columns, function (&$value, $key) {
|
||||||
|
$value = $this->generateColumn($value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return implode(',', $columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateJoins(): string
|
||||||
|
{
|
||||||
|
$joins = $this->joins;
|
||||||
|
|
||||||
|
array_walk($joins, function (&$value, $key) {
|
||||||
|
$value = $value[0] . ' JOIN ' . $this->generateTable($value[1], true) . ' ON ' . $this->generateColumn($value[2]) . ' ' . $value[3] . ' ' . $this->generateColumn($value[4]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return implode(' ', $joins);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateConditions(string $type): array
|
||||||
|
{
|
||||||
|
$conditions = '';
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
foreach ($this->conditions[$type] as $condition) {
|
||||||
|
list($logic, $column, $relation, $value) = $condition;
|
||||||
|
|
||||||
|
if ($column instanceof Closure) {
|
||||||
|
list($conditionsStringFragment, $paramsFragment) = $this->generateComplexConditionFragment($type, $column);
|
||||||
|
} else {
|
||||||
|
list($conditionsStringFragment, $paramsFragment) = $this->generateConditionFragment($condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($conditions !== '') {
|
||||||
|
$conditions .= ' ' . $logic . ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
$conditions .= $conditionsStringFragment;
|
||||||
|
$params = array_merge($params, $paramsFragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$conditions, $params];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateConditionFragment(array $condition): array
|
||||||
|
{
|
||||||
|
list($logic, $column, $relation, $value) = $condition;
|
||||||
|
|
||||||
|
if ($column instanceof RawExpression) {
|
||||||
|
return [(string) $column, []];
|
||||||
|
}
|
||||||
|
|
||||||
|
$conditionsString = $this->generateColumn($column) . ' ';
|
||||||
|
|
||||||
|
if ($value === null) {
|
||||||
|
return [$conditionsString . ($relation == '=' ? 'IS NULL' : 'IS NOT NULL'), []];
|
||||||
|
}
|
||||||
|
|
||||||
|
$conditionsString .= strtoupper($relation) . ' ';;
|
||||||
|
|
||||||
|
switch ($relation = strtolower($relation)) {
|
||||||
|
case 'between':
|
||||||
|
$params = [$value[0], $value[1]];
|
||||||
|
|
||||||
|
$conditionsString .= '? AND ?';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'in':
|
||||||
|
case 'not in':
|
||||||
|
$params = $value;
|
||||||
|
|
||||||
|
if (count($value) > 0) {
|
||||||
|
$conditionsString .= '(' . implode(', ', array_fill(0, count($value), '?')) . ')';
|
||||||
|
} else {
|
||||||
|
$conditionsString = $relation == 'in' ? '0' : '1';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$params = [$value];
|
||||||
|
|
||||||
|
$conditionsString .= '?';
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$conditionsString, $params];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateComplexConditionFragment(string $type, Closure $conditionCallback): array
|
||||||
|
{
|
||||||
|
$instance = new static($this->connection, $this->table);
|
||||||
|
$instance->tableAliases = $this->tableAliases;
|
||||||
|
|
||||||
|
$conditionCallback($instance);
|
||||||
|
|
||||||
|
list($conditions, $params) = $instance->generateConditions($type);
|
||||||
|
|
||||||
|
return ['(' . $conditions . ')', $params];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateGroupBy(): string
|
||||||
|
{
|
||||||
|
$groups = $this->groups;
|
||||||
|
|
||||||
|
array_walk($groups, function (&$value, $key) {
|
||||||
|
$value = $this->generateColumn($value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return implode(',', $groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateOrderBy(): string
|
||||||
|
{
|
||||||
|
$orders = $this->orders;
|
||||||
|
|
||||||
|
array_walk($orders, function (&$value, $key) {
|
||||||
|
$value = $this->generateColumn($value[0]) . ' ' . $value[1];
|
||||||
|
});
|
||||||
|
|
||||||
|
return implode(',', $orders);
|
||||||
|
}
|
||||||
|
}
|
16
src/Database/RawExpression.php
Normal file
16
src/Database/RawExpression.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php namespace MapGuesser\Database;
|
||||||
|
|
||||||
|
class RawExpression
|
||||||
|
{
|
||||||
|
private string $expression;
|
||||||
|
|
||||||
|
public function __construct(string $expression)
|
||||||
|
{
|
||||||
|
$this->expression = $expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return $this->expression;
|
||||||
|
}
|
||||||
|
}
|
7
src/Database/Utils.php
Normal file
7
src/Database/Utils.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php namespace MapGuesser\Database;
|
||||||
|
|
||||||
|
class Utils {
|
||||||
|
public static function backtick(string $name) {
|
||||||
|
return '`' . $name . '`';
|
||||||
|
}
|
||||||
|
}
|
20
src/Interfaces/Database/IConnection.php
Normal file
20
src/Interfaces/Database/IConnection.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Database;
|
||||||
|
|
||||||
|
interface IConnection
|
||||||
|
{
|
||||||
|
public function startTransaction(): void;
|
||||||
|
|
||||||
|
public function commit(): void;
|
||||||
|
|
||||||
|
public function rollback(): void;
|
||||||
|
|
||||||
|
public function query(string $query): ?IResultSet;
|
||||||
|
|
||||||
|
public function multiQuery(string $query): array;
|
||||||
|
|
||||||
|
public function prepare(string $query): IStatement;
|
||||||
|
|
||||||
|
public function lastId(): int;
|
||||||
|
|
||||||
|
public function getAffectedRows(): int;
|
||||||
|
}
|
16
src/Interfaces/Database/IResultSet.php
Normal file
16
src/Interfaces/Database/IResultSet.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Database;
|
||||||
|
|
||||||
|
interface IResultSet
|
||||||
|
{
|
||||||
|
const FETCH_ASSOC = 0;
|
||||||
|
|
||||||
|
const FETCH_NUM = 1;
|
||||||
|
|
||||||
|
const FETCH_BOTH = 2;
|
||||||
|
|
||||||
|
public function fetch(int $type);
|
||||||
|
|
||||||
|
public function fetchAll(int $type);
|
||||||
|
|
||||||
|
public function fetchOneColumn(string $valueName, string $keyName);
|
||||||
|
}
|
8
src/Interfaces/Database/IStatement.php
Normal file
8
src/Interfaces/Database/IStatement.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Database;
|
||||||
|
|
||||||
|
interface IStatement
|
||||||
|
{
|
||||||
|
public function execute(array $params): ?IResultSet;
|
||||||
|
|
||||||
|
public function getAffectedRows(): int;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user