Кэширование Минимальных и Максимальных Цен для Всех Категорий Продуктов (WooCommerce)
Представьте, что у нас есть фильтр продуктов для определенной категории, который позволяет отсортировать товары по цене. Для такого фильтра удобно знать минимальные и максимальные цены продуктов в этой категории.
Можно получать эти цены каждый раз при загрузке страницы категории, но это создаёт нагрузку на сервер. Чтобы улучшить производительность, лучше всего получить минимальные и максимальные цены для каждой категории один раз и использовать их впоследствии в фильтре.
Существует несколько способов кэширования минимальных и максимальных цен. В этой статье я покажу один из них, с которым столкнулся при решении этой задачи.
Принцип Работы
Мы собираем все минимальные и максимальные цены продуктов для каждой категории (на всех уровнях) и для всей таксономии в целом. Затем все эти данные сохраняются в опциях WordPress. Когда продукт обновляется или добавляется, данные обновляются не сразу, а через 60 секунд после изменения записи. Это позволяет обновлять записи массово и не выполнять дорогостоящую операцию при каждом обновлении. Время в 60 секунд можно увеличить до, например, 3600 секунд (1 час).
Есть и другой подход: можно обновлять только данные конкретных категорий, в которых находится обновляемый продукт, сохраняя при этом общие данные для всей таксономии. В этом случае потребуется дополнительная логика, чтобы отслеживать, из какой категории был удалён продукт. Я выбрал первый подход, так как он не имеет таких недостатков.
Пример Кода
Ниже приводится пример кода, который собирает и кэширует минимальные и максимальные значения указанного мета-поля для заданных терминов таксономии. Этот код подходит для получения минимальных и максимальных цен продуктов из категорий.
meta_key
илиtaxonomy
.'); } $this->meta_key = $args['meta_key']; $this->taxonomy = $args['taxonomy']; $this->post_type = $args['post_type']; $this->cache_ttl = (int)($args['cache_ttl'] ?? WEEK_IN_SECONDS); $this->cache_key = "minmax_{$args['taxonomy']}_{$args['meta_key']}_values"; } public function init(): void { add_action('init', [$this, 'check_update_data'], 99); } public function get_data(): array { return ((array)get_transient($this->cache_key)) ?: []; } public function get_term_minmax($term_id_or_all): array { return $this->get_data()[$term_id_or_all] ?? [0, 0]; } public function check_update_data(): void { add_action("save_post_{$this->post_type}", [$this, 'mark_data_for_update']); add_action('deleted_post', [$this, 'mark_data_for_update']); if (time() > ($this->get_data()['valid_until'] ?? 0)) { $this->update_data(); } } public function mark_data_for_update(): void { $minmax_data = $this->get_data(); $minmax_data['valid_until'] = time() + $this->update_timeout; set_transient($this->cache_key, $minmax_data); } public function update_data(): void { $minmax_data = ['valid_until' => time() + $this->cache_ttl]; $this->add_all_minmax($minmax_data); $this->add_terms_minmax($minmax_data); set_transient($this->cache_key, $minmax_data); } private function add_all_minmax(&$minmax_data): void { global $wpdb; $sql = str_replace('{AND_WHERE}', '', $this->minmax_base_sql()); $minmax = $wpdb->get_row($sql, ARRAY_A); $minmax_data['all'] = [(int)$minmax['min'], (int)$minmax['max']] ?? [0, 0]; } private function add_terms_minmax(&$minmax_data): void { global $wpdb; $base_sql = $this->minmax_base_sql(); $terms_data = self::get_terms_post_ids_data($this->taxonomy); foreach ($terms_data as $term_id => $post_ids) { if (empty($post_ids)) { continue; } $IN_post_ids = implode(',', array_map('intval', $post_ids)); $minmax = $wpdb->get_row(str_replace('{AND_WHERE}', "AND post_id IN($IN_post_ids)", $base_sql), ARRAY_A); if (array_filter($minmax)) { $minmax_data[$term_id] = [(int)($minmax['min'] ?? 0), (int)($minmax['max'] ?? 0)]; } } } private function minmax_base_sql(): string { global $wpdb; return $wpdb->prepare(" SELECT MIN(CAST(meta_value as UNSIGNED)) as min, MAX(CAST(meta_value as UNSIGNED)) as max FROM $wpdb->postmeta WHERE meta_key = %s AND meta_value > 0 {AND_WHERE} ", $this->meta_key); } private static function get_terms_post_ids_data(string $taxonomy): array { global $wpdb; $cats_data_sql = $wpdb->prepare(" SELECT term_id, object_id, parent FROM $wpdb->term_taxonomy tax LEFT JOIN $wpdb->term_relationships rel ON (rel.term_taxonomy_id = tax.term_taxonomy_id) WHERE taxonomy = %s ", $taxonomy); $terms_data = (array)$wpdb->get_results($cats_data_sql); $new_terms_data = []; foreach ($terms_data as $data) { if (!isset($new_terms_data[$data->term_id])) { $new_terms_data[$data->term_id] = (object)[ 'parent' => $data->parent, 'object_id' => [], 'child' => [], ]; } if ($data->object_id) { $new_terms_data[$data->term_id]->object_id[] = $data->object_id; } } foreach ($terms_data as $term_id => $data) { if ($data->parent) { $terms_data[$data->parent]->child[] = &$terms_data[$term_id]; // ссылка } } $terms_post_ids = []; foreach ($terms_data as $term_id => $data) { $post_ids = []; self::collect_post_ids_recursively($post_ids, $data); $terms_post_ids[$term_id] = array_unique($post_ids); } return $terms_post_ids; } private static function collect_post_ids_recursively(&$post_ids, $data) { if ($data->object_id) { $post_ids = array_merge($post_ids, (array)$data->object_id); } foreach ($data->child as $child_data) { self::collect_post_ids_recursively($post_ids, $child_data); } } }
Как Использовать
Чтобы инициализировать класс, добавьте следующий код в ваш файл functions.php
(в хуке init
, чтобы все таксономии были зарегистрированы к этому времени):
global $kama_minmax;
$kama_minmax = new Kama_Minmax_Post_Meta_Values([
'meta_key' => 'price',
'taxonomy' => 'product_cat',
'post_type' => 'product',
'cache_ttl' => DAY_IN_SECONDS, // по умолчанию WEEK_IN_SECONDS
]);
$kama_minmax->init();
Получение Данных
Чтобы получить данные, используйте следующий код:
global $kama_minmax;
$minmax_data = $kama_minmax->get_data();
В этом случае $minmax_data
будет содержать такие данные:
$minmax_data = [
[uptime] => 1508719235,
[all] => [80, 68000],
[1083] => [950, 7300],
// и так далее...
];
Вы можете получить минимальную и максимальную цену для конкретной категории:
[ $min, $max ] = $kama_minmax->get_term_minmax(123);
echo $min;
echo $max;
Для тестирования кода вы можете использовать параметры GET:
if (isset($_GET['get_minmax_prices_test'])) {
die(print_r($kama_minmax->get_data()));
}
if (isset($_GET['update_minmax_prices_test'])) {
$kama_minmax->update_data();
die(print_r($kama_minmax->get_data()));
}
Заключение
Этот код подходит не только для WooCommerce, но и для любого магазина на платформе WordPress. Основная идея заключается в сборе всех минимальных и максимальных цен из указанного мета-поля для терминов всех уровней вложенности. Это может значительно улучшить производительность вашего интернет-магазина.