1: <?php defined('_JOOS_CORE') or exit();
2:
3: /**
4: * Библиотека работы с базой данных MySQL через PDO
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 joosDatabasePDO implements joosInterfaceDatabase
18: {
19: /**
20: * @var joosDatabasePDO Объект работы с базой данных
21: */
22: private static $instance = NULL;
23:
24: /**
25: * @var PDO Объект соединения с базой данных
26: */
27: protected $_connection = NULL;
28:
29: /**
30: * @var string Префикс таблиц базы данных
31: */
32: private $_table_prefix = NULL;
33:
34: /**
35: * @var string Что именно считается префиксом
36: */
37: private $_prefix_key = '#__';
38:
39: /**
40: * @var string Строка, хранящая последний установленный запрос
41: */
42: private $_sql = NULL;
43:
44: /**
45: * @var PDOStatement Последний использованный объект запроса
46: */
47: private $_statement = NULL;
48:
49: /**
50: * @var array Массив параметров для привязки
51: */
52: private $_params = array();
53:
54: /**
55: * Закрытый конструктор для соединений с базой данных. В случае отсутствия соединения
56: * прекращает работу сайта.
57: *
58: * @param string $host Хост базы
59: * @param string $user Имя пользователя
60: * @param string $password Пароль
61: * @param string $db Имя базы
62: * @param string $charset Кодировка базы
63: * @param string $prefix Префикс таблиц
64: */
65: protected function __construct($host, $user, $password, $db, $charset = 'utf8', $prefix = 'jos_')
66: {
67: //а существует ли расширение вообще
68: if (!extension_loaded('PDO') || !extension_loaded('pdo_mysql')) {
69:
70: $this->offline();
71: }
72:
73: //пытаемся соединиться
74: try {
75:
76: $connection = new PDO('mysql:host=' . $host . ';dbname=' . $db . ';charset=' . $charset, $user, $password);
77: $this->_connection = $connection;
78: $this->_table_prefix = $prefix;
79:
80: } catch (Exception $e) {
81:
82: $this->offline();
83: }
84:
85: //указание charset в строке DSN игнорируется, поэтому указываем так
86: $this->set_query('SET NAMES ' . $charset)->query();
87:
88: $this->set_profiling();
89: }
90:
91: /**
92: * Любая ошибка соединений с базой это повод выключить сайт.
93: */
94: private function offline()
95: {
96: include JPATH_BASE . '/app/templates/system/offline.php';
97: exit();
98: }
99:
100: /**
101: * Простой синглетон для единого коннекта к базе данных
102: *
103: * @return joosDatabasePDO Объект соединений с базой
104: */
105: public static function instance()
106: {
107: //объект создается однажды
108: if (self::$instance === NULL) {
109:
110: $db = joosConfig::get('db');
111: joosDatabasePDO::$instance = new joosDatabasePDO($db['host'], $db['user'], $db['password'], $db['name'], $db['charset'], $db['prefix']);
112: }
113:
114: return joosDatabasePDO::$instance;
115: }
116:
117: /**
118: * При включенной отладке необходимо профилирование запросов
119: */
120: private function set_profiling()
121: {
122: if (JDEBUG) {
123:
124: $this->set_query('set profiling=1')->query();
125: $this->set_query('set profiling_history_size=100')->query();
126: }
127: }
128:
129: /**
130: * Пока что метод клонирование закрываем, хз зачем он вообще нужен
131: */
132: public function __clone()
133: {
134: }
135:
136: /**
137: * Метод обрамления служебных названий кавычками
138: *
139: * @param string $s Входная строка
140: * @return string Заквотированная строка
141: */
142: public function name_quote($s)
143: {
144: return '`' . $s . '`';
145: }
146:
147: /**
148: * Получение нулевого значения времени для использования по умолчанию в sql запросах
149: *
150: * @return string строка определяющая нулевое значение времени для использования в базе
151: */
152: public function get_null_date()
153: {
154: return '0000-00-00 00:00:00';
155: }
156:
157: /**
158: * Установка запроса для последующего исполнения
159: *
160: * @param string $sql SQL-запрос
161: * @param array $params Массив параметров для замены вида :name => $value
162: * @return joosDatabasePDO
163: */
164: public function set_query($sql, $params = array())
165: {
166: $this->_sql = str_replace($this->_prefix_key, $this->_table_prefix, $sql);
167: $this->_params = $params;
168:
169: return $this;
170: }
171:
172: /**
173: * Исполнение запроса с указанными параметрами. Если произошла ошибка - выбрасывается
174: * исключение и работа прекращается.
175: *
176: * @return PDOStatement В случае успеха возвращается объект запроса
177: */
178: public function query()
179: {
180: //установка запроса и привязка параметров
181: $this->_statement = $this->_connection->prepare($this->_sql);
182: $this->bind_params();
183:
184: //исполнение и обработка ошибок
185: $this->_statement->execute();
186: if ('00000' != $this->_statement->errorCode()) {
187:
188: //тут хранится информация об ошибке
189: $error_info = $this->_statement->errorInfo();
190:
191: throw new joosDatabaseException('Ошибка выполнения SQL #:error_num <br /> :error_message.<br /><br /> Ошибка в SQL: :sql', array(':error_num' => $this->_statement->errorCode(), ':error_message' => $error_info[2], ':sql' => $this->_sql));
192: }
193:
194: return $this->_statement;
195: }
196:
197: /**
198: * Хитрая привязка параметров для исполнения запроса
199: *
200: * @param array $params Параметры
201: */
202: private function bind_params()
203: {
204: foreach ($this->_params as $param_name => $param_value) {
205:
206: //эта сволочь привязывается к переменной, а в цикле она всегда будет привязана
207: //к последнему значению переменной, поэтому такой изврат с новой переменной
208: $tmp = 'aaa' . rand(1, 1000) . rand(1, 1000) . rand(1, 1000) . rand(1, 1000) . rand(1, 1000);
209: $$tmp = $param_value;
210:
211: $this->_statement->bindParam(':' . $param_name, $$tmp);
212: }
213: }
214:
215: /**
216: * Возвращает число строк, измененныхп при последнем запросе DELETE, UPDATE или INSERT
217: */
218: public function get_affected_rows()
219: {
220: return $this->_statement->rowCount();
221: }
222:
223: /**
224: * Возвращает первый результат запроса
225: *
226: * @return string Значение поля
227: */
228: public function load_result()
229: {
230: //результат исполнения запроса не проверяем, так как если что - оно само упадет
231: $this->query();
232:
233: //получаем массив, пронумерованный с нуля
234: $result = $this->_statement->fetch(PDO::FETCH_NUM);
235:
236: $this->free_result();
237:
238: //и пытаемся вернуть нулевой элемент
239: return isset($result[0]) ? $result[0] : NULL;
240: }
241:
242: /**
243: * Получение одного столбца запроса (по умолчанию нулевого) как обычного
244: * массива со значениями полей.
245: *
246: * @param int $column Индекс поля в запросе (с нуля)
247: * @return array Массив значений
248: */
249: public function load_result_array($column = 0)
250: {
251: //исполняем запрос
252: $this->query();
253:
254: $result = array();
255: while ($row = $this->_statement->fetch(PDO::FETCH_NUM)) {
256:
257: $result[] = isset($row[$column]) ? $row[$column] : NULL;
258: }
259:
260: $this->free_result();
261:
262: return $result;
263: }
264:
265: /**
266: * Возвращает массив строк выборки, где каждая строка это ассоциативный
267: * массив с данными столбцов
268: *
269: * @param string $key Ключ выборки
270: */
271: public function load_assoc_list($key = '')
272: {
273: $this->query();
274:
275: $result = array();
276: while ($row = $this->_statement->fetch(PDO::FETCH_ASSOC)) {
277:
278: if ($key) {
279:
280: $result[$row[$key]] = $row;
281: } else {
282:
283: $result[] = $row;
284: }
285: }
286:
287: $this->free_result();
288:
289: return $result;
290: }
291:
292: /**
293: * Загрузка полей выборки в указанный объект.
294: *
295: * @param object $object Куда сохраняем данные
296: */
297: public function load_object(& $object)
298: {
299: $this->query();
300:
301: if ($object !== NULL) {
302:
303: $array = $this->_statement->fetch(PDO::FETCH_ASSOC);
304: $this->bind_array_to_object($array, $object, null, null, false);
305: } else {
306:
307: $object = $this->_statement->fetch(PDO::FETCH_OBJ);
308: }
309:
310: $this->free_result();
311: }
312:
313: /**
314: * Загрузка строк из таблицы как массива объектов stdClass
315: *
316: * @param string $key Ключ массива (имя столбца)
317: * @return array Результирующий массив
318: */
319: public function load_object_list($key = '')
320: {
321: $this->query();
322:
323: $result = array();
324: while ($row = $this->_statement->fetch(PDO::FETCH_OBJ)) {
325:
326: if ($key) {
327:
328: $result[$row->$key] = $row;
329: } else {
330:
331: $result[] = $row;
332: }
333: }
334:
335: $this->free_result();
336:
337: return $result;
338: }
339:
340: /**
341: * Вставка в базу готового ассоциативного массива с данными
342: *
343: * @param string $table Имя таблицы
344: * @param object $object Объект модели с полями
345: * @param array $values_array Массив массивов значений
346: */
347: public function insert_array($table, $object, array $values_array)
348: {
349: $ignore = isset($object->__ignore) ? ' IGNORE ' : '';
350: unset($object->__ignore);
351:
352: $fmtsql = "INSERT {$ignore} INTO {$table} ( %s ) VALUES %s ";
353:
354: //храним поля и значения
355: $fields = array();
356: $values = array();
357: $values_size = 0;
358:
359: //получаем все простые публичные поля модели (не начинающиеся с _)
360: foreach (get_object_vars($object) as $field_name => $model_field_value) {
361:
362: if (is_array($model_field_value) or is_object($model_field_value)) {
363: continue;
364: }
365:
366: if ($field_name[0] == '_') {
367: continue;
368: }
369:
370: //имена полей базы
371: $fields[] = $this->name_quote($field_name);
372:
373: //цикл по входному массиву массивов
374: foreach ($values_array as $value_index => $one_array) {
375:
376: //если указано значение в самой модели - то оно важнее данных в массиве
377: if ($model_field_value !== NULL) {
378:
379: $result = $model_field_value;
380: } else {
381:
382: //если поле есть во входном массиве - используем его
383: if (isset($one_array[$field_name])) {
384:
385: $result = $one_array[$field_name];
386: }
387: //иначе NULL
388: else {
389:
390: $result = 'NULL';
391: }
392: }
393:
394: //кладем в массив, который потом развернем в SQL-запрос
395: $values[$field_name][] = $result;
396: }
397:
398: //число элементов в подчиненном массиве
399: $values_size++;
400: }
401:
402: //формируем заполнение полей для столбца VALUE()
403: $params = array();
404: for ($i = 0; $i < $values_size; $i++) {
405:
406: //формируем кортеж одной записи
407: $row = array();
408: foreach ($values as $field => $one_array) {
409:
410: //$row[] = $one_array[$i];
411: $param_name = $field . '_' . $i;
412: $params[$param_name] = $one_array[$i];
413:
414: $row[] = ':' . $param_name;
415: }
416:
417: $row_strings[] = '(' . implode(', ', $row) . ')';
418: }
419:
420: //готовим запрос
421: $query = sprintf($fmtsql, implode(",", $fields), implode(",", $row_strings));
422: //и исполняем
423: $this->set_query($query, $params);
424: $this->query();
425:
426: $this->free_result();
427:
428: return true;
429: }
430:
431: /**
432: * Возвращает значение ID, сгенерированное для столбца AUTO_INCREMENT в
433: * предыдущем запросе INSERT
434: *
435: * @return int Значение последнего ID
436: */
437: public function insert_id()
438: {
439: return $this->_connection->lastInsertId();
440: }
441:
442: /**
443: * Возвращаем объект с утилитарными функциями работы с базой данных
444: * @return joosDatabaseUtilsPDO Объект функций
445: */
446: public function get_utils()
447: {
448: return new joosDatabaseUtilsPDO($this);
449: }
450:
451: /**
452: * Преобразование массива в объект (взял без изменения из старого класса)
453: *
454: * @param array $array исходный массив ключ=>значение
455: * @param object $obj объект, свойства которого будут заполнены значениями сообтветсвующих ключей массива
456: * @param string $ignore свойства объекта которые следует игнорировать, через пробел ('id title slug')
457: * @param string $prefix префикс полей массива. Например в объекте title, а в массивe blog_title
458: * @param bool $checkSlashes флаг экранизации значений через addslashes
459: *
460: * @return bool результат предразования
461: */
462: public function bind_array_to_object(array $array, &$obj, $ignore = '', $prefix = null, $checkSlashes = false)
463: {
464: $ignore = ' ' . $ignore . ' ';
465: foreach (get_object_vars($obj) as $k => $v) {
466: if (substr($k, 0, 1) != '_') { // закрытые свойства пропускаем
467: if (strpos($ignore, ' ' . $k . ' ') === false) {
468: if ($prefix) {
469: $ak = $prefix . $k;
470: } else {
471: $ak = $k;
472: }
473: if (isset($array[$ak])) {
474: $obj->$k = $checkSlashes ? addslashes($array[$ak]) : $array[$ak];
475: }
476: }
477: }
478: }
479:
480: return true;
481: }
482:
483: /**
484: * Освобождаем ресурсы соединения
485: */
486: private function free_result()
487: {
488: if (!JDEBUG) {
489:
490: $this->_statement->closeCursor();
491: $this->_statement = NULL;
492: }
493: }
494:
495: /**
496: * Быстрое статическое создание модели и доступ к её медотам и свойствам
497: *
498: * @tutorial joosDatabase::models('modelUsers')->count()
499: * @tutorial joosDatabase::models('Blog')->get_list( array('where'=>'sate=1') )
500: * @tutorial joosDatabase::models('Blog')->save( $_POST )
501: *
502: * @param string $model_name
503: *
504: * @return joosModel объект выбранной модели
505: */
506: public static function models($model_name)
507: {
508: return new $model_name;
509: }
510: }
511:
512: /**
513: * Библиотека утилитарных функций работы с базой данных через расширение PDO
514: *
515: * @version 1.0
516: * @package Core\Libraries
517: * @subpackage Libraries
518: * @subpackage joosDatabase
519: * @author Joostina Team <info@joostina.ru>
520: * @copyright (C) 2007-2012 Joostina Team
521: * @license MIT License http://www.opensource.org/licenses/mit-license.php
522: * Информация об авторах и лицензиях стороннего кода в составе Joostina CMS: docs/copyrights
523: *
524: * */
525: class joosDatabaseUtilsPDO extends joosDatabasePDO implements joosInterfaceDatabaseUtils
526: {
527: /**
528: * @var joosDatabasePDO Объект базы данных
529: */
530: private $_db;
531:
532: /**
533: * @param joosDatabase $db Уже существующий объект работы с базой данных
534: */
535: public function __construct(joosDatabasePDO $db)
536: {
537: $this->_db = $db;
538: }
539:
540: /**
541: * Возвращает список таблиц активной базы
542: *
543: * @param bool $only_joostina флаг позволяющий оставить в результирующем наборе только таблицы текущего сайта
544: * @return array массив таблиц текущей базы данных
545: */
546: public function get_table_list($only_joostina = true)
547: {
548: $only_joostina = $only_joostina ? " LIKE '" . $this->_db->_table_prefix . "%' " : '';
549:
550: return $this->_db->set_query('SHOW TABLES ' . $only_joostina)->load_result_array();
551: }
552:
553: /**
554: * Возвращает ассоциативный массив свойств столбцов таблицы
555: *
556: * @param string $tables название таблицы
557: * @return array ассоциативный массив, ключами которого являются названия полей, а значения - свойства полей
558: */
559: public function get_table_fields($tables)
560: {
561: $fields = $this->_db->set_query('SHOW FIELDS FROM ' . $tables)->load_object_list();
562:
563: $result = array();
564: foreach ($fields as $field) {
565: $result[$field->Field] = $field->Type;
566: }
567:
568: return $result;
569: }
570: }
571: