Мета-данные в WordPress
Под словом «Метаданные» в WordPress скрывается огромная работа и потрясающая логика, которую можно ругать и хвалить одновременно, но одного у метаданных не отнять - они очень удобны. При этом разобраться в принципе их работы очень просто.
Читайте также, как устроены таксономии в WordPress
Читайте также, как устроенны записи в WordPress
Что такое метаданные?
Метаданные в WordPress — это данные, которые дополняют основные данные. Их еще называют метаполя, произвольные поля, кастомные поля. По-другому можно сказать, что метаданные - это дополнительная таблица в базе данных, которая расширяет основную таблицу.
Например:
-
у записи (поста) есть основные данные: контент, заголовок, ... и могут быть дополнительные данные (метаданные), например число просмотров записи, ID миниатюры, ID редактировавшего юзера, и т.д.
- у пользователя есть основные данные - это данные из таблицы wp_users: логин, ссылка на сайт, email, ... и метаданные: биография, ссылка на соц. профиль, настройки админ панели.
Таблицы метаданных в Базе Данных WP
По умолчанию в WordPress существует 5 таблиц для разных объектов (записи, комментарии, пользователи, элементы таксономии, сайты):
- wp_postmeta
- Записи - таблица wp_postmeta для wp_posts. Сюда записываются привычные в WordPress "произвольные поля поста"
- wp_usermeta
- Пользователи - таблица wp_usermeta для wp_users. Дополнительные данные о пользователе.
- wp_termmeta(С WP 4.4)
- Термины (элементы таксономий) - таблица wp_termmeta для wp_terms. Дополнительные данные для элементов таксономии.
- wp_commentmeta
- Комментарии - таблица wp_commentmeta для wp_comments. Метаданные для каждого комментария.
- wp_sitemeta(для мультисайт сборки)
- Сайты (главный сайт сети в мультисайт) - таблица wp_sitemeta для wp_site. Опции основного сайта сети.
Все метаданные имеют одинаковую логику и таблицы одинаковой структуры в базе данных.
Как можно видеть, структура всех таблиц одинаковая, разница только в названии основных полей. Связываются они с основной таблицей через второе поле (оно у всех таблиц называется по-разному: post_id, user_id, comment_id, term_id). Основной ключ (желтый) обычно не используется и нужен только для точной идентификации конкретной строки в таблице (это иногда нужно, потому что удобно). Запросы как правило работают по трем полям: 2, 3, 4.
По умолчанию WordPress активно использует таблицы метаданных для записей и для пользователей. Менее активно используется таблица метаданных для комментариев - туда при помещении комментария в корзину записывается время удаления (если корзина отключена, то таблица не используется). И совсем не используется таблица метаданных для терминов.
Все таблицы метаданных отлично подходят для расширения возможностей движка. И их активно используют разработчики плагинов и тем.
Таблица метаданных для терминов, была добавлена только в версии 4.4 специально для того, чтобы разработчикам было куда сохранять нужные данные этих самых терминов.
Скрытые (защищенные) метаполя
В WordPress есть такое понятие как скрытые метаполя. Это поля, название которых (значение meta_key) начинается с нижнего подчеркивания _
. Так принято называть метаполя, которые используются для нужд кода и не должны изменяться вручную.
В админке, для постов в блоке «Произвольные поля» скрытые метаполя не отображаются, а значит их нельзя изменить. Так, например, при редактировании записи в метаполе _edit_lock записывается метка времени и ID пользователя, который редактирует запись. Благодаря этому мы можем видеть что запись в текущий момент редактируется другим пользователем. Или другой пример, ID картинки-вложения, которая устанавливается как миниатюра записи, сохраняется в метаполе _thumbnail_id.
Для выяснения является ли метаполе скрытым есть специальная функция: is_protected_meta( $meta_key, $meta_type ).
Чтобы сделать любое метаполе скрытым, есть хук is_protected_meta:
// Скроем некоторые метаполя add_filter( 'is_protected_meta', 'my_protected_custom_fields', 10, 2 ); function my_protected_custom_fields( $protected, $meta_key ){ if( in_array( $meta_key, [ 'luboe_pole' ] ) ) return true; return $protected; }
Функции метаданных
Почти все функции метаданных работают на основе четырех базовых функций. На основе этих четырех функций по сути построено API работы со всеми метаданными в WordPress.
- get_metadata( $meta_type, $object_id, $key, $single )
- update_metadata( $meta_type, $object_id, $key, $value, $prev_value )
- delete_metadata( $meta_type, $object_id, $key, $value, $delete_all )
- add_metadata( $meta_type, $object_id, $key, $value, $unique )
Для записей:
- get_post_meta( $post_id, $key, $single )
- add_post_meta( $post_id, $key, $value, $unique )
- update_post_meta( $post_id, $key, $value, $prev_value )
- delete_post_meta( $post_id, $key, $value )
- delete_post_meta_by_key( $key ) - удаляет все метаполя у всех постов по имени поля.
- get_post_custom( $post_id ) - получает массив всех метаполей текущего поста (включая скрытые).
- the_meta() - выводит значения метаполей записи в списке <li> (без скрытых).
Для пользователей:
- get_user_meta( $user_id, $key, $single )
- add_user_meta( $user_id, $key, $value, $unique )
- update_user_meta( $user_id, $key, $value, $prev_value )
- delete_user_meta( $user_id, $key, $value )
- get_the_author_meta( $field ) - получает значение указанного метаполя текущего юзера.
Для комментариев:
- get_comment_meta( $comment_id, $key, $single )
- add_comment_meta( $comment_id, $key, $value, $unique )
- update_comment_meta( $comment_id, $key, $value, $prev_value )
- delete_comment_meta( $comment_id, $key, $value )
Для таксономий (терминов):
- get_term_meta( $term_id, $key, $single )
- add_term_meta( $term_id, $key, $value, $unique )
- update_term_meta( $term_id, $key, $value, $prev_value )
- delete_term_meta( $term_id, $key, $value )
- has_term_meta( $term_id ) - получает массив всех метаданных термина (получит все поля таблицы).
Получить значения всех метаполей объекта можно с помощью функции get_***_meta(). Для этого нужно указать только первый параметр: id объекта:
$metas = get_post_meta( 76 ); /* Array( [_edit_lock] => Array [0] => 1517175359:1 [_edit_last] => Array [0] => 1 [views] => Array [0] => 10164 [_thumbnail_id] => Array [0] => 9556 [photo] => Array [0] => https://wp-kama.ru/wp-content/uploads/2010/03/Quicktags-API.png [1] => https://wp-kama.ru/wp-content/uploads/2017/07/image.png ) */
Очистка значений метаполей при сохранении
Значение любого метаполя можно очистить через фильтр: sanitize_(type)_meta_(meta_key).
Этот фильтр срабатывает всегда при добавлении или обновлении метаполя.
Все варианты фильтра, если указать первый изменяемый параметр:
- sanitize_post_meta_(meta_key)
- sanitize_user_meta_(meta_key)
- sanitize_comment_meta_(meta_key)
- sanitize_term_meta_(meta_key)
Пример использования фильтра
Допустим, у нас есть метаполе пользователя my_history (моя история). В это поле пользователь может писать какой-то текст, но в нем нельзя использовать HTML теги. Чтобы быть уверенным наверняка, что туда не попадут эти самые теги, лучше всего очистить значение поля перед сохранением его в базу данных:
add_filter( 'sanitize_user_meta_'.'my_history', function( $meta_value ){ return wp_strip_all_tags( $meta_value ); });
Регистрация метаполей
С версии 4.6 в WordPress появилась возможность дополнительно описывать каждое метаполе. Делается это через функцию register_meta().
Регистрация метаполя, нужна для использования его в разных API, например для REST API (полная поддержка REST API ожидается с версии WP 5.0).
Поведение register_meta() чем-то походе на register_post_type() - данные аналогично сохраняются в глобальную переменную $wp_meta_keys. Это позволяет получать данные метаполя в любое время при написании кода.
Пример регистрация метаполя
Зарегистрируем метаполе для поста с функциями доступа и очистки:
register_meta( 'post', 'bookname', [ 'type' => 'string', 'description' => 'Название книги', 'single' => true, 'sanitize_callback' => function( $meta_value, $meta_key, $object_type ){ return wp_strip_all_tags( $meta_value ); // удалим html теги }, 'auth_callback' => function( $false, $meta_key, $postID, $user_id, $cap, $caps ){ // запретим создание и редактирование этого метаполя для всех кроме админа return current_user_can('manage_options'); }, 'show_in_rest' => false, ] );
В результате, если зайти на страницу редактирования записи и попробовать там создать метаполе bookname:
- Если вы вошли как админ - метаполе создастся.
- Если как редактор, автор и т.д. - вы не сможете создать метаполе.
За это отвечает параметр auth_callback
Далее, если в значении указать строку с HTML тегами, при обновлении они будут вырезаны.
За это отвечает параметр sanitize_callback
Параметры: type
, description
, single
, show_in_rest
в версии WP 4.6, носят только информационный характер. А с версии WP 5.0 используются в REST API. Например, если указать show_in_rest=true
, то метаполе можно будет редактировать через REST API.
Остальные функции связанные с регистрацией метаполей:
- registered_meta_key_exists()
- unregister_meta_key()
- get_registered_meta_keys()
- get_registered_metadata()
Метаполя для свой таблицы
API метаполей позволяет создать свою таблицу метаполей для любой таблицы. Рассмотрим на примере.
Допустим у нас есть таблица my_books:
Создаем для нее таблицу метаданных my_bookmeta:
Для создания таблицы метаданных, нужно один раз запустить написанную нами функцию create_book_meta_table(). Код функции:
## register_activation_hook( __FILE__, 'create_book_meta_table'); ## Функция создания таблицы метаданных. Нужно запустить один раз. Можно повесить на register_activation_hook() function create_book_meta_table(){ global $wpdb; $collate = ''; if ( ! empty($wpdb->charset) ) $collate = "DEFAULT CHARACTER SET $wpdb->charset"; if ( ! empty($wpdb->collate) ) $collate .= " COLLATE $wpdb->collate"; /* * Indexes have a maximum size of 767 bytes. Historically, we haven't need to be concerned about that. * As of 4.2, however, we moved to utf8mb4, which uses 4 bytes per character. This means that an index which * used to have room for floor(767/3) = 255 characters, now only has room for floor(767/4) = 191 characters. */ $max_index_length = 191; $main_field = 'book_id'; // название главной колонки, должно выглядеть как: $meta_type . '_id' $table_name = 'my_bookmeta'; $wpdb->query( "CREATE TABLE $table_name ( meta_id bigint(20) unsigned NOT NULL auto_increment, $main_field bigint(20) unsigned NOT NULL default '0', meta_key varchar(255) default NULL, meta_value longtext, PRIMARY KEY (meta_id), KEY $main_field ($main_field), KEY meta_key (meta_key($max_index_length)) ) $collate;" ); }
Название главной колонки, должно выглядеть как: $meta_type . '_id'
. Так оно собирается в функциях: *_metadata().
Зарегистрируем функции для работы с метаданными book:
function add_book_meta( $id, $meta_key, $meta_value, $unique = false ) { return add_metadata( 'book', $id, $meta_key, $meta_value, $unique ); } function delete_book_meta( $id, $meta_key, $meta_value = '' ) { return delete_metadata( 'book', $id, $meta_key, $meta_value ); } function get_book_meta( $id, $meta_key = '', $single = false ) { return get_metadata( 'book', $id, $meta_key, $single ); } function update_book_meta( $id, $meta_key, $meta_value, $prev_value = '' ){ return update_metadata( 'book', $id, $meta_key, $meta_value, $prev_value ); }
Все!
Теперь мы можем использовать функции для управления метаданными, которые будут работать в точности, как работают метаданные в WordPress. Сюда входит и кэширование метаданных, и всевозможные хуки...
Например:
// добавим данные в таблицу метаданных update_book_meta( 12, 'author_name', 'Циркон' ); // получим значение метаполя get_book_meta( 12, 'author_name', 1 ); //> Циркон // получим значения всех метполей get_book_meta( 12 ); //> вернет массив
Построение запроса с использованием метаданных
В WordPress очень удобно можно выбирать или сортировать строки основной таблицы, на основе параметров метаданных. Например, в WP_query() это делается через параметр meta_query.
Прикрутить такой же функционал к нашей таблице можно с помощью класса WP_Meta_Query{}. Напишу, для примера функцию получения книг, с возможностью выборки по метаданным:
// установим таблицы в $wpdb global $wpdb; $wpdb->books = "my_books"; $wpdb->bookmeta = "my_bookmeta"; ## Пример функции для получения книг, с возможностью выборки по метаданным function get_books( $args = array() ){ global $wpdb; $default = [ 'book_id' => 0, 'name' => '', 'content_search' => '', // понимаемые мета-параметры 'meta_key' => '', 'meta_value' => '', 'meta_value_num' => '', 'meta_compare' => '', 'meta_query' => array(), ]; $args = array_merge( $default, $args ); $WHERE = array(); $JOIN = $ORDER_BY = $LIMIT = ''; if( $args['book_id'] ){ // 'my_books.' нужно потому что поле назвается одинаково у главной и у мета таблицы $WHERE[] = $wpdb->prepare('my_books.book_id = %d', $args['book_id'] ); } if( $args['name'] ){ $WHERE[] = $wpdb->prepare('name = %s', $args['name'] ); } if( $args['content_search'] ){ $WHERE[] = $wpdb->prepare('content LIKE %s', '%'. $wpdb->esc_like( $args['content_search'] ) .'%' ); } // мета запрос if( $args['meta_query'] || $args['meta_key'] ){ $metaq = new WP_Meta_Query(); $metaq->parse_query_vars( $args ); // парсим возможные мета-параметры из параметров $args // первый параметр 'book' должен быть началом свойства $wpdb->bookmeta без суффика 'meta' // Т.е. мы указываем 'book' к нему добавляется 'meta' и свойство 'bookmeta' должно существовать в $wpdb // см. https://wp-kama.ru/function/_get_meta_table $mq_sql = $metaq->get_sql( 'book', $wpdb->books, 'book_id' ); $JOIN = $mq_sql['join']; // INNER JOIN my_bookmeta ON ( my_books.book_id = my_bookmeta.book_id ) $WHERE[] = $mq_sql['where']; // AND ( ( my_bookmeta.meta_key = 'author_name' AND my_bookmeta.meta_value = 'Циркон' ) ) } $WHERE = 'WHERE '. implode( ' AND ', $WHERE ); /* для сортировки по метаполям понадобится $metaq->get_clauses() Array( [metasort] => Array( [key] => author_name [value] => Циркон [compare] => = [alias] => my_bookmeta [cast] => CHAR ) ) пример смотрите в: https://wp-kama.ru/function/WP_Query::parse_orderby */ $ORDER_BY = 'ORDER BY name ASC'; $fields = '*'; if( isset($metaq) && $metaq->has_or_relation() ) $fields = "DISTINCT $fields"; $res = $wpdb->get_results( "SELECT $fields FROM $wpdb->books $JOIN $WHERE $ORDER_BY $LIMIT" ); return $res; }
Проверим функцию, сделаем запрос:
// запрос на получение книг $books = get_books([ 'meta_key' => 'author_name', 'meta_value' => 'Циркон', ]); // или так $books = get_books([ 'meta_query' =>[ 'metasort' => [ 'key' => 'author_name', 'value' => 'Циркон', ] ] ]); print_r( $books ); /* Получим: Array( [0] => stdClass Object( [book_id] => 12 [name] => Вишневый сад [content] => Содержание книги ... [meta_id] => 2 [meta_key] => author_name [meta_value] => Циркон ) ) */
Повторюсь, теперь выборку по метаполям можно делать любой сложности, все что позволяет делать WP_Meta_Query{}, оно же meta_query в WP_Query.
Пример выборки посложнее:
$args = array( 'meta_query' => array( 'relation' => 'AND', array( 'key' => 'author_name', 'value' => 'алекс', 'compare' => 'LIKE' ), array( 'key' => 'price', 'value' => array( 20, 100 ), 'type' => 'numeric', 'compare' => 'BETWEEN' ) ) ); $books = get_books( $args );
Производительность и метаданные
Запросы на основе meta_query открывают огромные возможности, но у этого всего есть обратная сторона - не самая лучшая производительность, особенно если составляются сложные запросы и обрабатывается большое количество данных.
Слабым звеном в метаданных является поле meta_value любой таблицы метаданных (например таблицы wp_postmeta). У meta_value нет и не может быть индекса, потому что поле имеет тип LONGTEXT, чтобы можно было хранить в ней любые данные: числа, тексты любой длинны, сериализованные массивы и т.д. Также индексирование невозможно, потому что при построении запроса, например, сортировка по метаполю, где хранятся числа, значения превращаются из строк в числа функцией CAST() и только потом сортируются. Такой подход «убил» бы индекс, даже если бы он там был.
Из-за вышеописанных особенностей не всегда правильно использовать метаполя для хранения данных, по которым затем будет выборка или сортировка, хотя в большинстве случаев они подходят хорошо.
Всегда проверяйте запросы в реальных условиях, смотрите на время выполнения и то, как меняется это время от количества данных и параметров запроса. Если запросы стали медленными, то пришло время как-то их кэшировать или подключать внешнюю систему индексирования, например Sphinx или Elasticsearch.
Если ожидается хранить большие объемы данных, по которым нужна будет выборка и сортировка, то возможно стоит создавать специальную таблицу под такие данные. Под большими объемами данных я имею ввиду количество значений одного мета-ключа от 20000 и более. С таким объемом данных в метаполях запросы уже будут заметно тормозить и без кэширования (которое не всегда подходит) или других надстроек уже будет не обойтись.
Как включить произвольные поля в Gutenberg?
-
Читайте также: