База данных

Для работы с базой данных используется класс Database, реализующий взаимодействие с СУБД через расширение PDO. Настройки доступа к базе данных указываются в системных настройках с префиксом db..

Инициализация и подключение к базе данных выполняется на этапе инициализации приложения. Доступ к объекту класса выполняется следующим образом:

$this->db - в конексте модулей, моделей, плагинов и других классов наследующих базовый класс Component (приоритетный)

bff::database() - в глобальном контексте и классах не наследующих Component

Настройки подключения

Параметры подключения указываются в файле системных настроек /config/sys.php в секции "Доступ к базе данных".

'db.type' => 'mysql', # тип базы данных
'db.host' => 'localhost', # локальное подключение (localhost, 127.0.0.1) или внешний IP адрес
'db.port' => '3306', # порт подключения, для mysql порт по умолчанию - 3306
'db.name' => 'name', # название базы данных
'db.user' => 'user', # логин пользователя
'db.pass' => 'pass', # пароль пользователя

Построение SQL запросов

Для выполнения SQL запросов типа SELECT доступны следующие методы класса Database:

select - получение нескольких строк из таблицы

$data = $this->db->select('SELECT id, title FROM ' . TABLE_TEST . ' WHERE id > 10');
/* пример:
array(2) {
    0 => ['id' => 11, 'title' => 'Title A'],
    1 => ['id' => 12, 'title' => 'Title B']
}
*/

select_key - получение нескольких строк из таблицы с группировкой по ключу (столбцу):

$data = $this->db->select_key('SELECT id, title FROM ' . TABLE_TEST . ' WHERE id > 10', 'id');
/* пример группировки данных по столбцу id:
array(2) {
    11 => ['id' => 11, 'title' => 'Title A'],
    12 => ['id' => 12, 'title' => 'Title B']
}
*/

select_one_column - получение нескольких строк с данными одного столбца:

$data = $this->db->select_one_column('SELECT id FROM ' . TABLE_TEST . ' WHERE id > 10', 'id');
/* пример:
array(2) {
    0 => 11,
    1 => 12
}
*/

one_data - получение одного значения:

$title = $this->db->one_data('SELECT title FROM ' . TABLE_TEST . ' WHERE id = :id', [':id'=>11]);
$count = $this->db->one_data('SELECT COUNT(id) FROM ' . TABLE_TEST . ' WHERE id >= :num', [':num'=>11]);

one_array - получение одной строки:

$data = $this->db->one_array('SELECT id, title FROM ' . TABLE_TEST . ' WHERE id = :id', [':id'=>11]);
/* пример:
array(2) {
    id => 11,
    title => Title A
}
*/

Также доступен ряд методов для простых SELECT запросов:

$data = $this->db->select_rows(TABLE_TEST, ['*'], ['id'=>5]);
# итоговый запрос: SELECT * FROM test WHERE id = 5
# используется для получения информации о нескольких записях
$count = $this->db->select_rows_count(TABLE_TEST, ['status'=>['>', 5]]);
# итоговый запрос: SELECT COUNT(*) FROM test WHERE status > 5
# используется для подсчета количества записей
$data = $this->db->select_row(TABLE_TEST, ['*'], ['id'=>5]);
# итоговый запрос: SELECT * FROM test WHERE id = 5
# используется для получения информации об одной записи
$data = $this->db->select_data(TABLE_TEST, 'title', ['id'=>5]);
# итоговый запрос: SELECT title FROM test WHERE id = 5
# используется для получения информации об одном столбце одной записи

И другие: select_rows_key, select_rows_column, ...

Методы используемые для остальных типов запросов:

insert - вставка строки в таблицу, INSERT запросы

$id = $this->db->insert(TABLE_TEST, ['title'=>'Title A', 'status'=>1]);
# итоговый запрос: INSERT INTO test (title, status) VALUES ('Title A', 1)
# результатом будет ID вновь добавленной строки

multiInsert - вставка нескольких строк в таблицу, INSERT запрос множественной вставки

# пример вставки нескольких строк
$this->db->multiInsert(TABLE_TEST, [
    ['title'=>'Title A', 'status'=>1],
    ['title'=>'Title B', 'status'=>2],
]);
# итоговый запрос: INSERT INTO test (title, status) VALUES ('Title A', 1), ('Title B', 2)

update - обновление строки в таблице, UPDATE запросы

$this->db->update(TABLE_TEST, ['title' => 'Title AA'], ['id'=>11]);
# итоговый запрос: UPDATE test SET title = 'Title AA' WHERE id = 11
# в результате данного запроса строка с id=11 обновит значение столбца title на 'Title AA'

$this->db->update(TABLE_TEST, ['status' => 2], ['status' => ['<=',3]]);
# итоговый запрос: UPDATE test SET status = 2 WHERE status <= 3

$this->db->update(TABLE_TEST, ['status' => 2], ['status' => [2,3], 'id' => ['!=', 2]]);
# итоговый запрос: UPDATE test SET status = 2 WHERE status IN(2,3) AND id != 2

$this->db->update(TABLE_TEST, ['status' => 3, 'num = num + 1'], ['title' => ['LIKE', 'example%']]);
# итоговый запрос: UPDATE test SET status = 3 AND num = num + 1 WHERE title LIKE 'example%'

delete - удаление строки из таблицы, DELETE запросы

$this->db->delete(TABLE_TEST, ['id' => 11]);
# итоговый запрос: DELETE FROM test WHERE id = 11
# в результате данного запроса будет удалена строка с id=11

exec - базовый метод выполняющий итоговый запрос при вызове любого из вышеперечисленных методов, используется также при нестандартных запросах, к примеру при запросе DELETE с использованием нескольких таблиц

В каждом из перечисленных выше методов возможна передача данных подставляемых вместо placeholders в строку запроса.

Дополнительно есть методы сокращающие построение отдельных частей запроса:

prepareIN - построение IN() условий

$this->db->select('SELECT id, title FROM ' . TABLE_TEST . '
           WHERE '.$this->db->prepareIN('id', [11,12]));
# итоговый запрос: SELECT id, title FROM test WHERE id IN (11,12)

prepareLimit - построение LIMIT

$this->db->select('SELECT id, title FROM ' . TABLE_TEST . ' 
           WHERE id > :id '.$this->db->prepareLimit(0, 2), [':id'=>10]);
# итоговый запрос: SELECT id, title FROM test WHERE id > 10 LIMIT 0, 2

Рекомендации при формировании текста SQL запросов

Разберем простой пример SELECT запроса:

'SELECT COUNT(S.id)
 FROM ' . TABLE_SHOPS . ' S
     LEFT JOIN ' . TABLE_USERS . ' U ON S.id = U.shop_id
 WHERE S.id = :id AND S.moderated = 0
 GROUP BY S.id'

Рекомендуем соблюдать следующие требования при написании запросов:

  • при объявлении строки запроса обязательно использование одинарных кавычек
  • все ключевые слова пишем большими буквами
  • используем переносы строк для удобства чтения запроса
  • соблюдаем ширину запроса, в случае если список получаемых столбцов уходит за область видимости - переносим на следующую строку
  • параметры подставляем через placehoders, в примере :id, кроме тех, которые можно указать явно (в примере moderated = 0)
  • избегаем использование кавычек вида `` , в случае когда столбец пересекается с зарезервированными переименовываем.
  • сокращение для название таблиц также называем большой буквой, если есть пересечение, двумя и более большими буквами. Сокращение строим на основе название сущности хранимой в данной таблице, либо на основе первых букв слов дефайна (кроме слова TABLE).

Модель

Модель реализуется посредством класса Model и является неотъемлемой составляющей модуля приложения, объединяя в себе все запросы к базе данных, доступные через вызовы её методов.

В модуле вызов методов модели выполняется через $this->model-> где доступ к объекту базы данных доступен посредством $this->db->

В классе модели есть удобный метод prepareFilter, задача которого - формирование WHERE с placeholder'ами и подстановка префикса таблицы (сокращенного названия), если необходимо.

Пример его использования:

<?php
$filter = $this->prepareFilter(array(
    'moderated' => 1,
    ':status' => array('(A.status >= :min OR A.status <= :max)', ':min'=>1, ':max'=>3),
    'id' => array(10,20,30),
    'title IS NOT NULL',
), 'A');

// результат:
array(
    'where' => 'WHERE A.moderated = :moderated AND
               (A.status >= :min OR status <= :max) AND
                A.id IN (10,20,30),
                A.title IS NOT NULL',
    'bind' => array(
        ':moderated' => 1,
        ':min' => 1,
        ':max' => 3,
    ),
);

Именование методов модели всегда выполняется по следующей логике:
{название сущности модуля}{название действия}

Примеры:

itemSave - добавление/ редактирование записи

itemData - получение данных записи

itemsList - получение списка записей

itemsSave - сохранение данных нескольких записей

Именование таблиц и структура

Все таблицы объявляются в виде define в формате TABLE_{название модуля}_{название сущности} либо TABLE_{название сущности}.

Первый вариант используется в случаях если в модуле используется более одной ключевой сущности, сущности типа категории, теги ключевыми не считаются.

Объявления данных define констант допустимо в файле модели соответствующего модуля приложения, в начале файла.

При объявлении обязательно использовать общий префикс DB_PREFIX. Пример объявления можно увидеть в файле /config/db.tables.php.

Примеры именования таблиц:

TABLE_USERS - таблица пользователей

TABLE_SHOPS - таблица магазинов

TABLE_BLOG_CATEGORIES - таблица категорий блога (модуль blog)

Примеры таблиц, со связью один ко многим:

TABLE_BBS_ITEMS_IMAGES - таблица изображений привязанных к объявлению (модуль bbs)

TABLE_BBS_ITEMS_COMMENTS - таблица комментариев к объявлению (модуль bbs)

TABLE_BLOG_POSTS_TAGS - таблица связи поста с тегами (модуль blog)

TABLE_SHOPS_IN_CATEGORIES - связь магазинов с категориями

Таблицы с локализируемыми данными всегда заканчиваются на _LANG, примеры:

TABLE_PAGES - таблица статических страниц

TABLE_PAGES_LANG - таблица локализируемых данных статических страниц

TABLE_BLOG_CATEGORIES - таблица категорий блога

TABLE_BLOG_CATEGORIES_LANG - таблица локализируемых данных категорий

Структура таблицы

Тип таблиц MySQL всегда InnoDB, кроме случаев когда необходимо использовать FULLTEXT индексы, либо есть другие аргументы в пользу MyISAM.

Для случаев использования внешних ключей в выгрузке необходимо предусмотреть вариант выполнения корректного последовательного удаления/создания таблиц с учетом внешних ключей, к примеру использовать SET FOREIGN_KEY_CHECKS=0; и SET FOREIGN_KEY_CHECKS=1;.

Также обязательно комментирование таблицы общим комментарием - какая сущность в ней хранится, плюс комментирование отдельных столбцов, если их назначение непонятно из названия.

Типы СУБД

На текущий момент поддерживается работа только с MySQL (pdo_mysql)

В приложении все SQL запросы, использующие специфичные конструкции конкректной СУБД помечаются комментарием. Например конструкции GROUP_CONCAT, INSERT ... ON DUPLICATE KEY UPDATE специфичны только для MySQL, в таком случае помечаем запрос комментарием # MySQL only.

Данная метка в дальнейшем поможет выполнить возможный переход на другую СУБД.

Миграции

В основе миграций структуры базы данных лежит библиотека Phinx версии 0.8.x, документацию под данному инструменту и написанию миграций можно найти здесь.

Миграции приложения

Создание миграции доступно из интерфейса в админ-панели в разделе "Настройки сайта / Обновления / Миграции". Предварительно необходимо включить debug-режим.

В случае успешного создания будет создан файл миграции в директории /files/migrations/*.php. В нем вы найдете функцию migrate в которой и следует писать код выполняющий модификацию структуры базы данных, функция rollback отвечает за код выполняющий откат структуры БД.

Запуск миграции выполняется в момент установки обновления или же вручную в том же разделе.

Также доступна возможность запуска миграции в консоли, для этого выполните следующую команду:

php bffc migrations/migrate -t 2.4.0

Версию миграции при этом измените на требуемую.

Миграции дополнений

Создание миграции доступно из интерфейса в админ-панели в настройках плагина, во вкладке "Разработчику / Миграции".

В случае успешного создания будет создан файл миграции в директории плагина
/plugins/название плагина/migrations/*.php

Запуск миграции выполняется в момент установки плагина или же вручную в том же разделе. Также доступна возможность запуска миграции в консоли, для этого выполните следующую команду

php bffc migrations/migrate -x plugin/name -t 2.4.0

Где измените name на название директории плагина, а номер версии на требуемую версию миграции. Для темы используется следующая команда:

php bffc migrations/migrate -x theme/name -t 2.4.0

Доступ к объекту дополнения в классе миграции доступен через $this->extension