1: <?php defined('_JOOS_CORE') or exit();
2:
3: /**
4: * Библиотека работы с базой данных Mysql через Mysqli
5: * Системная библиотека
6: *
7: * @version 1.0
8: * @package Core\Libraries
9: * @subpackage Database\Drivers
10: * @category Libraries
11: * @author Joostina Team <info@joostina.ru>
12: * @copyright (C) 2007-2012 Joostina Team
13: * @license MIT License http://www.opensource.org/licenses/mit-license.php
14: * Информация об авторах и лицензиях стороннего кода в составе Joostina CMS: docs/copyrights
15: *
16: * */
17: class joosDatabaseMysqli implements joosInterfaceDatabase
18: {
19: /**
20: * Объект активного соединения с базой данных
21: *
22: * @var joosDatabase
23: */
24: private static $instance;
25:
26: /**
27: * Переменныя хранения активной или готовящейся к выполнению SQL команды
28: *
29: * @var string
30: */
31: protected $_sql;
32:
33: /**
34: * Код ошибки работы с базой данных
35: *
36: * @var int
37: */
38: protected $_error_num = 0;
39:
40: /**
41: * Текст ошибки работы с базой данных
42: *
43: * @var string
44: */
45: protected $_error_msg;
46:
47: /**
48: * Префикс таблиц активного соединения
49: *
50: * @var string
51: */
52: protected $_table_prefix = 'jos_';
53:
54: /**
55: * Ресурс активного соединения с базой данных
56: *
57: * @var mysqli_result
58: */
59: protected $_resource;
60:
61: /**
62: * Результат последнего активного SQL запроса
63: *
64: * @var mysqli_result
65: */
66: protected $_cursor;
67:
68: /**
69: * Лимит для активного запроса
70: *
71: * @var int
72: */
73: protected $_limit;
74:
75: /**
76: * Смещение для активного запроса
77: *
78: * @var int
79: */
80: protected $_offset;
81:
82: /**
83: * Конструктор открывающий соединение с базой данных
84: *
85: * @param string $host - хост базы данных, обычно localhost
86: * @param string $user - имя пользователя базы данных
87: * @param string $pass - пароль соединения с базой данных
88: * @param string $db - название базы данных
89: * @param int $port - порт сервера MySQL
90: * @param string $socket - сокет MySQL
91: */
92: protected function __construct($host = 'localhost', $user = 'root', $pass = '', $db = '', $port = null, $socket = null)
93: {
94: // проверка доступности поддержки работы с базой данных в php
95: if (!function_exists('mysqli_connect')) {
96:
97: joosPages::error_database('Нет поддержки mysql');
98: }
99:
100: // попытка соединиться с сервером баз данных
101: if (!($this->_resource = mysqli_connect($host, $user, $pass, $db, $port, $socket))) {
102:
103: joosPages::error_database('Ошибка соединения с БД');
104: }
105:
106: // при активации отладки выполнение дополнительных запросов профилирования
107: if (JDEBUG) {
108:
109: mysqli_query($this->_resource, 'set profiling=1');
110: mysqli_query($this->_resource, sprintf('set profiling_history_size=%s', joosConfig::get2('db', 'profiling_history_size', 100)));
111: }
112:
113: // устанавливаем кодировку для корректного соединения с сервером базы данных
114: mysqli_set_charset($this->_resource, 'utf8');
115: }
116:
117: /**
118: * Уничтожение объекта
119: * При уничтожении объекта происходит закрытие соединения с базой
120: *
121: */
122: public function __destruct()
123: {
124: if (is_resource($this->_resource)) {
125: // TODO это убрать при постоянных соединениях
126: mysqli_close($this->_resource);
127: }
128: }
129:
130: /**
131: * Получение инстанции для работы с базой данных
132: * @return joosDatabaseMysqli - объект базы данных
133: */
134: public static function instance()
135: {
136: // отметка получения инстенции базы данных
137: JDEBUG ? joosDebug::inc('joosDatabaseMysqli::instance()') : null;
138:
139: if (self::$instance === NULL) {
140: $db_config = joosConfig::get('db');
141: $database = new self($db_config['host'], $db_config['user'], $db_config['password'], $db_config['name']);
142:
143: if ($database->_error_num) {
144: $error_message = $database->_error_msg;
145: joosPages::error_database($error_message);
146: }
147: self::$instance = $database;
148: }
149:
150: return self::$instance;
151: }
152:
153: /**
154: * Закрытый метод для предотвращения клонирования объекта базы данных
155: *
156: * @todo исправить, метод CLONE используется при кешированиии и сериалзации модели
157: */
158: public function __clone()
159: {
160: }
161:
162: /**
163: * Экранирование элементов
164: *
165: * @param string $text - значение для экранирования
166: * @param boolean $extra - дополнительная обработка элемента
167: *
168: * @return string
169: */
170: public function get_escaped($text, $extra = false)
171: {
172: $string = mysqli_real_escape_string($this->_resource, $text);
173:
174: return $extra ? addcslashes($string, '%_') : $string;
175: }
176:
177: /**
178: * Получение квотированного значения элемента
179: *
180: * @param string $text - значение для квотирования
181: * @param boolean $escaped - параметр расширенного квотирования
182: *
183: * @return string обработанный результат
184: */
185: public function get_quoted($text, $escaped = true)
186: {
187: return '\'' . ($escaped ? $this->get_escaped($text) : $text) . '\'';
188: }
189:
190: /**
191: * Квотирование элементов спецсиволами
192: * Используется для обрамления названий таблиц и полей базы данных в SQL запросах
193: *
194: * @param string $s встрока для квотирования
195: *
196: * @return string обработанная строка
197: */
198: public function get_name_quote($s)
199: {
200: return '`' . $s . '`';
201: }
202:
203: /**
204: * Получение преффикса таблиц, по умолчанию jos_
205: * Преффиксы используются для размещения в одной базе данных нескольких структур баз разных сайтов
206: * @return string
207: */
208: public function get_prefix()
209: {
210: return $this->_table_prefix;
211: }
212:
213: /**
214: * Установка префикса таблиц базы данных
215: *
216: * @param string $prefix
217: */
218: public function set_prefix($prefix)
219: {
220: $this->_table_prefix = $prefix;
221: }
222:
223: /**
224: * Получение нулевого значения времени для использования по умолчанию в sql запросах
225: * @return string строка определяющая нулевое значение времени для использования в базе
226: */
227: public function get_null_date()
228: {
229: return '0000-00-00 00:00:00';
230: }
231:
232: /**
233: * Установка строки SQL запроса для дальнейшего выполнения
234: * Первый и гравный метод для любой работы с базой данных
235: *
236: * @param string $sql текст sql запроса для выполнения
237: * @param int $offset значения смещения для результато ввыборки
238: * @param int $limit ограничение н ачисло выбираемых объектов
239: *
240: * @return joosDatabaseMysqli
241: */
242: public function set_query($sql, $offset = 0, $limit = 0)
243: {
244: $this->_sql = $this->replace_prefix($sql);
245: $this->_limit = (int) $limit;
246: $this->_offset = (int) $offset;
247:
248: return $this;
249: }
250:
251: /**
252: * Замена преффикса таблиц базы данных
253: *
254: * @param string $sql текст sql запроса для замены преффикса
255: *
256: * @return string sql с заменённым преффиксом
257: */
258: private function replace_prefix($sql)
259: {
260: return str_replace('#__', $this->_table_prefix, $sql);
261: }
262:
263: /**
264: * Получение текста последнего установленного SQL запроса
265: * @return string строка sql запроса
266: */
267: public function get_query()
268: {
269: return sprintf('<pre code="sql">%s</pre>', htmlspecialchars($this->_sql, ENT_QUOTES, 'utf-8'));
270: }
271:
272: /**
273: * Выполнение установленного ранее SQL запроса
274: * Непосредственно само действие выполняемое в базе данных
275: *
276: * @return mysqli_result ресурс результата выполнения запроса
277: * @throws joosDatabaseException
278: */
279: public function query()
280: {
281: if ($this->_limit > 0 && $this->_offset == 0) {
282: $this->_sql .= "\nLIMIT $this->_limit";
283: } elseif ($this->_limit > 0 || $this->_offset > 0) {
284: $this->_sql .= "\nLIMIT $this->_offset, $this->_limit";
285: }
286:
287: $this->_error_num = 0;
288: $this->_error_msg = '';
289: $this->_cursor = mysqli_query($this->_resource, $this->_sql);
290:
291: if (!$this->_cursor) {
292:
293: throw new joosDatabaseException('Ошибка выполнения SQL #:error_num <br /> :error_message.<br /><br /> Ошибка в команде: :sql', array(':error_num' => mysqli_errno($this->_resource), ':error_message' => mysqli_error($this->_resource), ':sql' => $this->_sql));
294:
295: }
296:
297: return $this->_cursor;
298: }
299:
300: /**
301: * Возвращает количество рядов, задействованных в последнем запросе INSERT, UPDATE или DELETE
302: * @return int число рядок результатов
303: */
304: public function get_affected_rows()
305: {
306: return mysqli_affected_rows($this->_resource);
307: }
308:
309: /**
310: * Возвращает один (первый) результат выполненного запроса
311: * @return string строка результата
312: */
313: public function load_result()
314: {
315: // TODO, логично, но спорно
316: //$this->_limit = 1;
317: //$this->_offset = 0;
318:
319: $cur = $this->query();
320:
321: $ret = ($row = mysqli_fetch_row($cur)) ? $row[0] : null;
322:
323: $this->free_result();
324:
325: return $ret;
326: }
327:
328: /**
329: * Возвращает результат запроса в виде массива. Массив содержит значения столбца под номером указанным в $numinarray
330: *
331: * @param int $numinarray номер столбца для отобрадения в результуриющем запросе. 0 - первый столбцев, 1 - второй столбец и т.д
332: *
333: * @return array массив результата
334: */
335: public function load_result_array($numinarray = 0)
336: {
337: $cur = $this->query();
338:
339: $array = array();
340: while ($row = mysqli_fetch_row($cur)) {
341: $array[] = $row[$numinarray];
342: }
343:
344: $this->free_result();
345:
346: return $array;
347: }
348:
349: /**
350: * Возвращаем массив результата запроса. Каждый результирующий столбец хранится как массив массива, начиная со позиции 0.
351: * Может возвращать ассоциативный массив гд еключем выступает значение поля указанное в параметре $key
352: *
353: * @param string $key поле выступающее в качестве ключа для ассоциативного массива результата
354: *
355: * @return array ассоциативнй либо обычный массив массивов результата
356: */
357: public function load_assoc_list($key = '')
358: {
359: $cur = $this->query();
360:
361: $array = array();
362: while ($row = mysqli_fetch_assoc($cur)) {
363: if ($key) {
364: $array[$row[$key]] = $row;
365: } else {
366: $array[] = $row;
367: }
368: }
369:
370: $this->free_result();
371:
372: return $array;
373: }
374:
375: /**
376: * Возвращает первый результат запроса в виде ассоциативного массива название поля - значение
377: * @return array ассоциативный массив результата
378: */
379: public function load_assoc_row()
380: {
381: $cur = $this->query();
382:
383: $row = mysqli_fetch_assoc($cur);
384:
385: $this->free_result();
386:
387: return $row;
388: }
389:
390: /**
391: * Загружает результат запроса в принимаемы в качестве параметра объект
392: *
393: * @param joosModel|stdClass $object объект для загрузки результата
394: *
395: * @return bool результат сбора результата в значения полей принимаемого объекта
396: */
397: public function load_object(& $object)
398: {
399: if ($object != null) {
400:
401: $cur = $this->query();
402:
403: if (($array = (array) mysqli_fetch_assoc($cur))) {
404: $this->free_result();
405: $this->bind_array_to_object($array, $object, null, null, false);
406:
407: return true;
408: } else {
409: return false;
410: }
411: } else {
412: if (($cur = $this->query())) {
413: if (($object = mysqli_fetch_object($cur))) {
414: $this->free_result();
415:
416: return true;
417: } else {
418: $object = null;
419:
420: return false;
421: }
422: } else {
423: return false;
424: }
425: }
426: }
427:
428: /**
429: * Возвращает ассоциативный либо простой массив объектов результата запроса.
430: * В качестве ключей массива результата может быть использовано значение поля указанного в $key
431: *
432: * @param boolean|string $key поле выступающее в качестве ключа для ассоциативного массива результата
433: *
434: * @return array ассоциативный или обычный массив результатов
435: */
436: public function load_object_list($key = false)
437: {
438: $cur = $this->query();
439:
440: $array = array();
441: while ($row = mysqli_fetch_object($cur)) {
442: if ($key) {
443: $array[$row->$key] = $row;
444: } else {
445: $array[] = $row;
446: }
447: }
448:
449: $this->free_result();
450:
451: return $array;
452: }
453:
454: /**
455: * Версия load_object_list работающая с кешем
456: *
457: * @param boolean|string $key поле выступающее в качестве ключа для ассоциативного массива результата
458: * @param int $cache_time Время жизни кэша
459: *
460: * @return array ассоциативный или обычный массив результатов
461: */
462: public function load_object_list_cache($key = false, $cache_time = 86400)
463: {
464: $cache = new joosCache();
465: $cache_key = md5($this->_sql);
466:
467: if (($value = $cache->get($cache_key)) === NULL) {
468:
469: if (!($cur = $this->query())) {
470: return null;
471: }
472:
473: $value = array();
474: while ($row = mysqli_fetch_object($cur)) {
475: if ($key) {
476: $value[$row->$key] = $row;
477: } else {
478: $value[] = $row;
479: }
480: }
481:
482: $this->free_result();
483:
484: $cache->set($cache_key, $value, $cache_time);
485: }
486:
487: return $value;
488: }
489:
490: /**
491: * Возвращает массив результата запроса, в котором в качестве значений выступают значения полей первого результата
492: * @return array массив значение полей первого результата
493: */
494: public function load_row()
495: {
496: $cur = $this->query();
497:
498: $ret = ($row = mysqli_fetch_row($cur)) ? $row : null;
499:
500: $this->free_result();
501:
502: return $ret;
503: }
504:
505: /**
506: * Возвращает ассоциативный массив результата запроса.
507: * В качестве ключей массива результата может быть использовано номер поля указанного в $key
508: *
509: * @param int|bool $key номер поля начиная с 0, значение которого необходимо использовать вкачестве ключа для ассициативного массива результата
510: * @return array ассоциативный или обычный массив результатов
511: */
512: public function load_row_list($key = false)
513: {
514: $cur = $this->query();
515:
516: $array = array();
517:
518: while ($row = mysqli_fetch_row($cur)) {
519: if ($key !== false) {
520: $array[$row[$key]] = $row;
521: } else {
522: $array[] = $row;
523: }
524: }
525:
526: $this->free_result();
527:
528: return $array;
529: }
530:
531: /**
532: * Возвращает ассоциативный массив результата, ключами которого являются значения поля $key, а значениями - значения поля $value
533: *
534: * @param string $key название поля для ключа результирующего массива
535: * @param string $value названи еполя для значения результирующего массива
536: *
537: * @return array ассоциативнй массив ключ=>значение результата
538: */
539: public function load_row_array($key, $value)
540: {
541: $cur = $this->query();
542:
543: $array = array();
544: while ($row = mysqli_fetch_object($cur)) {
545: $array[$row->$key] = $row->$value;
546: }
547:
548: $this->free_result();
549:
550: return $array;
551: }
552:
553: /**
554: * Вставка записи.
555: * Работает с объектами, свойства которых являются названиями поле в базе, а значения свойств - значениями полей
556: * Работает ТОЛЬКО через joosDatabaseMysqli::instance()->insert_object
557: *
558: * @param string $table название таблицы, можно с преффиксом #__
559: * @param stdClass $object объект с заполненными свойствами
560: * @param string $keyName название ключевого автоинскриментного поля таблицы
561: *
562: * @return int идентификатор вставленной записи, истину или ложь если операция провалилась
563: */
564: public function insert_object($table, $object, $keyName = null)
565: {
566: $ignore = isset($object->__ignore) ? ' IGNORE ' : '';
567: unset($object->__ignore);
568:
569: $fmtsql = "INSERT $ignore INTO $table ( %s ) VALUES ( %s ) ";
570:
571: $fields = array();
572: $values = array();
573: foreach (get_object_vars($object) as $k => $v) {
574: if (is_array($v) or is_object($v) or $v === null) {
575: continue;
576: }
577: if ($k[0] == '_') { // внешние поля
578: continue;
579: }
580:
581: $fields[] = $this->get_name_quote($k);
582: $values[] = $this->get_quoted($v);
583: }
584:
585: $this->set_query(sprintf($fmtsql, implode(",", $fields), implode(",", $values)));
586:
587: if (!$this->query()) {
588: return false;
589: }
590:
591: // TODO тут был прямой вызов
592: $id = $this->insert_id();
593:
594: if ($keyName && $id) {
595: $object->$keyName = $id;
596: }
597:
598: return ($id > 0) ? $id : true;
599: }
600:
601: public function insert_array($table, $object, array $values_array)
602: {
603: $ignore = isset($object->__ignore) ? ' IGNORE ' : '';
604: unset($object->__ignore);
605:
606: $fmtsql = "INSERT $ignore INTO $table ( %s ) VALUES %s ";
607:
608: $fields = array();
609: $n = 0;
610: $values = array();
611: foreach (get_object_vars($object) as $k => $v) {
612:
613: if (is_array($v) or is_object($v)) {
614: continue;
615: }
616:
617: if ($k[0] == '_') {
618: continue;
619: }
620:
621: $fields[] = $this->get_name_quote($k);
622: foreach ($values_array as $key => $value) {
623: $values[$key][$n] = (isset($value[$k]) && $v == null) ? $this->get_quoted($value[$k]) : (($v != null) ? $this->get_quoted($v) : 'NULL');
624: ++$n;
625: }
626: }
627:
628: array_walk($values, function(&$d) {
629: $d = ' (' . implode(",", $d) . ') ';
630: });
631:
632: $this->set_query(sprintf($fmtsql, implode(",", $fields), implode(",", $values)));
633:
634: return $this->query() ? true : false;
635: }
636:
637: /**
638: * Обновление записи.
639: * Работает с объектами, свойства которых являются названиями поле в базе, а значения свойств - значениями полей
640: *
641: * @param string $table название таблицы, можно с преффиксом #__
642: * @param stdClass $object объект с заполненными свойствами
643: * @param string $key_name название ключевого автоинскриментного поля таблицы
644: * @param bool $update_nulls флаг обновления неопределённых свойств
645: *
646: * @return bool результат обновления данных записи
647: */
648: public function update_object($table, $object, $key_name, $update_nulls = true)
649: {
650: $fmtsql = "UPDATE $table SET %s WHERE %s";
651: $tmp = array();
652: $where = '';
653: foreach (get_object_vars($object) as $k => $v) {
654: if (is_array($v) or is_object($v) or $k[0] == '_') { // internal or NA field
655: continue;
656: }
657: if ($k == $key_name) { // PK not to be updated
658: $where = $key_name . '=' . $this->get_quoted($v);
659: continue;
660: }
661: if ($v === null && !$update_nulls) {
662: continue;
663: }
664: if ($v == '') {
665: $val = "''";
666: } else {
667: $val = $this->get_quoted($v);
668: }
669: $tmp[] = $this->get_name_quote($k) . '=' . $val;
670: }
671: $this->set_query(sprintf($fmtsql, implode(",", $tmp), $where));
672:
673: return (bool) $this->query();
674: }
675:
676: /**
677: * Возвращает ID-номер, сгенерированный для столбца AUTO_INCREMENT предыдущим запросом INSERT
678: * @return int
679: */
680: public function insert_id()
681: {
682: return mysqli_insert_id($this->_resource);
683: }
684:
685: /**
686: * Возвращаем объект с утилитарными функциями работы с базой данных
687: * @return joosDatabaseMysqliUtils
688: */
689: public function get_utils()
690: {
691: return new joosDatabaseMysqliUtils($this);
692: }
693:
694: /**
695: * Преобразование массива в объект
696: *
697: * @param array $array исходный массив ключ=>значение
698: * @param object $obj объект, свойства которого будут заполнены значениями сообтветсвующих ключей массива
699: * @param string $ignore свойства объекта которые следует игнорировать, через пробел ('id title slug')
700: * @param string $prefix префикс полей массива. Например в объекте title, а в массивe blog_title
701: * @param bool $checkSlashes флаг экранизации значений через addslashes
702: *
703: * @return bool результат предразования
704: */
705: public function bind_array_to_object(array $array, &$obj, $ignore = '', $prefix = null, $checkSlashes = false)
706: {
707: $ignore = ' ' . $ignore . ' ';
708: foreach (get_object_vars($obj) as $k => $v) {
709: if (substr($k, 0, 1) != '_') { // закрытые свойства пропускаем
710: if (strpos($ignore, ' ' . $k . ' ') === false) {
711: if ($prefix) {
712: $ak = $prefix . $k;
713: } else {
714: $ak = $k;
715: }
716: if (isset($array[$ak])) {
717: $obj->$k = $checkSlashes ? addslashes($array[$ak]) : $array[$ak];
718: }
719: }
720: }
721: }
722:
723: return true;
724: }
725:
726: /**
727: * Быстрое статическое создание модели и доступ к её медотам и свойствам
728: *
729: * @tutorial joosDatabaseMysqli::model('modelUsers')->count()
730: * @tutorial joosDatabaseMysqli::model('Blog')->get_list( array('where'=>'sate=1') )
731: * @tutorial joosDatabaseMysqli::model('Blog')->save( $_POST )
732: *
733: * @param string $model_name
734: *
735: * @return joosModel объект выбранной модели
736: */
737: public static function model($model_name)
738: {
739: return new $model_name;
740: }
741:
742: /**
743: * Очистка буфера mysqli
744: */
745: private function free_result()
746: {
747: ;
748:
749: !JDEBUG ? mysqli_free_result($this->_cursor) : null;
750: }
751:
752: }
753:
754: /**
755: * Библиотека утилитарных функций работы с базой данных через расширение mysqli
756: *
757: * @version 1.0
758: * @package Core\Libraries
759: * @subpackage Libraries
760: * @subpackage joosDatabase
761: * @author Joostina Team <info@joostina.ru>
762: * @copyright (C) 2007-2012 Joostina Team
763: * @license MIT License http://www.opensource.org/licenses/mit-license.php
764: * Информация об авторах и лицензиях стороннего кода в составе Joostina CMS: docs/copyrights
765: *
766: * */
767: class joosDatabaseMysqliUtils extends joosDatabaseMysqli implements joosInterfaceDatabaseUtils
768: {
769: /**
770: *
771: * @var
772: */
773: private $_db;
774:
775: /**
776: * Объект работы с базой данных
777: *
778: * @param joosDatabaseMysqli|string $db
779: */
780: public function __construct(joosDatabaseMysqli $db)
781: {
782: $this->_db = $db;
783: }
784:
785: /**
786: * Возвращает строку, представляющую номер версии сервера
787: * @return string строка версии сервера
788: */
789: public function get_version()
790: {
791: return mysqli_get_server_info($this->_db->_resource);
792: }
793:
794: /**
795: * Возвращает список таблиц активной базы
796: *
797: * @param bool $only_joostina флаг позволяющий оставить в результирующем наборе только таблицы текущего сайта
798: *
799: * @return array массив таблиц текущей базы данных
800: */
801: public function get_table_list($only_joostina = true)
802: {
803: $only_joostina = $only_joostina ? " LIKE '" . $this->_db->_table_prefix . "%' " : '';
804:
805: return $this->_db->set_query('SHOW TABLES ' . $only_joostina)->load_result_array();
806: }
807:
808: /**
809: * Возвращает ассоциативный массив структур таблиц
810: *
811: * @param array $tables массив таблиц структуру которых необходимо получить
812: *
813: * @return array ассоциативный массив, ключами которогоявляются названия таблиц, а значениями - самаструктура этихтаблиц
814: */
815: public function get_table_create(array $tables)
816: {
817: $result = array();
818:
819: foreach ($tables as $tblval) {
820: $rows = $this->_db->set_query('SHOW CREATE table ' . $this->_db->get_escaped($tblval))->load_row_list();
821: foreach ($rows as $row) {
822: $result[$tblval] = $row[1];
823: }
824: }
825:
826: return $result;
827: }
828:
829: /**
830: * Возвращает ассоциативный массив свойств столбцов таблицы
831: *
832: * @param string $tables название таблицы
833: *
834: * @return array ассоциативный массив, ключами которого являются названия полей, а значения - свойства полей
835: */
836: public function get_table_fields($tables)
837: {
838: $fields = $this->_db->set_query('SHOW FIELDS FROM ' . $tables)->load_object_list();
839:
840: $result = array();
841: foreach ($fields as $field) {
842: $result[$field->Field] = $field->Type;
843: }
844:
845: return $result;
846: }
847:
848: }
849: