Некоторое время назад мне понадобилось сделать интернет-магазин на WordPress. В официальном репозитории есть немало хороших решений. Среди них уже давно выделился лидер — Woocommerce. Думаю он не нуждается в представлении. Многомилионная армия пользователей, сотни платных и бесплатных расширений и невероятная гибкость. Вот почему Woocommerce имеет более 5 миллионов активных установок и охватывает крупную долю интернет-магазинов во всём мире.
Всё же я решил изобрести свой велосипед. Отчасти, чтобы прокачать навыки, отчасти, чтобы попробовать сделать не требовательный к ресурсам и достаточно быстрый ecommerce плагин. Недавно я разместил его в официальный репозиторий, поэтому, приглашаю всех желающих его потестировать. В этой статье я не буду делать обзор возможностей, а расскажу лишь о некоторых интересных технических решениях.

Вообще, пермалинки в WordPress — один из самых сложных участков, так как требует решения ряда проблем связей и зависимостей. В wpStore встроена возможность управления постоянными ссылками. Например, вы можете удалить слаг
Особо попотеть пришлось, чтобы добиться вложенности категорий. Используя функцию
Но на этом этапе появилась проблема производительности. Сайт стал работать медленнее, особенно на странице вывода нескольких товаров. Например, на странице категории с выводом всего лишь 16 товаров производилось почти 90 запросов в базу данных и резко увеличивалось время ответа сервера примерно на 25-30%.
Оказалось, что при вызове функции the_permalink WordPress производит множество операций: получает тип ЧПУ и собирает данные записи в зависимости от этого типа. Чтобы вывести 1 ссылку WordPress производит несколько некешируемых запросов в базу. При этом процесс получения таксономий и иерархий товара не самый быстрый.
Так как постоянная генерация ссылки нам не подходит, логично её где-нибудь хранить и затем просто подтягивать оттуда. Было решено использовать специальное поле
Чтобы в базу ссылки правильно сохранялись повесим на событие
Нам остаётся перехватить вывод ссылки на хуке
Здесь мы проверяем заполненность поля guid, тип поста и тип ЧПУ. Если значения трёх параметров нас устраивают, выводим заранее сохранённую ссылку.
Теперь, самое приятное: посмотреть на результаты. Количество запросов в базу снизилось почти в 2 раза — с почти 90 до 44! А время ответа сервера на тестовом хостинге опустилось до приемлемых 0.24 секунды.
Обновление поля guid происходит не только во время создания и редактирования товара. В плагин встроена система импорта товаров через csv, поэтому обновление происходит также и при завершении импорта.
Update: документация по плагину
Всё же я решил изобрести свой велосипед. Отчасти, чтобы прокачать навыки, отчасти, чтобы попробовать сделать не требовательный к ресурсам и достаточно быстрый ecommerce плагин. Недавно я разместил его в официальный репозиторий, поэтому, приглашаю всех желающих его потестировать. В этой статье я не буду делать обзор возможностей, а расскажу лишь о некоторых интересных технических решениях.

Проблема постоянных ссылок
Вообще, пермалинки в WordPress — один из самых сложных участков, так как требует решения ряда проблем связей и зависимостей. В wpStore встроена возможность управления постоянными ссылками. Например, вы можете удалить слаг
product
из урл товара, изменить на свой или добавить в него слаг категории или даже вложенность категорий. Ссылки на товары могут выглядеть так: вашсайт.рф/родительская-категория/дочерняя-категория-второго-уровня/дочерняя-категория-третьего-уровня/имя-товара/
. Неплохо.Особо попотеть пришлось, чтобы добиться вложенности категорий. Используя функцию
wp_get_object_terms,
получаем категории указанного товара, а затем в цикле собираем слаги и формируем урл по иерархии от родительской к дочерней. Чтобы выводить нужные ссылки используем фильтр post_link. Вот лишь часть кода (полный код вы сможете посмотреть в исходниках):add_filter( 'post_link', 'wpsl_post_type_permalink', 20, 3 );
add_filter( 'post_type_link', 'wpsl_post_type_permalink', 20, 3 );
function wpsl_post_type_permalink( $permalink, $post_id, $leavename ) {
....
/**
* Works only in the admin panel when changing the structure of permanent links or creating/updating the product
* In the frontend to display links to products using $post->guid
* Relevant if the structure of permalinks are used %category% or %categories%
*/
if ( is_admin() ) {
// get all terms (product categories) of this post (product) by hierarchicaly
// change %category%
if ( strpos( get_option( 'product_permalink' ), '%category%' ) !== false && $terms = wpsl_get_terms_hierarchicaly( $post->ID, 'product_cat' ) ) {
$custom_slug = str_replace( '%category%', isset( $terms[0] ) && is_object( $terms[0] ) ? $terms[0]->slug : '', $custom_slug );
}
// change %categories%
if ( strpos( get_option( 'product_permalink' ), '%categories%' ) !== false && $terms = wpsl_get_terms_hierarchicaly( $post->ID, 'product_cat' ) ) {
foreach( $terms as $term ) {
$hierarchical_slugs[] = $term->slug;
}
$custom_slug = str_replace( '%categories%', implode( '/', $hierarchical_slugs ), $custom_slug );
} else {
$custom_slug = str_replace( '%categories%', 'product', $custom_slug );
}
}
....
return $permalink;
}
Но на этом этапе появилась проблема производительности. Сайт стал работать медленнее, особенно на странице вывода нескольких товаров. Например, на странице категории с выводом всего лишь 16 товаров производилось почти 90 запросов в базу данных и резко увеличивалось время ответа сервера примерно на 25-30%.
Оказалось, что при вызове функции the_permalink WordPress производит множество операций: получает тип ЧПУ и собирает данные записи в зависимости от этого типа. Чтобы вывести 1 ссылку WordPress производит несколько некешируемых запросов в базу. При этом процесс получения таксономий и иерархий товара не самый быстрый.
Так как постоянная генерация ссылки нам не подходит, логично её где-нибудь хранить и затем просто подтягивать оттуда. Было решено использовать специальное поле
guid
таблицы wp_posts
. Хотя разработчики WP не рекомендуют его менять, всё же мы можем игнорировать это предупреждение по нескольким причинам:- guid используется для формирования RSS, а его мало кто использует.
- в RSS выводятся только записи, а у нас тип поста — product.
Чтобы в базу ссылки правильно сохранялись повесим на событие
save_post
функцию, которое обновляет поле guid:add_action( 'save_post', 'wpsl_guid_rewrite', 100 );
function wpsl_guid_rewrite( $id ) {
if( !is_admin() ) return;
if( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return false;
if ( strpos( get_option( 'product_permalink' ), '%category%' ) !== false || strpos( get_option( 'product_permalink' ), '%categories%' ) !== false ) {
if( $id && get_post_type( (int)$id ) == 'product' ){
global $wpdb;
$wpdb->update( $wpdb->posts, [ 'guid' => ( get_permalink( $id ) ) ], [ 'ID' => intval( $id ) ] );
}
clean_post_cache( $id );
}
}
Нам остаётся перехватить вывод ссылки на хуке
post_type_link
и вывести сгенерированную ссылку:add_filter( 'post_type_link', 'wpsl_get_permalink_change', 10, 4 );
function wpsl_get_permalink_change( $post_link, $post, $leavename, $sample ){
if ( isset( $post->guid ) && $post->guid && $post->post_type == 'product' && ( strpos( get_option( 'product_permalink' ), '%category%' ) !== false || strpos( get_option( 'product_permalink' ), '%categories%' ) !== false ) ) {
return $post->guid;
}
return $post_link;
}
Здесь мы проверяем заполненность поля guid, тип поста и тип ЧПУ. Если значения трёх параметров нас устраивают, выводим заранее сохранённую ссылку.
Теперь, самое приятное: посмотреть на результаты. Количество запросов в базу снизилось почти в 2 раза — с почти 90 до 44! А время ответа сервера на тестовом хостинге опустилось до приемлемых 0.24 секунды.
Обновление поля guid происходит не только во время создания и редактирования товара. В плагин встроена система импорта товаров через csv, поэтому обновление происходит также и при завершении импорта.
Update: документация по плагину