<?php
/**
* ÐлаÑÑ DBA.
*
* @package energine
* @subpackage core
* @author 1m.dm
* @copyright ColoCall 2006
* @version $Id: DBA.class.php,v 1.8 2008/04/07 14:34:24 pavka Exp $
*/
//require_once('core/framework/SystemConfig.class.php');
/**
* Database Abstraction Layer.
*
* @package energine
* @subpackage core
* @abstract
*/
abstract class DBA extends Object {
/**
* @access protected
* @var PDO ÑкземплÑÑ ÐºÐ»Ð°ÑÑа PDO (PHP Data Objects)
*/
protected $pdo;
/**
* @access protected
* @var string поÑледний запÑÐ¾Ñ Ðº ÐÐ
*/
protected $lastQuery;
/**
* @access protected
* @var mixed ÑезÑлÑÑÐ°Ñ Ð¿Ð¾Ñледнего запÑоÑа к ÐÐ
*/
protected $lastResult;
/*
* Ð¢Ð¸Ð¿Ñ Ð¿Ð¾Ð»ÐµÐ¹ ÑÐ°Ð±Ð»Ð¸Ñ ÐÐ:
*/
/**
* Целое ÑиÑло
*/
const COLTYPE_INTEGER = 'INT';
/**
* ЧиÑло Ñ Ð¿Ð»Ð°Ð²Ð°ÑÑей ÑоÑкой
*/
const COLTYPE_FLOAT = 'FLOAT';
/**
* ÐаÑа
*/
const COLTYPE_DATE = 'DATE';
/**
* ÐÑемÑ
*/
const COLTYPE_TIME = 'TIME';
/**
* Timestamp
*/
const COLTYPE_TIMESTAMP = 'TIMESTAMP';
/**
* ÐаÑа и вÑемÑ
*/
const COLTYPE_DATETIME = 'DATETIME';
/**
* СÑÑока
*/
const COLTYPE_STRING = 'VARCHAR';
/**
* Ð¢Ð¸Ð¿Ñ ÑÑÑок ÑолÑко Ð´Ð»Ñ Ð²Ð½ÑÑÑеннего иÑполÑзованиÑ. Ðез комменÑаÑиев :)
*/
const COLTYPE_STRING1 = 'STRING';
const COLTYPE_STRING2 = 'VAR_STRING';
/**
* ТекÑÑ
*/
const COLTYPE_TEXT = 'TEXT';
/**
* ÐинаÑнÑе даннÑе
*/
const COLTYPE_BLOB = 'BLOB';
/**
* ÐÑибки
*/
const ERR_BAD_REQUEST = 'ERR_DATABASE_ERROR';
/**
* ÐеÑвиÑнÑй индекÑ
*
*/
const PRIMARY_INDEX = 'PRI';
/**
* УникалÑнÑй индекÑ
*
*/
const UNIQUE_INDEX = 'UNI';
/**
* ÐндекÑ
*
*/
const INDEX = 'MUL';
/**
* ÐонÑÑÑÑкÑÐ¾Ñ ÐºÐ»Ð°ÑÑа.
*
* @access public
* @param string $dsn Data Source Name Ð´Ð»Ñ Ð¿Ð¾Ð´ÐºÐ»ÑÑÐµÐ½Ð¸Ñ Ðº ÐÐ
* @param string $username Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑзоваÑелÑ
* @param string $password паÑолÑ
* @param array $driverOptions ÑпеÑиÑиÑеÑкие паÑамеÑÑÑ Ð´ÑайвеÑа ÐÐ
* @return void
*/
public function __construct($dsn, $username, $password, array $driverOptions, $charset = 'utf8') {
parent::__construct();
try {
$this->pdo = new PDO($dsn, $username, $password, $driverOptions);
}
catch (PDOException $e) {
throw new SystemException($e->getMessage(), SystemException::ERR_DB);
}
$this->pdo->query('SET NAMES '.$charset);
}
/**
* ÐÑполнÑÐµÑ SELECT-запÑÐ¾Ñ Ðº ÐÐ.
*
* ÐÑли колиÑеÑÑво аÑгÑменÑов меÑода болÑÑе 1, Ñогда $query ÑÑакÑÑеÑÑÑ
* как ÑÑÑока ÑоÑмаÑа подобно ÑÑнкÑии printf, а дополниÑелÑнÑе аÑгÑменÑÑ
* ÑкÑаниÑÑÑÑÑÑ Ð¸ помеÑаÑÑÑÑ Ð½Ð° меÑÑо меÑок (placeholder) ÑÑÑоки $query.
*
* ÐозвÑаÑÐ°ÐµÑ Ð² ÑезÑлÑÑаÑе:
* 1. ÐаÑÑив вида
* array(
* rowID => array(fieldName => fieldValue, ...)
* )
* еÑли запÑÐ¾Ñ Ð¸ÑполнилÑÑ ÑÑпеÑно и веÑнÑл какие-либо ÑÑÑоки;
* 2. true, еÑли запÑÐ¾Ñ Ð¸ÑполнилÑÑ ÑÑпеÑно, но не веÑнÑл ни одной ÑÑÑоки;
* 3. false, еÑли пÑи вÑполнении запÑоÑа пÑоизоÑла оÑибка.
*
* @access public
* @param string $query SELECT-запÑÐ¾Ñ Ðº ÐÐ
* @param mixed $var, ...
* @return mixed
* @see printf()
*/
public function selectRequest($query) {
if (!is_string($query) || strlen($query) == 0) {
return false;
}
$result = false;
$query = $this->constructQuery(func_get_args());
$this->lastQuery = $query;
$res = $this->pdo->query($query);
if (!($res instanceof PDOStatement)) {
$errorInfo = $this->pdo->errorInfo();
throw new SystemException(self::ERR_BAD_REQUEST, SystemException::ERR_DB, array($this->getLastRequest(), $errorInfo[2]));
}
$result = array();
$rowCount = 0;
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
$fieldNum = 0;
foreach ($row as $fieldName => $fieldValue) {
$fieldMeta = @$res->getColumnMeta($fieldNum);
if (isset($fieldMeta['native_type'])) {
if ($fieldMeta['native_type'] == self::COLTYPE_DATETIME ||
$fieldMeta['native_type'] == self::COLTYPE_DATE) {
$fieldValue = convertDatetimeToTimestamp($fieldValue);
}
elseif (in_array($fieldMeta['native_type'], array(self::COLTYPE_STRING1, self::COLTYPE_STRING2))) {
$fieldValue = stripslashes($fieldValue);
}
}
else {
if ($fieldMeta['len'] == 1) {
$fieldValue = (intval($fieldValue) == 0 ? false : true);
}
}
$result[$rowCount][$fieldName] = $fieldValue;
$fieldNum++;
}
$rowCount++;
}
if (empty($result)) {
$result = true;
}
$this->lastResult = $result;
return $result;
}
/**
* ÐÑполнÑÐµÑ Ð¼Ð¾Ð´Ð¸ÑиÑиÑÑÑÑÑÑ (INSERT, UPDATE, DELETE) опеÑаÑÐ¸Ñ Ð² ÐÐ.
*
* ÐÑли колиÑеÑÑво аÑгÑменÑов меÑода болÑÑе 1, Ñогда $query ÑÑакÑÑеÑÑÑ
* как ÑÑÑока ÑоÑмаÑа подобно ÑÑнкÑии printf, а дополниÑелÑнÑе аÑгÑменÑÑ
* ÑкÑаниÑÑÑÑÑÑ Ð¸ помеÑаÑÑÑÑ Ð½Ð° меÑÑо меÑок (placeholder) ÑÑÑоки $query.
*
* ÐозвÑаÑÐ°ÐµÑ Ð² ÑезÑлÑÑаÑе:
* 1. ÐоÑледний ÑгенеÑиÑованнÑй ID Ð´Ð»Ñ Ð¿Ð¾Ð»Ñ Ñипа AUTO_INCREMENT, или
* 2. true, еÑли запÑÐ¾Ñ Ð²Ñполнен ÑÑпеÑно;
* 2. false, в ÑлÑÑае неÑдаÑи.
*
* @access public
* @param string $query
* @return mixed
* @see printf()
*/
public function modifyRequest($query) {
if (!is_string($query) || strlen($query) == 0) {
return false;
}
$result = false;
$query = $this->constructQuery(func_get_args());
$this->lastQuery = $query;
$res = $this->pdo->query($query);
if (!($res instanceof PDOStatement)) {
$errorInfo = $this->pdo->errorInfo();
throw new SystemException(self::ERR_BAD_REQUEST, SystemException::ERR_DB, array($this->getLastRequest(), $errorInfo[2]));
}
$result = intval($this->pdo->lastInsertId());
if ($result == 0) {
$result = true;
}
$this->lastResult = $result;
return $result;
}
/**
* СÑÐ°Ð²Ð¸Ñ ÐºÐ°Ð²ÑÑки вокÑÑг вÑ
одной ÑÑÑоки (еÑли необÑ
одимо) и ÑкÑаниÑÑеÑ
* ÑпеÑиалÑнÑе ÑÐ¸Ð¼Ð²Ð¾Ð»Ñ Ð²Ð½ÑÑÑи вÑ
одной ÑÑÑоки.
*
* @access public
* @param string $string
* @return string
*/
public function quote($string) {
return $this->pdo->quote($string);
}
/**
* ÐозвÑаÑÐ°ÐµÑ Ð¿Ð¾Ñледний запÑÐ¾Ñ Ðº ÐÐ.
*
* @access public
* @return string
*/
public function getLastRequest() {
return $this->lastQuery;
}
/**
* ÐозвÑаÑÐ°ÐµÑ ÑезÑлÑÑÐ°Ñ Ð¿Ð¾Ñледнего запÑоÑа к ÐÐ.
*
* @access public
* @return mixed
*/
public function getLastResult() {
return $this->lastResult;
}
/**
* ÐозвÑаÑÐ°ÐµÑ Ð¿Ð¾ÑледнÑÑ Ð¾ÑибкÑ
*
* @return string
* @access public
*/
public function getLastError() {
return $this->pdo->errorInfo();
}
/**
* СÑаÑÑÑÐµÑ ÑÑанзакÑиÑ.
*
* @access public
* @return boolean
*/
public function beginTransaction() {
return $this->pdo->beginTransaction();
}
/**
* ÐÑполнÑÐµÑ (commit) ÑÑанзакÑиÑ.
*
* @access public
* @return boolean
*/
public function commit() {
return $this->pdo->commit();
}
/**
* ÐÑкаÑÑÐ²Ð°ÐµÑ ÑÑанзакÑиÑ.
*
* @access public
* @return boolean
*/
public function rollback() {
return $this->pdo->rollBack();
}
/**
* ÐозвÑаÑÐ°ÐµÑ Ð¸Ð½ÑоÑмаÑÐ¸Ñ Ð¾ колонкаÑ
ÑаблиÑÑ $tableName в виде маÑÑива:
* array(
* 'columnName' => array(
* 'type' => Ñип колонки,
* 'length' => длина,
* 'nullable' => пÑÐ¸Ð½Ð¸Ð¼Ð°ÐµÑ Ð»Ð¸ знаÑение NULL?,
* 'key' => опиÑание клÑÑа колонки (еÑли еÑÑÑ),
* 'default' => знаÑение по-ÑмолÑаниÑ,
* 'index'=> Ñип индекÑа
* )
* )
*
* @access public
* @param string $tableName
* @return array
*/
public function getColumnsInfo($tableName) {
$res = $this->selectRequest("SHOW COLUMNS FROM `$tableName`");
if (!is_array($res)) {
return false;
}
$result = false;
foreach ($res as $row) {
$name = $row['Field'];
$type = strtoupper($row['Type']);
$length = false;
$nullable = (strtolower($row['Null']) == 'yes' ? true : false);
$key = $row['Key'];
$index = $key;
$default = (empty($row['Default']))?false:$row['Default'];
// полÑÑаем Ñип и ÑÐ°Ð·Ð¼ÐµÑ Ð¿Ð¾Ð»Ñ
preg_match('/([A-Z]+)(\(([0-9]+)(,[0-9]+)?\))?/', $type, $matches);
if (count($matches) >= 2) {
$type = $matches[1];
if (isset($matches[3])) {
$length = intval($matches[3]);
}
}
$type = $this->convertType($type);
// полÑÑаем инÑоÑмаÑÐ¸Ñ Ð¾ клÑÑе полÑ
switch ($key) {
case 'PRI':
$fk = $this->getForeignKeyInfo($tableName, $name);
$key = ($fk == false ? true : $fk);
break;
case 'MUL':
$key = $this->getForeignKeyInfo($tableName, $name);
break;
default:
$key = false;
}
$result[$name] = compact('length', 'nullable', 'default', 'key', 'type' , 'tableName', 'index');
}
return $result;
}
/**
* ÐозвÑаÑÐ°ÐµÑ Ð¸Ð½ÑоÑмаÑÐ¸Ñ Ð¾ внеÑнем клÑÑе Ð¿Ð¾Ð»Ñ $fieldName ÑаблиÑÑ $tableName
* в виде маÑÑива
* array(
* 'tableName' => Ð¸Ð¼Ñ ÑаблиÑÑ,
* 'fieldName' => Ð¸Ð¼Ñ Ð¿Ð¾Ð»Ñ
* )
* или false, еÑли $tableName.$fieldName не ÑвлÑеÑÑÑ Ð¿ÐµÑвиÑнÑм клÑÑем.
*
* @access private
* @param string $tableName Ð¸Ð¼Ñ ÑаблиÑÑ
* @param string $fieldName Ð¸Ð¼Ñ Ð¿Ð¾Ð»Ñ
* @return mixed
*/
private function getForeignKeyInfo($tableName, $fieldName) {
/*
$res = $this->selectRequest("SHOW TABLE STATUS LIKE '$tableName'");
$fkinfos = explode(';', $res[0]['Comment']);
foreach ($fkinfos as $fkinfo) {
if (preg_match('/\(`([^`]+)`\) REFER `([^`]+)\/([^`]+)`(\(`([^`]+)`\))?/', $fkinfo, $matches) && count($matches) >= 4) {
$matches[4] = (isset($matches[4]) ? $matches[5] : $matches[1]);
if ($fieldName == $matches[1]) {
$result = array('tableName' => $matches[3], 'fieldName' => $matches[4]);
return $result;
}
}
}
return false;
*/
$res = $this->selectRequest("SHOW CREATE TABLE $tableName");
$fkinfos = explode(",", $res[0]['Create Table']);
foreach ($fkinfos as $fkinfo) {
if (preg_match("/FOREIGN KEY \(`$fieldName`\) REFERENCES `([^`]+)` \(`([^`]+)`\)/", $fkinfo, $matches)/* && sizeof($matches) >= 4*/) {
$result = array('tableName' => $matches[1], 'fieldName' => $matches[2]);
return $result;
}
}
return false;
}
/**
* ÐонвеÑÑиÑÑÐµÑ Ñип даннÑÑ
из опиÑÐ°Ð½Ð¸Ñ ÐÐ (MySQL) в наÑ, ÑиÑÑемнÑй Ñип.
*
* @access private
* @param string $mysqlType
* @return string
*/
private function convertType($mysqlType) {
$result = $mysqlType;
switch ($mysqlType) {
case 'TINYINT':
case 'MEDIUM':
case 'SMALLINT':
case 'INT':
case 'BIGINT':
$result = self::COLTYPE_INTEGER;
break;
case 'FLOAT':
case 'DOUBLE':
case 'DECIMAL':
case 'NUMERIC':
$result = self::COLTYPE_FLOAT;
break;
case 'DATE':
$result = self::COLTYPE_DATE;
break;
case 'TIME':
$result = self::COLTYPE_TIME;
break;
case 'TIMESTAMP':
$result = self::COLTYPE_TIMESTAMP;
break;
case 'DATETIME':
$result = self::COLTYPE_DATETIME;
break;
case 'VARCHAR':
case 'CHAR':
$result = self::COLTYPE_STRING;
break;
case 'TEXT':
case 'TINYTEXT':
case 'MEDIUMTEXT':
case 'LONGTEXT':
$result = self::COLTYPE_TEXT;
break;
case 'BLOB':
case 'TINYBLOB':
case 'MEDIUMBLOB':
case 'LONGBLOB':
$result = self::COLTYPE_BLOB;
break;
default: // не иÑполÑзÑеÑÑÑ
}
return $result;
}
/**
* ÐозвÑаÑÐ°ÐµÑ Ð´Ð»Ñ ÑаблиÑÑ $tableName Ð¸Ð¼Ñ ÑаблиÑÑ Ñ Ð¿ÐµÑеводами,
* еÑли ÑÐ°ÐºÐ°Ñ ÑÑÑеÑÑвÑеÑ. РпÑоÑивном ÑлÑÑае возвÑаÑÐ°ÐµÑ false.
*
* @access public
* @param string $tableName
* @return mixed
*/
public function getTranslationTablename($tableName) {
$tableName .= '_translation';
$res = $this->selectRequest("SHOW TABLES LIKE '$tableName'");
return (empty($res) || $res === true) ? false : $tableName;
}
/**
* ФоÑмиÑÑÐµÑ ÑÑÑÐ¾ÐºÑ Ð·Ð°Ð¿ÑоÑа к ÐÐ.
*
* @access private
* @param array $args маÑÑив аÑгÑменÑов, пеÑеданнÑÑ
в меÑÐ¾Ð´Ñ selectRequest и modifyRequest
* @return string
* @see DBA::selectRequest()
* @see DBA::modifyRequest()
*/
private function constructQuery(array $args) {
if (sizeof($args) > 1) {
$query = array_shift($args); // оÑбÑаÑÑваем пеÑвÑй аÑгÑÐ¼ÐµÐ½Ñ $query
foreach ($args as &$arg) {
$arg = $this->pdo->quote($arg);
}
array_unshift($args, $query);
$query = call_user_func_array('sprintf', $args);
}
else {
$query = $args[0];
}
return $query;
}
}