ГлавнаяРазноеСоздание собственных таблиц в базе данных WordPress

Создание собственных таблиц в базе данных WordPress

Схема данных в 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. В большинстве случаев это именно то, что задумал разработчик.

Тем не менее, иногда нам необходимо создать так называемую «глобальную» таблицу, которая будет одной единственной во всей сети. В этом случае нам необходимо внести два небольших изменения:

  1. Указать 0 в вызове get_blog_prefix(), чтобы использовать глобальный префикс базы данных WordPress.
  2. Использовать функции 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, вы можете узнать в кодексе (англ.)

Подписаться на рассылку

Подписаться → Подпишитесь на бесплатную рассылку журнала WP Magazine и получайте новости, события, подборки тем и плагинов, уроки, советы и многое другое в мире WordPress!

  • chumachkin_m

    Это все прекрасно, но можно ли как-то сделать связь своих таблиц с WP_Query, или есть какой-то абстрактный класс который может сделать обертку на подобии WP_Query к собственным таблицам?

    • К сожалению WP_Query не создан для работы со сторонними таблицами, поэтому связать его с вашими таблицами так просто не получится, тем более, что произвольные таблицы часто создаются с другой структурой.

      Самым надежным вариантом будет создание собственного класса взяв за основу WP_Query. Пример этого есть как в ядре WordPress (WP_Comment_Query, WP_User_Query), так и в сторонних продуктах, например SP_Query в SupportPress.

      Если ваша таблица имеет мета-данные с той же структурой, что и postmeta, usermeta и commentmeta, вы можете воспользоваться стандартными функциями для доступа к мета-данным get_metadata() и т.д., а также класс WP_Meta_Query. Для этого ваша таблица с мета-данными должна быть объявлена в объекте $wpdb и доступна функции _get_meta_table(). Подробнее в wp-includes/meta.php.

  • versusbassz

    А есть у кого-нибудь опыт работы с кастомными таблицами через плагин «Pods»? Жизнеспобен этот способ, как считаете? подробнее здесь: http://pods.io/docs/learn/what-are-advanced-content-types/

  • Виктор

    Стоить отметить, чтобы не использовали в запросе, при объявление ключей, символ (`), иначе при повторном запуске dbDelta() можно получить ошибку, примером «Multiple primary key defined».

    • Еще можно отметить интересную особенность, которую легко пропустить в мануале, это может стоить несколько часов поисков ошибки:
      You must have two spaces between the words PRIMARY KEY and the definition of your primary key.

  • Нет желания рассказать про WP_List_Table()?