Схема данных в WordPress достаточно гибкая, и после появления произвольных типов записей в ядре WordPress, практически любые данные можно каким-либо образом разместить в таблице wp_posts
и работать с ними стандартными средствами.
Но на практике это не всегда оптимально, и порой возникает необходимость создать собственную таблицу в базе данных WordPress. При таком подходе есть очевидные минусы:
- Нет возможности использовать WP_Query для доступа к данным
- Данные в произвольной таблице не используют кэширование объектов
- Пользовательский интерфейс для работы с данными приходится строить самостоятельно
- Роли, привилегии, шаблоны вывода и форматы постоянных ссылок также приходится брать на себя
- Нет возможности использовать таксономию WordPress для группировки данных
Есть конечно один плюс, который часто оправдывает использование собственных таблиц в WordPress: мы самостоятельно определяем структуру, типы данных и индексы. А при работе с большим количеством данных, это может значительно повлиять на скорость сайта.
Функция dbDelta()
Встроенная функция dbDelta()
позволяет создавать таблицы в базе данных WordPress, и вносить изменения в их структуру. Но перед созданием новой таблицы, необходимо определиться с ее наименованием и кодировкой.
Название новой таблицы должно иметь тот же префикс, который используется ядром WordPress (по умолчанию wp_
) и дополнительный префикс для нашего плагина или проекта, например: wp_my_table_name
. Префикс wp_
можно получить с помощью метода get_blog_prefix()
глобального объекта $wpdb
, а с помощью свойств charset
и collate
можно определить используемую кодировку:
global $wpdb; $table_name = $wpdb->get_blog_prefix() . 'my_products'; $charset_collate = "DEFAULT CHARACTER SET {$wpdb->charset} COLLATE {$wpdb->collate}";
Далее с помощью функции dbDelta()
мы можем создать новую таблицу:
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); $sql = "CREATE TABLE {$table_name} ( id int(11) unsigned NOT NULL auto_increment, name varchar(255) NOT NULL default '', price int(11) unsigned NOT NULL default '0', PRIMARY KEY (id), KEY price (price) ) {$charset_collate};"; // Создать таблицу. dbDelta( $sql );
Функция dbDelta()
отличается от простого MySQL запроса в базу данных WordPress. Она разбивает структуру таблицы на части и сравнивает ее с той таблицей, которая уже существует. Это позволяет dbDelta()
вносить изменения в структуру таблицы, без необходимости удалять и снова создавать таблицу.
Например, чтобы добавить новую колонку в нашу таблицу, мы можем изменить наш основной запрос CREATE TABLE
следующим образом:
$sql = "CREATE TABLE {$table_name} ( id int(11) unsigned NOT NULL auto_increment, name varchar(255) NOT NULL default '', price int(11) unsigned NOT NULL default '0', color varchar(255) NOT NULL default '', PRIMARY KEY (id), KEY price (price) ) {$charset_collate};";
При этом разбив запрос на части, функция dbDelta()
поймет, что добавилась новая колонка color
, и выполнит соответствующий запрос ALTER TABLE
над существующей таблицей в базе данных WordPress. Для того, чтобы проверить что именно сделала функция dbDelta
мы можем вывести ее результат, например с помощью var_dump()
.
Подобное поведение функции dbDelta()
позволяет ее многократно использовать над одной и той же таблицей, но учтите, что при каждом вызове dbDelta()
она будет опрашивать базу данных для получения структуры требуемой таблицы, поэтому выполнять dbDelta()
при каждой загрузке страницы — это не оптимально.
Версии таблиц и dbDelta()
В нашем плагине мы можем указать текущую версию схемы данных и обновлять ее только тогда, когда действительно изменяем схему собственной таблицы. А при обновлении схемы с помощью dbDelta()
мы можем записывать версию в опции WordPress, чтобы подобное обновление не выполнять более одного раза.
class My_Plugin_Class { public $db_version = 1; function __construct() { add_action( 'init', array( $this, 'init' ) ); } function init() { $installed_db_version = get_option( 'my_db_version', 0 ); if ( version_compare( $this->db_version, $installed_db_version, '>' ) ) $this->upgrade(); } function upgrade() { // здесь вызывается dbDelta() update_option( 'my_db_version', $this->db_version ); } } new My_Plugin_Class;
Таким образом во время события init, наш плагин проверяет установленную версию схемы данных в опциях WordPress.
Если установленная версия отсутствует или меньше чем текущая версия, мы вызываем метод upgrade()
нашего плагина, где можем использовать функцию dbDelta()
для обновления схемы данных, и наконец обновить версию в опциях WordPress.
При таком подходе, после изменения структуры данных в запросе к dbDelta()
нам необходимо лишь увеличить переменную класса $db_version
.
Multisite
В режиме Multisite описанный выше код будет срабатывать для каждого сайта в сети отдельно, т.е. у каждого сайта будет собственная таблица wp_*_my_products
. В большинстве случаев это именно то, что задумал разработчик.
Тем не менее, иногда нам необходимо создать так называемую «глобальную» таблицу, которая будет одной единственной во всей сети. В этом случае нам необходимо внести два небольших изменения:
- Указать 0 в вызове
get_blog_prefix()
, чтобы использовать глобальный префикс базы данных WordPress. - Использовать функции
get_site_option()
иupdate_site_option()
для хранения версии схемы данных только на основном сайте в сети.
Запросы
Как мы упомянули в начале статьи, для получения данных из нашей новой таблицы, мы не можем использовать класс WP_Query
или любые другие вспомогательные функции в WordPress. Запросы необходимо строить вручную и запускать с помощью объекта $wpdb
:
function get_my_product( $product_id ) { global $wpdb; $table_name = $wpdb->get_blog_prefix() . 'my_products'; $product = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table_name} WHERE `id` = %d LIMIT 1;", $product_id ) ); return $product; }
Данная функция вернет продукт с определенным идентификатором из базы данных. Учтите, что при повторном вызове этой же функции с одинаковым идентификатором, она повторно выполнит запрос к базе данных MySQL. Для избежания этого вы можете воспользоваться кэшированием объектов в WordPress.
Отдельно следует обратить внимание на метод prepare()
объекта $wpdb
. Он позволяет указать формат передаваемых переменных в базу данных WordPress и в большинстве случаев защищает от SQL-инъекции.
Подробнее об использовании объекта $wpdb
для выполнения запросов в базу данных WordPress, вы можете узнать в кодексе (англ.)