[Из песочницы] Свежий плагин интернет-магазина на WordPress

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

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

fvskikx14klkav7qwkfvkw-gnou.png

Проблема постоянных ссылки


Вообще, пермалинки в WordPress — один из самых сложных участков, так как требует решения ряда проблем связей и зависимостей. В WP Store встроена возможность управления постоянными ссылками. Например, вы можете удалить слаг 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, поэтому обновление происходит также и при завершении импорта.

© Habrahabr.ru