energine
[ class tree: energine ] [ index: energine ] [ all elements ]

Source for file DBA.class.php

Documentation is available at DBA.class.php

  1. <?php
  2.  
  3. /**
  4.  * Класс DBA.
  5.  *
  6.  * @package energine
  7.  * @subpackage core
  8.  * @author 1m.dm
  9.  * @copyright Energine 2006
  10.  * @version $Id$
  11.  */
  12.  
  13. //require_once('core/framework/SystemConfig.class.php');
  14.  
  15. /**
  16.  * Database Abstraction Layer.
  17.  *
  18.  * @package energine
  19.  * @subpackage core
  20.  * @author 1m.dm
  21.  * @abstract
  22.  */
  23. abstract class DBA extends Object {
  24.  
  25.     /**
  26.      * @access protected
  27.      * @var PDO экземпляр класса PDO (PHP Data Objects)
  28.      */
  29.     protected $pdo;
  30.  
  31.     /**
  32.      * @access protected
  33.      * @var string последний запрос к БД
  34.      */
  35.     protected $lastQuery;
  36.  
  37.     /**
  38.      * @access protected
  39.      * @var mixed результат последнего запроса к БД
  40.      */
  41.     protected $lastResult;
  42.  
  43.     /*
  44.      * Типы полей таблиц БД:
  45.      */
  46.  
  47.     /**
  48.      * Целое число
  49.      */
  50.     const COLTYPE_INTEGER = 'INT';
  51.  
  52.     /**
  53.      * Число с плавающей точкой
  54.      */
  55.     const COLTYPE_FLOAT = 'FLOAT';
  56.  
  57.     /**
  58.      * Дата
  59.      */
  60.     const COLTYPE_DATE = 'DATE';
  61.  
  62.     /**
  63.      * Время
  64.      */
  65.     const COLTYPE_TIME = 'TIME';
  66.  
  67.     /**
  68.      * Timestamp
  69.      */
  70.     const COLTYPE_TIMESTAMP = 'TIMESTAMP';
  71.  
  72.     /**
  73.      * Дата и время
  74.      */
  75.     const COLTYPE_DATETIME = 'DATETIME';
  76.  
  77.     /**
  78.      * Строка
  79.      */
  80.     const COLTYPE_STRING = 'VARCHAR';
  81.  
  82.     /**
  83.      * Типы строк только для внутреннего использования. Без комментариев :)
  84.      */
  85.     const COLTYPE_STRING1 = 'STRING';
  86.     const COLTYPE_STRING2 = 'VAR_STRING';
  87.  
  88.     /**
  89.      * Текст
  90.      */
  91.     const COLTYPE_TEXT = 'TEXT';
  92.  
  93.     /**
  94.      * Бинарные данные
  95.      */
  96.     const COLTYPE_BLOB = 'BLOB';
  97.  
  98.     /**
  99.      * Ошибки
  100.      */
  101.     const ERR_BAD_REQUEST = 'ERR_DATABASE_ERROR';
  102.  
  103.     /**
  104.      * Первичный индекс
  105.      *
  106.      */
  107.     const PRIMARY_INDEX = 'PRI';
  108.  
  109.     /**
  110.      * Уникальный индекс
  111.      *
  112.      */
  113.     const UNIQUE_INDEX = 'UNI';
  114.  
  115.     /**
  116.      * Индекс
  117.      *
  118.      */
  119.     const INDEX = 'MUL';
  120.  
  121.     /**
  122.      * Конструктор класса.
  123.      *
  124.      * @access public
  125.      * @param string $dsn Data Source Name для подключения к БД
  126.      * @param string $username имя пользователя
  127.      * @param string $password пароль
  128.      * @param array $driverOptions специфические параметры драйвера БД
  129.      * @return void 
  130.      */
  131.     public function __construct($dsn$username$passwordarray $driverOptions$charset 'utf8'{
  132.         parent::__construct();
  133.         try {
  134.             $this->pdo = new PDO($dsn$username$password$driverOptions);
  135.         }
  136.         catch (PDOException $e{
  137.             throw new SystemException('Unable to connect. The site is temporarily unavailable.'SystemException::ERR_DB'The site is temporarily unavailable');
  138.         }
  139.         $this->pdo->query('SET NAMES '.$charset);
  140.     }
  141.  
  142.     /**
  143.      * Выполняет SELECT-запрос к БД.
  144.      *
  145.      * Если количество аргументов метода больше 1, тогда $query трактуется
  146.      * как строка формата подобно функции printf, а дополнительные аргументы
  147.      * экранируются и помещаются на место меток (placeholder) строки $query.
  148.      *
  149.      * Возвращает в результате:
  150.      *     1. Массив вида
  151.      *            array(
  152.      *                rowID => array(fieldName => fieldValue, ...)
  153.      *            )
  154.      *        если запрос исполнился успешно и вернул какие-либо строки;
  155.      *     2. true, если запрос исполнился успешно, но не вернул ни одной строки;
  156.      *     3. false, если при выполнении запроса произошла ошибка.
  157.      *
  158.      * @access public
  159.      * @param string $query SELECT-запрос к БД
  160.      * @param mixed $var, ...
  161.      * @return mixed 
  162.      * @see printf()
  163.      */
  164.     public function selectRequest($query{
  165.         if (!is_string($query|| strlen($query== 0{
  166.             return false;
  167.         }
  168.  
  169.         $result false;
  170.  
  171.         $query $this->constructQuery(func_get_args());
  172.         //simple_log($query);
  173.         $this->lastQuery $query;
  174.         $res $this->pdo->query($query);
  175.  
  176.         if (!($res instanceof PDOStatement)) {
  177.             $errorInfo $this->pdo->errorInfo();
  178.             throw new SystemException(self::ERR_BAD_REQUESTSystemException::ERR_DBarray($this->getLastRequest()$errorInfo[2]));
  179.         }
  180.  
  181.         $result array();
  182.         $rowCount 0;
  183.         while ($row $res->fetch(PDO::FETCH_ASSOC)) {
  184.             $fieldNum 0;
  185.             foreach ($row as $fieldName => $fieldValue{
  186.                 $fieldMeta @$res->getColumnMeta($fieldNum);
  187.                 if (isset($fieldMeta['native_type'])) {
  188.                     if ($fieldMeta['native_type'== self::COLTYPE_DATETIME ||
  189.                         $fieldMeta['native_type'== self::COLTYPE_DATE{
  190.                         $fieldValue convertDatetimeToTimestamp($fieldValue);
  191.                     }
  192.                     elseif (in_array($fieldMeta['native_type']array(self::COLTYPE_STRING1self::COLTYPE_STRING2))) {
  193.                         $fieldValue stripslashes($fieldValue);
  194.                     }
  195.                 }
  196.                 else {
  197.                     if ($fieldMeta['len'== 1{
  198.                         $fieldValue (intval($fieldValue== false true);
  199.                     }
  200.                 }
  201.                 $result[$rowCount][$fieldName$fieldValue;
  202.                 $fieldNum++;
  203.             }
  204.             $rowCount++;
  205.         }
  206.  
  207.         if (empty($result)) {
  208.             $result true;
  209.         }
  210.  
  211.         $this->lastResult $result;
  212.         return $result;
  213.     }
  214.  
  215.     /**
  216.      * Выполняет модифицирующую (INSERT, UPDATE, DELETE) операцию в БД.
  217.      *
  218.      * Если количество аргументов метода больше 1, тогда $query трактуется
  219.      * как строка формата подобно функции printf, а дополнительные аргументы
  220.      * экранируются и помещаются на место меток (placeholder) строки $query.
  221.      *
  222.      * Возвращает в результате:
  223.      *     1. Последний сгенерированный ID для поля типа AUTO_INCREMENT, или
  224.      *     2. true, если запрос выполнен успешно;
  225.      *     2. false, в случае неудачи.
  226.      *
  227.      * @access public
  228.      * @param string $query 
  229.      * @return mixed 
  230.      * @see printf()
  231.      */
  232.     public function modifyRequest($query{
  233.         if (!is_string($query|| strlen($query== 0{
  234.             return false;
  235.         }
  236.  
  237.         $result false;
  238.  
  239.         $query $this->constructQuery(func_get_args());
  240.         $this->lastQuery $query;
  241.         $res $this->pdo->query($query);
  242.  
  243.         if (!($res instanceof PDOStatement)) {
  244.             $errorInfo $this->pdo->errorInfo();
  245.             throw new SystemException(self::ERR_BAD_REQUESTSystemException::ERR_DBarray($this->getLastRequest()$errorInfo[2]));
  246.         }
  247.  
  248.         $result intval($this->pdo->lastInsertId());
  249.  
  250.         if ($result == 0{
  251.             $result true;
  252.         }
  253.  
  254.         $this->lastResult $result;
  255.         return $result;
  256.     }
  257.  
  258.     /**
  259.      * Ставит кавычки вокруг входной строки (если необходимо) и экранирует
  260.      * специальные символы внутри входной строки.
  261.      *
  262.      * @access public
  263.      * @param string $string 
  264.      * @return string 
  265.      */
  266.     public function quote($string{
  267.         return $this->pdo->quote($string);
  268.     }
  269.  
  270.     /**
  271.      * Возвращает последний запрос к БД.
  272.      *
  273.      * @access public
  274.      * @return string 
  275.      */
  276.     public function getLastRequest({
  277.         return $this->lastQuery;
  278.     }
  279.  
  280.     /**
  281.      * Возвращает результат последнего запроса к БД.
  282.      *
  283.      * @access public
  284.      * @return mixed 
  285.      */
  286.     public function getLastResult({
  287.         return $this->lastResult;
  288.     }
  289.     /**
  290.      * Возвращает последнюю ошибку
  291.      *
  292.      * @return string 
  293.      * @access public
  294.      */
  295.  
  296.     public function getLastError({
  297.         return $this->pdo->errorInfo();
  298.     }
  299.     /**
  300.      * Стартует транзакцию.
  301.      *
  302.      * @access public
  303.      * @return boolean 
  304.      */
  305.     public function beginTransaction({
  306.         return $this->pdo->beginTransaction();
  307.     }
  308.  
  309.     /**
  310.      * Выполняет (commit) транзакцию.
  311.      *
  312.      * @access public
  313.      * @return boolean 
  314.      */
  315.     public function commit({
  316.         return $this->pdo->commit();
  317.     }
  318.  
  319.     /**
  320.      * Откатывает транзакцию.
  321.      *
  322.      * @access public
  323.      * @return boolean 
  324.      */
  325.     public function rollback({
  326.         return $this->pdo->rollBack();
  327.     }
  328.  
  329.     /**
  330.      * Возвращает информацию о колонках таблицы $tableName в виде массива:
  331.      *     array(
  332.      *         'columnName' => array(
  333.      *             'type' => тип колонки,
  334.      *             'length' => длина,
  335.      *             'nullable' => принимает ли значение NULL?,
  336.      *             'key' => описание ключа колонки (если есть),
  337.      *             'default' => значение по-умолчанию,
  338.      *             'index'=> тип индекса
  339.      *         )
  340.      *     )
  341.      *
  342.      * @access public
  343.      * @param string $tableName 
  344.      * @return array 
  345.      * @staticvar $columnsInfo  - кеш полей таблицы
  346.      */
  347.     public function getColumnsInfo($tableName{
  348.         static $columnsInfo;
  349.          
  350.         if(!isset($columnsInfo[$tableName])){
  351.             $res $this->selectRequest("SHOW COLUMNS FROM `$tableName`");
  352.             foreach ($res as $row{
  353.                 $name $row['Field'];
  354.                 $type strtoupper($row['Type']);
  355.                 $length false;
  356.                 $nullable (strtolower($row['Null']== 'yes' true false);
  357.                 $key $row['Key'];
  358.                 $index $key;
  359.                 $default (empty($row['Default']))?false:$row['Default'];
  360.     
  361.                 // получаем тип и размер поля
  362.                 preg_match('/([A-Z]+)(\(([0-9]+)(,[0-9]+)?\))?/'$type$matches);
  363.                 if (count($matches>= 2{
  364.                     $type $matches[1];
  365.                     if (isset($matches[3])) {
  366.                         $length intval($matches[3]);
  367.                     }
  368.                 }
  369.                 $type $this->convertType($type);
  370.     
  371.                 // получаем информацию о ключе поля
  372.                 switch ($key{
  373.                     case 'PRI':
  374.                         $fk $this->getForeignKeyInfo($tableName$name);
  375.                         $key ($fk == false true $fk);
  376.                         break;
  377.                     case 'MUL':
  378.                         $key $this->getForeignKeyInfo($tableName$name);
  379.                         break;
  380.                     default:
  381.                         $key false;
  382.                 }
  383.     
  384.                 $columnsInfo[$tableName][$namecompact('length''nullable''default''key''type' 'tableName''index');
  385.             }
  386.         }
  387.         return $columnsInfo[$tableName];
  388.     }
  389.  
  390.     /**
  391.      * Возвращает информацию о внешнем ключе поля $fieldName таблицы $tableName
  392.      * в виде массива
  393.      *     array(
  394.      *         'tableName' => имя таблицы,
  395.      *         'fieldName' => имя поля
  396.      *     )
  397.      * или false, если $tableName.$fieldName не является первичным ключем.
  398.      *
  399.      * @access private
  400.      * @param string $tableName имя таблицы
  401.      * @param string $fieldName имя поля
  402.      * @return mixed 
  403.      * @staticvar $foreignKeyInfo кеш результатов
  404.      */
  405.     private function getForeignKeyInfo($tableName$fieldName{
  406.         static $foreignKeyInfo;
  407.         if(!isset($foreignKeyInfo[$tableName][$fieldName])){
  408.             $res $this->selectRequest("SHOW CREATE TABLE `$tableName`");
  409.             $res preg_match_all("/FOREIGN KEY \(`([_a-z0-9]+)`\) REFERENCES `([^`]+)` \(`([^`]+)`\)/m"$res[0]['Create Table']$matchesPREG_SET_ORDER);
  410.             if(!empty($res)){
  411.                 foreach($matches as $row){
  412.                   $foreignKeyInfo[$tableName][$row[1]] array('tableName' => $row[2]'fieldName' => $row[3]);    
  413.                 }
  414.             }
  415.         }
  416.         
  417.         if(!isset($foreignKeyInfo[$tableName][$fieldName])){
  418.             $foreignKeyInfo[$tableName][$fieldNamefalse;
  419.         }
  420.         return $foreignKeyInfo[$tableName][$fieldName];
  421.     }
  422.  
  423.     /**
  424.      * Конвертирует тип данных из описания БД (MySQL) в наш, системный тип.
  425.      *
  426.      * @access private
  427.      * @param string $mysqlType 
  428.      * @return string 
  429.      */
  430.     private function convertType($mysqlType{
  431.         $result $mysqlType;
  432.         switch ($mysqlType{
  433.             case 'TINYINT':
  434.             case 'MEDIUM':
  435.             case 'SMALLINT':
  436.             case 'INT':
  437.             case 'BIGINT':
  438.                 $result self::COLTYPE_INTEGER;
  439.                 break;
  440.             case 'FLOAT':
  441.             case 'DOUBLE':
  442.             case 'DECIMAL':
  443.             case 'NUMERIC':
  444.                 $result self::COLTYPE_FLOAT;
  445.                 break;
  446.             case 'DATE':
  447.                 $result self::COLTYPE_DATE;
  448.                 break;
  449.             case 'TIME':
  450.                 $result self::COLTYPE_TIME;
  451.                 break;
  452.             case 'TIMESTAMP':
  453.                 $result self::COLTYPE_TIMESTAMP;
  454.                 break;
  455.             case 'DATETIME':
  456.                 $result self::COLTYPE_DATETIME;
  457.                 break;
  458.             case 'VARCHAR':
  459.             case 'CHAR':
  460.                 $result self::COLTYPE_STRING;
  461.                 break;
  462.             case 'TEXT':
  463.             case 'TINYTEXT':
  464.             case 'MEDIUMTEXT':
  465.             case 'LONGTEXT':
  466.                 $result self::COLTYPE_TEXT;
  467.                 break;
  468.             case 'BLOB':
  469.             case 'TINYBLOB':
  470.             case 'MEDIUMBLOB':
  471.             case 'LONGBLOB':
  472.                 $result self::COLTYPE_BLOB;
  473.                 break;
  474.             default// не используется
  475.         }
  476.         return $result;
  477.     }
  478.  
  479.     /**
  480.      * Возвращает для таблицы $tableName имя таблицы с переводами,
  481.      * если такая существует. В противном случае возвращает false.
  482.      *
  483.      * @access public
  484.      * @param string $tableName 
  485.      * @return mixed 
  486.      */
  487.     public function getTranslationTablename($tableName{
  488.         static $translationTables;
  489.         
  490.         if(!isset($translationTables[$tableName])){
  491.            $translationTableName $tableName.'_translation';
  492.            $res $this->selectRequest('SHOW TABLES LIKE \''.$translationTableName.'\'');
  493.            $translationTables[$tableName(empty($res|| $res === truefalse $translationTableName;       
  494.         }
  495.         return $translationTables[$tableName];
  496.     }
  497.  
  498.     /**
  499.      * Формирует строку запроса к БД.
  500.      *
  501.      * @access private
  502.      * @param array $args массив аргументов, переданных в методы selectRequest и modifyRequest
  503.      * @return string 
  504.      * @see DBA::selectRequest()
  505.      * @see DBA::modifyRequest()
  506.      */
  507.     private function constructQuery(array $args{
  508.         if (sizeof($args1{
  509.             $query array_shift($args)// отбрасываем первый аргумент $query
  510.             foreach ($args as &$arg{
  511.                 $arg $this->pdo->quote($arg);
  512.             }
  513.             array_unshift($args$query);
  514.             $query call_user_func_array('sprintf'$args);
  515.         }
  516.         else {
  517.             $query $args[0];
  518.         }
  519.         return $query;
  520.     }
  521. }
В создании документации нам помог: phpDocumentor