Работа с базами данных

  • Работа с БД осуществляется через класс DbSimple от DkLab
  • В микроне можно работать с несколькими БД сразу
  • Так же предусмотренно как текстовое логирование запросов в файл, так и вывод в нижную панель при Debug-режиме работы сайта.

Настройка подключения(-ий) к БД

Настройка подключения и работы с БД находятся в файле /core/config/db.php, рассмотрим параметры этого файла подбробнее:

  • $g_config['dbSimple']['databases'] - массив в котором содержатся все подключения к базам данных в формате DSN. DSN (Data Source Name) - это универсальный формат описывающий подключение к источнику данных, по своей сути обычная строка где через спец. разделители указаны все необходимые параметры подключения.
    В нашем примере этот формат выглядит следущим образом:
    mysql://ИМЯ_ПОЛЬЗОВАТЕЛЯ:ПАРОЛЬ@ХОСТ/ИМЯ_БАЗЫ_ДАННЫХ?charset=КОДИРОВКА_БАЗЫ_ДАННЫХ

    Рассмотрим пример:

                    $g_config['dbSimple']['databases'] = array
                    (
                        'db' => array('dsn' => 'mysql://root:@localhost/test_db?charset=UTF8')
                    );
                

    Здесь есть

    • db - имя создаваемого подключения к БД, т.е. после к объекту DbSimple работающему с этим подключением можно будет обращаться так: $g_databases->db
    • mysql://root:@localhost/test_db?charset=UTF8 - само подключение в формате DSN

    Стоит отметить, что часто при разработке сайта, программист работает с локальной копией БД, конечно он мог бы каждый раз править подключение перед заливкой файлов на сервер, однако куда удобнее сделать вот так:

                $g_config['dbSimple']['databases'] = array
                (
                    'db' => array
                            (
                                // DEBUG_MODE - будет true только на локальной копии
                                'dsn' => DEBUG_MODE ?
                                         'mysql://root:@localhost/my_local_db?charset=UTF8' :
                                         'mysql://user:pwd@localhost/real_db?charset=UTF8'
                            )
                );
                

    Что бы работать с разными БД сразу, Вам нужно лишь перечислить их в массиве подключений и дать имя будущему объекту для обращения

                    $g_config['dbSimple']['databases'] = array
                    (
                        'db'         => array('dsn' => 'mysql://root:@localhost/main_db?charset=UTF8'),
                        'dbPayments' => array('dsn' => 'mysql://root:@localhost/payments_db?charset=UTF8'),
                        'dbLogger'   => array('dsn' => 'mysql://root:@localhost/logger_db?charset=UTF8'),
                    );
                

    В приведённом выше примере, я создал 3 подключения к разным БД, обращаться к ним мне нужно будет $g_databases->db, $g_databases->dbPayments, $g_databases->dbLogger соответственно.

  • $g_config['dbSimple']['dbLogFile'] - путь к файлу для записи ошибочных запросов к БД Измените это имя с дефолтного на любое другое для повышения безопастности сайта
  • $g_config['dbSimple']['logDbError'] - Выставьте в false если логирования не требуется В реальности, нет необходимости отключать логгирование даже на продакшен сервере

Работа с моделями

Модель - класс предназначенный для работы с данными объектов системы. Примерами моделей могут служить: класс работы с пользователем, класс работы с новостями, класс работы с партнёрами и пр.

Модели в микроне хранятся в папке /model.
Модели, как и библиотеки, подключаются автоматически если вызываемый класс и называются одинаково, т.е. если у Вас в коде встречается строчка:

$u = new UserModel();
и при этом подключение файла где описана модель UserModel еще не производилось, то будет произведена попытка подключить файл /model/UserModel.php.

По своей сути модель - является отображением информационных объектов системы.
В 99 случаях из 100 модель работает с данными хранящимися в БД (хотя это и не правило)
Это означает, что вот такой код:

            $user = new UserModel(1001); // 1001 - это user_id, уникальный индентификатор пользователя в системе
            $user->pwd_hash = md5($newPassword); // Конечно простой md5 некуда не годится как хеш на пароль, но это всего лишь пример :-)
        
По результату работы был бы индентичен:
            $g_databases->db->select("UPDATE `users` SET `pwd_hash` = ? WHERE `user_id` = 1001", $newPassword);
        
Но если во втором случае мы напрямую работаем с данными, то в первом мы работаем с логической сущностью "пользователь" что идеологически приятнее, делает код менее зависимым, более читаемым зачастую сокращает его, да и кроме того открывает огромное кол-во дополнительных возможностей, как то, создание виртуальных (вычисляемых) полей, прозрачный ввод логирования, кеширования и пр.

Стоит заметить, что зачастую работа с моделями разных объектов носит весьма схожий характер, и складывается из

  • Извлечение данных записи по ключевому полю (например извлечение конкретной новости или статьи)
  • Обновление данных конретной записи (той же новости, статьи, пользователя)
  • Добавление новогй записи

Чтобы упростить создание моделей с подобным функционалом, нами была разработана модель Model описанная в файле /model/Model.php.
Посмотрим как работать с ней:

Пусть у нас уже есть созданное подключение к БД и мы можем обращаться к нему через $g_databases->db, перед нами стоит задача сделать модель при помощи которой мы могли бы извлекать данные для конретного пользователя, изменять их, а так же удалять этого пользователя при необходимости.
Тогда мы создаем файле /model/UserModel.php в котором пишем код:

Пускай для начала у наших пользователей будет только 2 поля: login (логин в системе) и pwd_hash (хеш на пароль)
    class UserModel extends Model
    {
        public function __construct($user_id = NULL, $onlyShow = false)
        {
            global $g_databases;
            parent::__construct($g_databases->db, 'user', 'user_id', $user_id, $onlyShow);
        }

        public function CreateTable()
        {
            $this->db->query("CREATE TABLE IF NOT EXISTS ?#
                             (
                               `user_id` int(11) NOT NULL AUTO_INCREMENT,
                               `login` varchar(128) CHARACTER SET utf8 NOT NULL,
                               `pwd_hash` varchar(32) CHARACTER SET utf8 NOT NULL,
                               PRIMARY KEY (`user_id`)
                             ) ENGINE=MyISAM DEFAULT CHARSET=latin1",
                             $this->table);
        }
    };
    

Теперь мы можем:

  • Извлекать данные о конкретном пользователе:
                    $u = new UserModel(123); // 123 - это user_id
                    echo "Логин пользователя: " . $u->login;
                
  • Изменять данные пользователя:
                    $u = new UserModel(123);
                    $u->login = "NewLogin";
                
  • Добавлять новых пользователей:
                    $u = new UserModel();
                    $u->login = "AnyLogin";
                    $u->pwd_hash = md5("123");
                    echo "USER_ID нового пользователя: " . $u->Flush(); // Если Flush() не вызывать то он автоматом вызовится в деструкторе
                
  • Удалять пользоваля:
                    $u = new UserModel(123);
                    $u->Delete();
                

Как видите наша модель построенна и полностью функциональна.
Если Вам нужны еще поля то просто создайте их в таблице пользователя:

    class UserModel extends Model
    {
        public function __construct($user_id = NULL, $onlyShow = false)
        {
            global $g_databases;
            parent::__construct($g_databases->db, 'user', 'user_id', $user_id, $onlyShow);
        }

        public function CreateTable()
        {
            // Если Ваша таблица уже создана то запрос 2-ой раз не пройдёт, и их нужно либо добавит ручками либо пересоздать таблицу
            $this->db->query("CREATE TABLE IF NOT EXISTS ?#
                             (
                               `user_id` int(11) NOT NULL AUTO_INCREMENT,
                               `login` varchar(128) CHARACTER SET utf8 NOT NULL,
                               `pwd_hash` varchar(32) CHARACTER SET utf8 NOT NULL,
                               `name` varchar(128) CHARACTER SET utf8 NOT NULL,
                               `second_name` varchar(32) CHARACTER SET utf8 NOT NULL,
                               `create_time` int(11) NOT NULL,
                               PRIMARY KEY (`user_id`)
                             ) ENGINE=MyISAM DEFAULT CHARSET=latin1",
                             $this->table);
        }
    };
    

Виртуальные поля в моделе

Предположим перед нами встала задача знать время последнего визита пользователя. В быстром решении мы можем просто добавить поле last_activity типа INT в нашу таблицу пользоватлей, и выставлять в неё значение time() при каждой процедуре авторизации.
В будущем же, когда потребуется еще хранить историю авторизаций, мы можем поступить так:
1) Создадим таблицу user_activities с полями user_id и time 2) Каждый раз при авторизации пользователя мы будет класть новую запись в эту таблицу с текущим time() 3) А для того что бы не менять нигде исходники уже написанно $user->last_activity мы сделаем виртуальное поле при магического метода __get который перехватывает вызов любой незадекларированной переменной в классе:

    class UserModel extends Model
    {
        public function __construct($user_id = NULL, $onlyShow = false)
        {
            global $g_databases;
            parent::__construct($g_databases->db, 'user', 'user_id', $user_id, $onlyShow);
        }

        public function CreateTable()
        {
            // Если Ваша таблица уже создана то запрос 2-ой раз не пройдёт, и их нужно либо добавит ручками либо пересоздать таблицу
            $this->db->query("CREATE TABLE IF NOT EXISTS ?#
                             (
                               `user_id` int(11) NOT NULL AUTO_INCREMENT,
                               `login` varchar(128) CHARACTER SET utf8 NOT NULL,
                               `pwd_hash` varchar(32) CHARACTER SET utf8 NOT NULL,
                               `name` varchar(128) CHARACTER SET utf8 NOT NULL,
                               `second_name` varchar(32) CHARACTER SET utf8 NOT NULL,
                               `create_time` int(11) NOT NULL,
                               PRIMARY KEY (`user_id`)
                             ) ENGINE=MyISAM DEFAULT CHARSET=latin1",
                             $this->table);
        }

        // Фунция перехватывающая вызов несуществующих методов
        public function __get($key)
        {
            swith ($key)
            {
                // Если запрашивается поле last_activity то мы перехватываем это и делаем запрос в таблицу user_activities извлекая последнее (максимальное) время когда юзер посещал нас
                case 'last_activity':
                    $ret = $this->db->selectCell("SELECT MAX(`time`) FROM `user_activities` WHERE `user_id` = ?d", $this->user_id);
                    break;
                // Иначе подключить стандартный ход работы для модели Model
                default:
                    $ret = parent::__get($key);
            };
            return $ret;
        }
    };
    
Интересным моментом также является возможность перехвата записи в поле по средствам метода __set($key, $value).
Вот например при записи в поле $user->last_activity Вы можете перехватить это сабытие через __set() и положить новую запись в таблицу user_activities.

Так же стоит отметить, что имена это обычные строки, потому Вы можете производить поиск в них или вырезать параметры, вот например можно организовать работу так что при вызове атрибута $u->avatar32x32 происходит перехват методом __get, поиск миниатуры и генерация налету если её нет (размером 32х32 пикселя).