web-dev-qa-db-ja.com

メニュー複数分類法

"Category"と "Brand"の両方の分類法を持つpost_type "products"があります。各製品には1つのカテゴリと1つのブランドがあります。

最初にカテゴリーをトップレベルのメニューとしてリストし、次にそれぞれに含まれるブランドをサブメニューとしてリストするメニューを表示したいと思います。

各サブメニューには、その特定の最上位カテゴリの製品に関連付けられているブランドのみが表示されます。

サブメニューのリンクは、選択されたブランドとカテゴリの商品のみを表示するページに移動する必要があります。しかし、この部分はwp_queryで理解できると思います。主に私はメニューの問題に混乱しています。

任意の助けは大歓迎です!

1
Charles

まず最初に、他の分類法(カテゴリ)に関連する投稿に関連する分類法(ブランド)から用語を取得する方法が必要です。

それをするための最も速い「コア」方法は、次のとおりです。

  1. 全カテゴリの一覧を取得する
  2. カテゴリごとに関連製品を入手
  3. 商品をループして関連ブランドを検索

このワークフローにはネストしたループが必要です。またa + b + 1クエリが必要です。ここでaはカテゴリの数、bは商品の数です。

あなたが持っているのであれば、e。 g。 200の投稿と20のカテゴリ、この関数は221のデータベースクエリとすべてのカテゴリとすべての投稿に対してネステッドループを実行します。

すべての製品をループ処理する必要があるため、ページ付けを使用できないことに注意してください。数千の製品がある場合、この機能によってサイトのパフォーマンスが低下する可能性があります。

この状況を改善するために何ができるでしょうか。 2つのこと

  • データベースクエリを削減するためのより高性能なSQLクエリメソッドの使用
  • キャッシュを使う

最初の部分として、適切なJOIN句とWHERE句を使用することで、投稿を必要とせずに2つの分類法から関連語を取得するカスタムSQLクエリを作成できます。

この関数を見てください。

function term_related_terms( $term, $term_tax, $target_tax, $onlyparent = FALSE ) {
  if ( ! taxonomy_exists( $term_tax ) || ! taxonomy_exists( $target_tax ) ) {
    return FALSE;
  }
  $term_tax_obj = $term_tax_id = FALSE;
  if ( is_numeric( $term ) ) {
    $term_tax_obj = get_term( $term, $term_tax );
  } elseif ( is_string( $term ) ) {
    $term_tax_obj = get_term_by( 'slug', $term, $term_tax );
  } elseif ( is_object( $term ) ) {
    $term_tax_obj = $term;
  }
  if ( is_object( $term_tax_obj ) && isset(  $term_tax_obj->term_taxonomy_id ) ) {
    $term_tax_id = $term_tax_obj->term_taxonomy_id;
  }
  if ( ! $term_tax_id ) return FALSE;
  global $wpdb;
  $query = $wpdb->prepare(
    "SELECT t.term_id, t.name, t.slug, tt.* FROM {$wpdb->terms} t
    INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_id = t.term_id
    INNER JOIN {$wpdb->term_relationships} tr ON tr.term_taxonomy_id = tt.term_taxonomy_id
    INNER JOIN {$wpdb->term_relationships} tr2 ON tr2.object_id = tr.object_id
    INNER JOIN {$wpdb->term_taxonomy} tt2 ON tr2.term_taxonomy_id = tt2.term_taxonomy_id
    WHERE tt.taxonomy = %s AND tt2.taxonomy = %s AND tr2.term_taxonomy_id = %s",
    $target_tax, $term_tax, $term_tax_id
  );
  if ( $onlyparent ) $query .= " AND tt.parent = 0";
  return $wpdb->get_results( $query .= " GROUP BY tt.term_taxonomy_id");
}

この関数は3つの必須引数を受け取ります:

  • 用語(用語ID、スラッグ、オブジェクト)
  • 用語が属する分類法
  • 別の分類名

そして、最初の分類法から与えられた用語を持つ投稿に関連付けられた2番目の分類法からのすべての用語を返します。

オプションの$onlyparent引数が、trueに設定されている場合に推測されるように、関数は2番目の分類法から最上位の用語のみを返します。

これは、この関数を次のように呼び出すことを意味します。

term_related_terms( $cadID, 'category', 'brand' );

与えられた$cadIDを持つ製品に関連するすべてのブランドを取得し、1つのdbクエリのみを実行することができます。

すべてのカテゴリに対してこの関数を実行すると、必要なデータをすべて取得できます。n + 1クエリを実行します。ここでnはカテゴリの数で、追加のクエリはすべてのカテゴリを取得します。

前の例(200の製品と20のカテゴリ)を使用すると、221の代わりに21のクエリを実行する必要があり、また入れ子になったクエリの代わりに単純なサイクルを実行する必要があります。

改善は目覚しいですが、21 dbクエリは簡単な作業ではありません、結果をキャッシュする必要があります。

最初の実行時に前の関数を呼び出し、結果を トランジェント にキャッシュし、その後の呼び出しでキャッシュされた結果を返すような関数を書きましょう。

function category_brand_menu_data() {
  // be sure following slugs match your setup
  $category_slug = 'category';
  $brand_slug = 'brand';
  $cache = get_transient( 'category_brand_menu_data' );  // try to get from cache
  if ( ! empty( $cache ) ) {
    return $cache;
  }
  // firts get the categories
  $cats = get_terms( $category_slug );
  $data = array();
  if ( ! empty( $cats ) ) {
    // get brands related to each category using term_related_terms()
    foreach ( $cats as $cat ) {
      $brands = term_related_terms( $cat, $category_slug, $brand_slug );
      if ( ! empty( $brands ) ) {
        $data[$cat->term_id] = array(
          'name' => $cat->name,
          'slug' => $cat->slug,
          'brands' => $brands
        );
      }
    }
    if ( ! empty( $brands ) ) {
      set_transient( 'category_brand_menu_data', $data ); // cache data for next call
    }
  }
  return $data;
}

キャッシュを実装したので、パフォーマンスは大幅に向上しますが、関連することが発生したときにキャッシュを無効にするメカニズムが必要です。

より正確には、関連性のあるは次のとおりです。

  • 商品はカテゴリに関連付けられています
  • 製品はブランドに関連付けられています
  • 商品とカテゴリの関連付けが削除されました
  • 製品とブランドの関連付けが削除されました
  • カテゴリー用語が削除された
  • ブランド用語が削除されました

最初の4つのイベントは、4番目の引数としてフック関数に分類法を渡す'set_object_terms'アクションを使用してターゲットにすることができます。

残りの2つのイベントは、3番目の引数としてフック関数に分類法を渡す'delete_term'アクションを使用してターゲットにすることができます。

それでは、キャッシュを無効にする(つまり一時的なものを削除する)関数を書いて、それを両方のフックに追加しましょう:

function category_brand_menu_clean( $a, $b, $tax_del, $tax_upd = NULL ) {
  // be sure following slugs match your setup
  $category_slug = 'category';
  $brand_slug = 'brand';
  // 'delete_term' pass taxonomy as 3rd argument, 'set_object_terms' as 4th
  $tax = ( current_filter() === 'delete_term' ) ? $tax_del : $tax_upd;
  if ( in_array( $tax, array( $category_slug, $brand_slug ) ) ) {
    delete_transient( 'category_brand_menu_data' );
    category_brand_menu_data();
  }
}

add_action( 'set_object_terms', 'category_brand_menu_clean', 20, 4 );
add_action( 'delete_term', 'category_brand_menu_clean', 20, 3 );

この関数は、キャッシュを削除した後、category_brand_menu_data()を呼び出してキャッシュが再構築され、関数がフロントエンドから呼び出されるとき、出力はキャッシュから実行されます。

これで、用語の関連付けを取得するパフォーマンス関数があり、取得したデータを使用してメニューを表示する関数だけが必要になりました。

function category_brand_menu() {
  // be sure following slugs match your setup
  $category_slug = 'category';
  $brand_slug = 'brand';
  // get data (can be cached or not)
  $data = category_brand_menu_data();
  if ( empty( $data ) ) return;
  // set html format according to your needs
  $format = '<nav><ul class="menu">%s</ul></nav>';
  $parentformat = '<li><a href="%s">%s</a><ul class="submenu">%s</ul></li>';
  $itemformat = '<li><a href="%s">%s</a></li>';
  $menu = '';
  $tax_obj = get_taxonomy( $category_slug );
  $query_var = $tax_obj->query_var;
  // loop through data retrieved to build menu html string
  foreach ( $data as $catid => $cat_data ) {
    $cat_link = get_term_link( $catid, $category_slug );
    $items = '';
    foreach( $cat_data['brands'] as $brand ) {
      $t_link = get_term_link( $brand, $brand_slug );
      // add category query arg to brand link, so when link is clicked
      // will show archives having both terms: the category and the brand
      $link = add_query_arg( array( $query_var => $cat_data['slug'] ), $t_link );
      $items .= sprintf( $itemformat, $link, $brand->name );
    }
    $menu .= sprintf( $parentformat, $cat_link, $cat_data['name'], $items );
  }
  // print the menu
  printf( $format, $menu );
}

あなたがメニューを出力したいあなたのテンプレートで、ちょうどそのように真新しいテンプレートタグを使用してください:

<?php category_brand_menu(); ?>

これで終わりです。


上記のすべての関数で、2つの変数を設定したことに注意してください。

 $category_slug = 'category';
 $brand_slug = 'brand';

私のコードをテストする前に、すべての関数に適切なスラッグを設定するようにしてください。

3
gmazzap

私は単一のMySQLクエリですべてのカテゴリ/ブランドの組み合わせを取得する方法を考え出しました。

次に、組み合わせをループして、すべてのカテゴリとそれに関連するブランドの配列を作成します。これがクエリです:

$brands_categories = $wpdb->get_results("
    SELECT te2.term_id AS category_id, te2.slug AS category_slug, te2.name AS category_name, t2.parent AS category_parent_id, te1.slug AS brand_slug, te1.name AS brand_name
    FROM wp_posts p

    LEFT JOIN wp_term_relationships r1 ON p.ID = r1.object_id
    CROSS JOIN wp_term_taxonomy t1 ON r1.term_taxonomy_id = t1.term_taxonomy_id AND t1.taxonomy = 'brands'
    LEFT JOIN wp_terms te1 ON t1.term_id = te1.term_id

    LEFT JOIN wp_term_relationships r2 ON p.ID = r2.object_id
    CROSS JOIN wp_term_taxonomy t2 ON r2.term_taxonomy_id = t2.term_taxonomy_id AND t2.taxonomy = 'product_cat'
    LEFT JOIN wp_terms te2 ON t2.term_id = te2.term_id

    WHERE p.post_status = 'publish' and p.post_type = 'product'

    GROUP BY te2.slug, te1.slug

    ORDER BY te2.slug ASC
", ARRAY_A);

基本的に

category_id
category_slug
category_name
brand_slug
brand_name

すべての組み合わせに対して.

次に、ブランドをサブ配列としてリストしながら、ループ処理をしてきれいなカテゴリの配列を作成します。

foreach ($brands_categories as $brand_category) {
    if (!is_array($categories[$brand_category['category_id']])) {
        $categories[$brand_category['category_id']] = array(
            "slug" => $brand_category['category_slug'],
            "name" => $brand_category['category_name'],
            "parent" => $brand_category['category_parent_id'],
            "brands" => array()
        );
    }
    $categories[$brand_category['category_id']]['brands'][] = array(
        "slug" => $brand_category['brand_slug'],
        "name" => $brand_category['brand_name']
    );
}

助けてくれてありがとう!これは間違いなく、はるかに複雑でした。これはカスタムのphp/mysql設定では非常に直接的な問題です。

2
Charles