Гайд по работе с базой данных WordPress — $wpdb для начинающих
Введение
В этой статье мы с вами научимся работать напрямую с базой данных WordPress используя $wpdb
. Начнем с простого примера для быстрого старта; далее рассмотрим основные методы и свойства; рассмотрим неочевидные, но нужные решения; научимся работать со сторонней БД; а также научимся создавать и удалять таблицы в БД и добавлять их для удобной работы в $wpdb
.
Данную статью следует воспринимать больше как гайд, нежели документацию по $wpdb
, т.к. здесь описаны не все методы и свойства и упор больше сделан на практическое применение.
Цель статьи: помочь разобраться во всех нюансах и научиться работать с $wpdb
.
Для лучшего понимания статьи, нужно знать основы ООП (объектно-ориентированного программирования).
Что такое $wpdb
$wpdb
— это глобальный объект в WordPress, который позволяет взаимодействовать с Базой данных сайта. Если точнее — то это глобальный экземпляр класса wpdb
.
С помощью $wpdb
можно работать как с основной БД сайта, данные которой указаны в wp-config.php
, так и с другими БД, доступными с хоста сайта.
Быстрый старт
Задача: получить 10 записей (posts)
из БД любого типа, в названии которых есть слово «Котик». По-сути, сделаем простой поиск по названию.
В обычной ситуации мы воспользовались-бы функцией get_posts()
или классом WP_Query()
, но для примера выполним эту задачу используя $wpdb
.
Решение: добавим в файл functions.php
активной темы следующий код:
/**
* @param string $need_word
* @param string $post_status
*
* @return array
*/
function feodoraxis_find_posts( $need_word, $post_status = 'publish' ) {
//Объявим глобальную переменную для доступа к ней
global $wpdb;
//Опишем SQL-запрос
$sql = "SELECT * FROM `" . $wpdb->posts . "` WHERE `post_title` LIKE %s AND `post_status` = %s";
//Очистим входящие данные для безопасного запроса с помощью метода prepare
$query = $wpdb->prepare(
$sql,
'%' . $wpdb->esc_like( $need_word ) . '%', //Первый параметр %s. Методом esc_like(), подготовим строку для работы с оператором LIKE
$post_status
);
//Получим результат. Для других запросов (не select), вместо get_results следует использовать query()
$result = $wpdb->get_results( $query );
//Если получили массив, даже пустой - значит запрос отработал корректно, возвращаем результат
if ( is_array( $result ) ) {
return $result;
}
//В случае любой ошибки возвращаем пустой массив
return [];
}
Теперь с помощью этой функции мы можем найти нужную запись по части её названия.
Предостережения
Потенциальные проблемы
При разработке лучше использовать функции и методы специально предназначенные для выборки, записи, обновления и удаления записей (далее CRUD — Create, Read, Update, Delete). Во встроенных методах и функциях реализованы оптимизированные запросы к БД, кеширование, а также хуки для встраивания функционала плагинов и тем. Например, если вы захотите использовать плагин «Redis объектный кеш», то данные, получаемые через функцию get_option()
или get_posts()
будут закешированы. А при использовании $wpdb
этот момент придется реализовывать самостоятельно.
Также при следующем обновлении WordPress, структура БД может измениться. Разработчики ответственно подходят к продукту, поэтому встроенные функции сразу будут адаптированы к новой структуре БД и ваш функционал продолжит работать как и раньше. При использовании $wpdb
, в случае такого обновления, ваш функционал может сломаться. Согласитесь, ситуация плохая, особенно для коммерческих проектов.
Когда можно использовать
Ситуация 1.
Если вам нужно реализовать запрос в БД, который не могут дать стандартные функции. Например, вы хотите вывести самую высокую и самую низкую стоимость товаров в каталоге. Перебирать все позиции просто глупо и неэффективно, когда можно написать один запрос. В этом случае отключите автообновления WordPress на боевом сервере, чтобы избежать ситуации, когда при обновлении изменилась БД. Сам дистрибутив в этом случае следует обновлять самостоятельно и регулярно, с предварительным созданием бекапов.
О создании бекапов и переносе WordPress можете почитать в статье «Перенос сайта WordPress на другой домен«
Ситуация 2.
Если потребовалось добавить свои таблицы в БД. В этом случае у вас просто нет других вариантов, т.к стандартные функции не умеют работать со сторонними таблицами.
Основные свойства
Таблицы $wpdb
В быстром старте для указания таблицы wp_posts
, я использовал свойство $wpdb->posts
. Аналогично можно обращаться ко всем стандартным таблицам WordPress.
Например: $wpdb->posts
, $wpdb->postsmeta
, $wpdb->options
и так далее.
wp_
в данном случае — префикс названия таблицы; posts
— название таблицы без префикса.
В разных БД для WordPress могут быть разные префиксы, поэтому для универсальности лучше вызывать название таблицы используя одноименные с таблицами БД, свойства класса wpdb
.
Есть альтернативный способ обращения к таблице БД, с учётом префикса: можно использовать свойство $wpdb->prefix
. Такой способ подойдет, если вам нужно обратиться к таблице, которую создал, например, плагин.
Пример со стандартной таблицей posts: $wpdb->prefix . "posts"
.
Пример с таблицей WooCommerce: $wpdb->prefix . "woocommerce_order_items"
Другие свойства
У класса wpdb есть другие полезные свойства. Рассмотрим некоторых из них вкратце:
- show_errors — определяет, нужно показывать ошибки в SQL-запросах или нет. Стандартно включается тогда, когда в конфигурации WordPress константы
WP_DEBUG
иWP_DEBUG_DISPLAY
имеют значение true. - last_error — хранит в себе ошибку, возникшую во время последнего запроса
- num_queries — сколько запросов было сделано
- num_rows — количество строк, полученных при последнем запросе
- rows_affecterd — количество строк, затронутых при последнем запросе
- insert_id — ID, сгенерированный колонкой с AUTO_INCREMENT во время последнего запроса. Обычно используется при запросах INSERT.
- last_query — последний сделанный запрос
- last_result — результат последнего выполненного запроса
- queries — журнал запросов для дебага
- ready — готова-ли БД выполнять запросы
- error — последняя обнаруженная ошибка SQL
Методы
query() — универсальный запрос
С его помощью можно делать любые запросы к активной БД. Исключение — SELECT — для него есть другие методы. Например, wpdb::get_results().
Как и для других методов wpdb, запрос необходимо очистить для защиты от sql-инъекций. Сделать это можно двумя способами: используя метод wpdb::prepare() или функцию esc_sql().
Пример: вставить сразу 3 записи одним запросом (метод wpdb::insert() умеет только одну запись за запрос)
<?php
global $wpdb;
$content = [
[
'post_title' => 'Название записи 1',
'post_content' => 'Контент записи 1',
'post_status' => 'publish',
],
[
'post_title' => 'Название записи 2',
'post_content' => 'Контент записи 2',
'post_status' => 'publish',
],
[
'post_title' => 'Название записи 3',
'post_content' => 'Контент записи 3',
'post_status' => 'draft',
]
];
$values = $inserts = [];
$sql = "INSERT INTO `" . $wpdb->posts . "` (`post_title`, `post_content`, `post_status`) VALUES ";
foreach ( $content as $item ) {
$inserts[] = '(%s, %s, %s)';
$values = array_merge(
[
$item['post_title'],
$item['post_content'],
$item['post_status']
],
$values
);
}
$sql .= implode( ', ', $inserts );
$query = $wpdb->prepare( $sql, $values );
$result = $wpdb->query( $query );
get_results() — получение данных
Работает аналогично методу wpdb::query(), но предназначен только для SELECT-запросов.
Пример: повторим решение из быстрого старта, но разовьём его и научимся искать записи по содержимому контента. Искать при этом будем только по товарам WooCommerce.
/**
* @param string $need_word
* @param string $post_status
*
* @return array
*/
function feodoraxis_find_products( $need_word, $post_status = 'publish' ) {
//Объявим глобальную переменную для доступа к ней
global $wpdb;
/**
* Опишем SQL-запрос.
*
* Т.к. в запросе строкой указываем нужный тип записи без переменных и внешних источников - то конкретно это условие можно не очищать
*/
$sql = "SELECT * FROM `" . $wpdb->posts . "` WHERE `post_title` LIKE %s AND `post_content` LIKE %s AND `post_status` = %s AND `post_type` = 'product'";
//Первый и второй параметр %s. Методом esc_like(), подготовим строку для работы с оператором LIKE
$need_word_prepared = '%' . $wpdb->esc_like( $need_word ) . '%';
//Очистим входящие данные для безопасного запроса с помощью метода prepare
$query = $wpdb->prepare(
$sql,
$need_word_prepared,
$need_word_prepared,
$post_status
);
//Получим результат
$result = $wpdb->get_results( $query );
//Если получили массив, даже пустой - значит запрос отработал корректно, возвращаем результат
if ( is_array( $result ) ) {
return $result;
}
//В случае любой ошибки возвращаем пустой массив
return [];
}
esk_like() — подготовка строки для LIKE
Подготавливает строку для работы с оператором LIKE в SQL-запросе. Он экранирует в строке специальные символы «_» и «%».
Данный метод следует использовать только для метода wpdb::prepare() и функции esc_sql().
Пример:
<?php
$find = "котики спят 70% своей жизни";
$like = "%" . $wpdb->esc_like( $find ) . "%";
$sql = $wpdb->prepare( "SELECT * FROM `" . $wpdb->posts . "` WHERE `post_content` LIKE %s", $like );
insert() — вставка
Позволяет вставить одну строку в таблицу. Входящие данные можно не очищать.
Если нужно вставить сразу несколько строк — то лучше воспользоваться методом wpdb::query().
Агрумуенты метода: wpdb::insert( string $table, array $data, array|null $format = null );
Рассмотрим каждый аргумент:
$table
— название таблицы, в которую мы хотим добавить строку$data
— массив данных, которые мы хотим добавить. Используется форматarray('колонка' => 'данные')
$format
— массив определяющий формат входящих данные аргумента$data
. Для определения формата используются следующие флаги:'%d', '%f', '%s' (integer, float и string соответственно)
Возвращает количество вставленных строк при успехе; а при ошибке возвращает false
.
Чтобы узнать ID
вставленной строки, колонки с AUTO_INCREMENT
, после выполнения метода можно обратиться к свойству wpdb::insert_id
. По нему-же можно определить, удачно отработал последний метод или нет — при неудаче возвращает значение 0
.
Пример использования:
<?php
function feodoraxis_insert_post( string $title, string $content, string $status = 'publish', int $parent_id = 0 ):int {
global $wpdb;
$result = $wpdb->insert(
//Название таблицы
$wpdb->posts,
//Данные, которые мы хотим добавить в таблицу
[
'post_title' => $title,
'post_content' => $content,
'post_status' => $status,
'post_parent' => $parent_id
],
//Формат вводимых данных
[
'%s',
'%s',
'%s',
'%d' //т.к. $parent_id имеет формат integer, то и указываем значение '%d'
],
);
//Если метод вернул не ложь - значит все сработало корректно
if ( $result !== false && $wpdb->insert_id > 0 ) {
return $wpdb->insert_id;
}
//Если метод вернул ложь - значит случилась ошибка. Вернём ноль.
return 0;
}
update() — обновление
Обновляет строки. Входящие данные можно не очищать.
Аргументы метода: wpdb::update( string $table, array $data, array $where, array|string|null $format = null, array|string|null $where_format = null )
Рассмотрим каждый аргумент:
$table
— название таблицы$data
— данные на обновление. Форматarray('колонка' => 'значение')
$where
— именованный массив для условий, согласно которым следует найти строки на обновление. Форматarray('колонка' => 'значение')
. Если указать несколько колонок — то будут учитываться условия всех колонок — т.е. использован оператор AND между условиями. Сравнение между колонкой и значением всегда ‘=
‘. Если указать, например,array('колонка' => null)
— то будет использована проверка IS NULL.$format
— массив, определяющий формат входящих данных аргумента$data
. Для определения формата используются следующие флаги:'%d', '%f', '%s' (integer, float и string соответственно)
$where_format
— работает аналогично аргументу$format
, но определяет формат для аргумента$where
.
Пример использования:
/**
* Опубликуем все товары со статусом "draft"
*
* @return bool
*/
function feodoraxis_to_publish_products():bool {
global $wpdb;
$result = $wpdb->update(
//Название таблицы
$wpdb->posts,
//Данные, которые мы хотим изменить
[
'post_status' => 'publish',
],
//Найдем нужные записи (условия)
[
'post_type' => 'product',
'post_status' => 'draft',
],
//Формат для данных (data)
[
'%s'
],
//Формат для условий (where)
[
'%s',
'%s'
]
);
//Если все хорошо - вернем истину
return $result !== false;
}
delete() — удаление
Удаляет строки. Входящие данные можно не очищать.
Аргументы метода: wpdb::delete( string $table, array $where, array|string|null $where_format = null )
Рассмотрим каждый аргумент:
$table
— название таблицы$where
— именованный массив для условий, согласно которым следует найти строки на обновление. Форматarray('колонка' => 'значение')
. Если указать несколько колонок — то будут учитываться условия всех колонок — т.е. использован оператор AND между условиями. Сравнение между колонкой и значением всегда ‘=
‘. Если указать, например,array('колонка' => null)
— то будет использована проверка IS NULL.$where_format
— массив, определяющий формат входящих данных аргумента$where
. Для определения формата используются следующие флаги:'%d', '%f', '%s' (integer, float и string соответственно)
Пример использования:
/**
* Удалим все товары со статусом "draft"
*
* @return bool
*/
function feodoraxis_delete_draft_products():bool {
global $wpdb;
$result = $wpdb->delete(
//Название таблицы
$wpdb->posts,
//Найдем нужные записи
[
'post_type' => 'product',
'post_status' => 'draft',
],
//Формат для where
[
'%s',
'%s'
]
);
//Если все хорошо - вернем истину
return $result !== false;
}
prepare() — очистка строки
Подготавливает строку с SQL-запросом для безопасного исполнения. Принимает строку с SQL и аргументы для нее. Возвращает подготовленную строку с SQL, которую можно смело использовать для методов wpdb::query()
и wpdb::get_results()
.
Синтаксис: wpdb::prepare( string $query, array|mixed ...$args )
Рассмотрим аргументы:
$query
— принимает в себя строку с SQL-запросом с плейсхолдерами как для функцииsprintf()
...$args
— принимает значения для SQL-запроса. Можно передавать значения как аргументы — последовательно — так и в виде массива.
Пример с массивом:
<?php
$wpdb->prepare(
"SELECT * FROM `table` WHERE `column` = %s AND `field` = %d OR `second_field` LIKE %s",
array(
'foo',
1337,
'%bar'
)
);
Пример с последовательной передачей аргументов:
<?php
$wpdb->prepare(
"SELECT * FROM `table` WHERE `column` = %s AND `field` = %d OR `second_field` LIKE %s",
'foo',
1337,
'%bar'
);
Оба примера будут работать одинаково.
Функция esc_sql()
Очищает входящие данные и возвращает очищенную строку. Принимает как массивы, так и строку. Возвращает те-же данные, которые были переданы.
Данные, очищенные этой функцией можно передавать в SQL-запрос без использования метода wpdb::prepare().
Данная функция является обёрткой над методом wpdb::_escape()
Синтаксис: esc_sql( array|string $data ):array|string
Пример использования:
<?php
$foo = "foo";
$bar = '%bar';
$wpdb->get_results( "SELECT * FROM `table` WHERE `column` = '" . esc_sql( $foo ) . "' AND `field` = '" . esc_sql( $bar ) . "'" );
Очистка аргументов для параметра IN
В рамках класса wpdb
на момент написания статьи нет встроенных механизмов очистки данных для параметра IN
. Поэтому тут нужна смекалка. Или умение гуглить 🙂
Пример решения:
<?php
function handle_in( array $values ):string {
$cites_list = array_map( function( $value ) {
return "'" . esc_sql( $value ) . "'";
}, $values );
return implode( ',', $cites_list );
}
$cites_list = ["Moscow", "Minsk", "Beijing"];
$result = $wpdb->get_results( "SELECT * FROM `" . $wpdb->posts . "` WHERE `post_title` IN (" . handle_in( $cites_list ) . ")" );
Подключение к сторонней БД
Стандартно в глобальной переменной $wpdb
происходит взаимодействие с БД, указанной в файле wp-config.php
. Но данный класс можно также использовать для подключения к сторонней БД, к которой есть доступ с вашего хоста. Для этого нужно создать отдельный экземпляр класса wpdb
, в котором следует указать параметры подключения к другой БД.
Пример:
<?php
$wpdb2 = new wpdb( "db_user", "password", "db_name", "db_host" );
if ( ! empty( $wpdb2->error ) ) {
wp_die( $wpdb2->error );
}
Теперь, используя переменную $wpdb2
с отдельным экземпляром класса wpdb
мы, используя те-же методы можем взаимодействовать с указанной БД.
Создание и удаление своей таблицы в БД, а также взаимодействие с ней
При разработке можно создавать сторонние таблицы БД для разных нужд. Но делать это следует с умом. Метод или функция, создающие таблицу должны срабатывать строго один раз — когда активируется ваша тема или плагин. При отключении плагина или темы, рекомендуется удалять такие таблицы. Но лучше перед отключением плагина давать пользователю выбор: сохранить данные или удалить.
Например WooCommerce оставляет свои таблицы и данные в них при отключении. И это хорошо. Потому что плагин может быть отключен из-за ошибки на сервере, может быть отключен случайно или для тестирования админом. Зато при повторном включении, все настройки и данные сохраняются, что удобно.
Примеры для плагина
Пример создания таблицы при активации плагина:
<?php
function table_carts_create():void {
global $wpdb;
$table_name = $wpdb->prefix . "carts"; // название таблицы
$charset_collate = $wpdb->get_charset_collate();
//Проверим, существует-ли таблица. Создаем только если ее нет.
if ( $wpdb->get_var( "show tables like `" . $table_name . "`" ) == $table_name ) {
return;
}
$sql = "CREATE TABLE `" . $table_name . "` (
id int(11) NOT NULL AUTO_INCTEMENT,
user_id int(11) NOT NULL,
createtime datetime NOT NULL,
UNIQUE KEY id (id)
) $charset_collate;";
require_once( ABSPATH . "wp-admin/includes/upgrade.php" );
dbDelta( $sql );
}
register_activation_hook( __FILE__, "table_carts_create" );
Удаление таблицы при деактивации плагина:
function table_carts_remove():void {
global $wpdb;
$table_name = $wpdb->prefix . "carts";
$sql = "DROP TABLE IF EXISTS `" . $table_name . "`";
$wpdb->query( $sql );
}
register_uninstall_hook( __FILE__, "table_carts_remove" );
Примеры для темы
Создание таблицы при активации темы:
function table_carts_create():void {
global $wpdb;
$table_name = $wpdb->prefix . "carts"; // название таблицы
$charset_collate = $wpdb->get_charset_collate();
//Проверим, существует-ли таблица. Создаем только если ее нет.
if ( $wpdb->get_var( "show tables like `" . $table_name . "`" ) == $table_name ) {
return;
}
$sql = "CREATE TABLE `" . $table_name . "` (
id int(11) NOT NULL AUTO_INCTEMENT,
user_id int(11) NOT NULL,
createtime datetime NOT NULL,
UNIQUE KEY id (id)
) $charset_collate;";
require_once( ABSPATH . "wp-admin/includes/upgrade.php" );
dbDelta( $sql );
}
add_action( "after_setup_theme", "table_carts_create" );
Удаление таблицы при деактивации темы:
<?php
function table_carts_remove():void {
global $wpdb;
$table_name = $wpdb->prefix . "carts";
$sql = "DROP TABLE IF EXISTS `" . $table_name . "`";
$wpdb->query( $sql );
}
add_action( "after_switch_theme", "table_carts_remove" );
Заключение
Надеюсь, статья была вам полезна. Если есть вопросы или замечания — пишите о них в ВК. Всё разберём 🙂
Успехов!