php-vn-lib/db/connection.php

511 lines
11 KiB
PHP

<?php
namespace Vn\Db;
use Vn\Lib\Type;
class Connection {
private $conn = NULL;
private $isOpen = FALSE;
var $enableDebug = FALSE;
function __construct() {
$this->initHandler();
}
/**
* Opens a new connection to the database.
*
* @param string $host The host name
* @param string $user The user name to authenticate
* @param string $pass The user password
* @param string $name The default schema name
*
* @return boolean %TRUE on success, %FALSE otherwise
*/
function open($host, $user, $pass, $name, $port = NULL) {
$conn = $this->initHandler();
$conn->options(MYSQLI_OPT_LOCAL_INFILE, TRUE);
$conn->real_connect($host, $user, $pass, $name, $port);
if (mysqli_connect_errno()) {
sleep(3);
throw new Exception(mysqli_connect_errno(), mysqli_connect_error());
return FALSE;
}
$this->isOpen = TRUE;
$this->query('SET CHARACTER SET utf8');
return TRUE;
}
/**
* Closes the current connection, if it's closed does nothing.
*/
function close() {
if ($this->isOpen) {
$this->conn->close();
$this->conn = NULL;
}
$this->isOpen = FALSE;
}
/**
* Initializes the internal database connection handler.
*
* @return Objetct The connection handler
*/
function initHandler() {
if (!$this->conn)
$this->conn = new \mysqli();
return $this->conn;
}
/**
* Returns the internal database connection handler. This function shoud be
* used to set options for specific databases that could not be set using
* the Connection class methods.
*
* @return Objetct The connection handler
*/
function getHandler() {
return $this->conn;
}
/**
* Changes the default schema for the current connection.
*
* @param string $schema The schema name
* @return boolean %TRUE if success, %FALSE otherwise
*/
function selectDb($dbName) {
return $this->conn->select_db($dbName);
}
/**
* Executes the query and gets the result.
*
* @param string $query The SQL query
* @param mixed[] $params The query parameters
*
* @return mixed The value or %NULL if error
*/
function query($query, $params = NULL) {
$result = $this->conn->query($this->renderDebug($query, $params));
if (!$result)
$this->checkError();
else
while ($this->moreResults())
$this->nextResult();
return $result;
}
/**
* Checks whether the connection is open.
*
* @return boolean %TRUE if connection is open, %FALSE otherwise
*/
function isOpen() {
return $this->isOpen;
}
/**
* Gets the first row value of the first column from a result.
*
* @param resource $result The database result
*
* @return mixed[] An associative array with the first row, %NULL if error
*/
function getRowFromResult($result) {
if ($result) {
$row = $result->fetch_assoc();
$result->free();
return $row;
}
return NULL;
}
/**
* Gets the first row value of the first column from a result.
*
* @param resource $result The database result
*
* @return object An object with the first row, %NULL if error
*/
function getObjectFromResult($result) {
if ($result) {
$row = $result->fetch_object();
$result->free();
return $row;
}
return NULL;
}
/**
* Gets the first row value of the first column from a result.
*
* @param resource $result The database result
*
* @return mixed The value or %NULL if error
*/
function getValueFromResult($result) {
$value = NULL;
if ($result) {
$row = $result->fetch_row();
if ($row && count($row) > 0)
$value = $row[0];
$result->free();
}
return $value;
}
/**
* Executes the query and gets it's first row.
*
* @param string $query The SQL query
* @param mixed[] $params The query parameters
*
* @return mixed[] An associative array with the first row, %NULL if error
*/
function getRow($query, $params = NULL) {
$result = $this->query($query, $params);
return $this->getRowFromResult($result);
}
/**
* Executes the query and gets it's first row.
*
* @param string $query The SQL query
* @param mixed[] $params The query parameters
*
* @return object An object with the first row, %NULL if error
*/
function getObject($query, $params = NULL) {
$result = $this->query($query, $params);
return $this->getObjectFromResult($result);
}
/**
* Executes the query and gets the first value of the first row.
*
* @param string $query The SQL query
* @param mixed[] $params The query parameters
*
* @return mixed The value or %NULL if error
*/
function getValue($query, $params = NULL) {
$result = $this->query($query, $params);
return $this->getValueFromResult($result);
}
/**
* Loads a query from a file and renders it.
*
* @param string $file The file path
* @param mixed[] $params The query parameters
*
* @return mixed The query string
*/
function loadFromFile($file, $params = NULL) {
$query = file_get_contents($file .'.sql');
if ($query === FALSE)
throw new Exception(NULL, 'Can not load query from file');
return $this->render($query, $params);
}
/**
* Executes the query stored in the specified file and gets the result.
*
* @param string $file The file path
* @param mixed[] $params The query parameters
*
* @return mixed The value or %NULL if error
*/
function queryFromFile($file, $params = NULL) {
$query = $this->loadFromFile($file, $params);
if ($query)
return $this->query($query);
return NULL;
}
/**
* Executes the file query and gets it's first row.
*
* @param string $file The file path
* @param mixed[] $params The query parameters
*
* @return mixed[] An associative array with the first row, %NULL if error
*/
function getRowFromFile($file, $params = NULL) {
$result = $this->queryFromFile($file, $params);
return $this->getRowFromResult($result);
}
/**
* Executes the file query and gets the first value of the first row.
*
* @param string $file The file path
* @param mixed[] $params The query parameters
*
* @return mixed The value or %NULL if error
*/
function getValueFromFile($file, $params = NULL) {
$result = $this->queryFromFile($file, $params);
return $this->getValueFromResult($result);
}
/**
* Execute multiple queries separated by semicolons.
*
* @param string $query The SQL query
* @param mixed[] $params The query parameters
*
* @return mixed The value or %NULL if error
*/
function multiQuery($query, $params = NULL) {
$success = $this->conn->multi_query($this->renderDebug($query, $params));
if (!$success)
$this->checkError();
return $success;
}
/**
* Prepare an SQL statement for execution.
*
* @param string $query The SQL query
*
* @return mixed The statement object or %FALSE if an error occurred
*/
function prepare($query) {
return $this->conn->prepare($query);
}
function storeResult() {
$result = $this->conn->store_result();
if (!$result)
$this->checkError();
return $result;
}
function moreResults() {
return $this->conn->more_results();
}
function nextResult() {
$hasNext = $this->conn->next_result();
$this->checkError();
return $hasNext;
}
/**
* Check if there has been an error in the last query or multiquery,
* throwing a @Exception exception if any.
*/
function checkError() {
if ($this->conn->errno) {
if ($this->conn->errno != 1644)
throw new Exception($this->conn->errno, $this->conn->error);
else
throw new \Vn\Lib\UserException($this->conn->error, $this->conn->errno);
}
}
/**
* Check if there have been warnings in the last query.
*
* @return boolean %TRUE if there have been warnings %FALSE otherwise
*/
function checkWarnings() {
return $this->conn->warning_count > 0;
}
/**
* Renders an SQL string using the given parameters.
*
* @param string $query The SQL string
* @param mixed[] $paramsMap The query parameters
*
* @return string The rendered SQL string
*/
function render($query, $paramsMap = NULL) {
if (isset($paramsMap) && is_array($paramsMap) && count($paramsMap) > 0) {
$i = 0;
$params = [];
foreach ($paramsMap as $key => $value)
$params[$key] = $this->renderValue($value);
$replaceFunc = function($matches) use(&$params, &$i) {
$key = substr($matches[0], 1);
if (strlen($key) == 0)
$key = $i++;
if (isset($params[$key]))
return $params[$key];
return '#'. $key;
};
return preg_replace_callback('/#\w*/', $replaceFunc, $query);
}
else
return $query;
}
/**
* Gets an SQL respresentation from a PHP value.
*
* @param mixed $value The value
*
* @return string The SQL value
*/
function renderValue($value) {
if ($value !== NULL)
switch (Type::get($value)) {
case Type::BOOLEAN:
return($value) ? 'TRUE' : 'FALSE';
case Type::STRING:
return '\'' . $this->escapeString($value) . '\'';
case Type::DATE:
return strftime('\'%Y-%m-%d\'', $value->getTimestamp());
case Type::TIME:
return strftime('\'%T\'', $value->getTimestamp());
case Type::DATE_TIME:
return strftime('\'%Y-%m-%d %T\'', $value->getTimestamp());
default:
return '\'' . $this->escapeString($value) . '\'';
}
else
return 'NULL';
}
/**
* Escapes an string, escaping special characters when necessary.
*
* @param string $string The string
* @return string The escaped string
*/
function escapeString($string) {
return $this->conn->real_escape_string($string);
}
/**
* Quotes an identifier, escaping special characters when necessary.
*
* @param string $identifier The identifier without quotes
* @return string The quoted identifier
*/
function quote($identifier) {
return "`". str_replace("`", "``", $identifier) ."`";
}
/**
* Renders an SQL string using the given parameters, also debugs the
* rendered string if $enableDebug property is set to %true.
*
* @param string $query The SQL string
* @param mixed[] $paramsMap The query parameters
*
* @return string The rendered SQL string
*/
function renderDebug($query, $params = NULL) {
$renderedQuery = $this->render($query, $params);
if ($this->enableDebug)
error_log($renderedQuery);
return $renderedQuery;
}
function commit() {
return $this->query('COMMIT');
}
function rollback() {
return $this->query('ROLLBACK');
}
function startTransaction() {
return $this->query('START TRANSACTION');
}
function lastInsertId() {
return $this->getValue('SELECT LAST_INSERT_ID()');
}
function call($procName, $values) {
return $this->query(
"CALL {$this->quote($procName)}({$this->renderList($values)})"
);
}
function insert($table, $values) {
return $this->query("INSERT INTO {$this->quote($table)}
SET {$this->renderFields($values)}");
}
function update($table, $values, $where) {
return $this->query("UPDATE {$this->quote($table)}
SET {$this->renderFields($values)}
WHERE {$this->renderWhere($where)}");
}
function renderFields($values) {
return $this->renderAssoc($values, ', ');
}
function renderWhere($operands) {
return $this->renderAssoc($operands, ' AND ');
}
function renderAssoc($list, $separator) {
$fields = '';
$isFirst = true;
foreach ($list as $field => $value) {
if ($isFirst)
$isFirst = false;
else
$fields .= $separator;
$fields .= $this->quote($field) .' = '. $this->renderValue($value);
}
return $fields;
}
function renderList($list, $separator = ', ') {
$values = '';
$isFirst = true;
foreach ($list as $value) {
if ($isFirst)
$isFirst = false;
else
$values .= $separator;
$values .= $this->renderValue($value);
}
return $values;
}
}