親子関係の配列を階層ごと取得したい

PHPの再帰処理でカテゴリ階層を取得する方法という前回の記事でカテゴリーの再帰処理を実装しました。WordPressの記事表示時に、その記事が属しているカテゴリーの階層(リンク+カテゴリー名)を取得するために考えたのですが、どうやらWordPressにはすでにそういう関数が準備されているようなので、そちらを採用することにしました。今回はそのまとめ。

get_the_terms関数で記事が属しているカテゴリーを取得

まず、投稿ページ(is_single関数判定)で記事が属しているカテゴリーのデータをget_the_terms関数で取得します。

global $post;
if ( is_single() ) {
    $terms = get_the_terms( $post->ID, 'category' );
}
カテゴリー設定例
カテゴリー設定例

カテゴリー設定例のような記事のカテゴリーを取得した場合、以下のようなデータが返ってきます。

array (size=5)
  0 => 
    object(WP_Term)[991]
      public 'term_id' => int 22
      public 'name' => string 'カテゴリー 1' (length=17)
      public 'slug' => string '%e3%82%ab%e3%83%86%e3%82%b4%e3%83%aa%e3%83%bc-1' (length=47)
      public 'term_group' => int 0
      public 'term_taxonomy_id' => int 22
      public 'taxonomy' => string 'category' (length=8)
      public 'description' => string '' (length=0)
      public 'parent' => int 0
      public 'count' => int 2
      public 'filter' => string 'raw' (length=3)
  1 => 
    object(WP_Term)[992]
      public 'term_id' => int 23
      public 'name' => string 'カテゴリー 2' (length=17)
      public 'slug' => string '%e3%82%ab%e3%83%86%e3%82%b4%e3%83%aa%e3%83%bc-2' (length=47)
      public 'term_group' => int 0
      public 'term_taxonomy_id' => int 23
      public 'taxonomy' => string 'category' (length=8)
      public 'description' => string '' (length=0)
      public 'parent' => int 22
      public 'count' => int 2
      public 'filter' => string 'raw' (length=3)
  2 => 
    object(WP_Term)[993]
      public 'term_id' => int 67
      public 'name' => string '子カテゴリー 01' (length=21)
      public 'slug' => string 'child-category-01' (length=17)
      public 'term_group' => int 0
      public 'term_taxonomy_id' => int 67
      public 'taxonomy' => string 'category' (length=8)
      public 'description' => string 'This is a description for the Child Category 01.' (length=48)
      public 'parent' => int 62
      public 'count' => int 2
      public 'filter' => string 'raw' (length=3)
  3 => 
    object(WP_Term)[994]
      public 'term_id' => int 154
      public 'name' => string '第3階層カテゴリー' (length=27)
      public 'slug' => string '%e7%ac%ac%ef%bc%93%e9%9a%8e%e5%b1%a4%e3%82%ab%e3%83%86%e3%82%b4%e3%83%aa%e3%83%bc' (length=81)
      public 'term_group' => int 0
      public 'term_taxonomy_id' => int 154
      public 'taxonomy' => string 'category' (length=8)
      public 'description' => string '' (length=0)
      public 'parent' => int 23
      public 'count' => int 1
      public 'filter' => string 'raw' (length=3)
  4 => 
    object(WP_Term)[995]
      public 'term_id' => int 62
      public 'name' => string '親カテゴリー' (length=18)
      public 'slug' => string '%e8%a6%aa%e3%82%ab%e3%83%86%e3%82%b4%e3%83%aa%e3%83%bc' (length=54)
      public 'term_group' => int 0
      public 'term_taxonomy_id' => int 62
      public 'taxonomy' => string 'category' (length=8)
      public 'description' => string '' (length=0)
      public 'parent' => int 0
      public 'count' => int 2
      public 'filter' => string 'raw' (length=3)

term_idが自分自身のユニークID、parentが親IDを指します。この2つの列を使用して、カテゴリー階層の取得を行います。

最下層のカテゴリーIDだけの配列を作成

前回の記事と同様に親子関係のカテゴリーから親カテゴリー配列を作成して、最下層カテゴリーを抽出します。

$terms       = get_the_terms( $post->ID, 'category' );
$term_bottom = array();

if ( $terms && ! is_wp_error( $terms ) ) {
	$parent_ids  = array();

	foreach ( $terms as $term ) {
		if ( $term->parent != 0 ) {
			$parent_ids[] = $term->parent;
		}
	}
	foreach ( $terms as $term ) {
		if ( !in_array( $term->term_id, $parent_ids ) ) {
			$term_bottom[] = $term->term_id;
		}
	}
}

get_the_terms関数の戻り値は、WP_Errorオブジェクトを返すので、4行目のように、is_wp_errorでエラーでないかの判定をします。Trueであれば7〜11行目で親IDの配列生成をし、その後12〜16行目で、最下層のカテゴリー配列を生成します。

get_ancestors関数

前回の記事では、ここから再帰処理用の関数を作って……という流れでしたが、ここでWordPressのget_ancestors関数を使用します。以下、引用。

指定されたオブジェクトの祖先オブジェクトの配列を返します。祖先オブジェクトの種類を第2引数で指定します。カスタム投稿タイプやカスタムタクソノミーの場合は第3引数も使います。

WordPress Codex 関数リファレンス/get ancestors より引用

要はカテゴリーの番号を引数で渡すとその親階層を全て取得してくれるというステキ関数です。先程、最下層のカテゴリー配列を作成したので、それをループしてget_ancestors関数に渡してあげれば取得できます。

if ( count( $term_bottom ) > 0 ) {
	$ancestors   = array_reverse( get_ancestors( $term_bottom[0], 'category' ) );
	$ancestors[] = $term_bottom[0];

	foreach ( $ancestors as $ancestor ) {
		$item_array[] = get_cat_name( $ancestor );
	}
}

これで$term_array変数に配列としてカテゴリー名が格納されます。2行目の$term_bottom[0]と添字を「0」と渡しているのは、パンくずリストは複数必要ないからです。実際は、例題のデータで実験すると以下のようなカテゴリーが取得できます。(カテゴリーの名前横のカッコは自身のID)

  • カテゴリー 1(22) > カテゴリー 2(23) > 第3階層カテゴリー(154)
  • 親カテゴリー(62) > 子カテゴリー 01(67)

$term_bottom[0]には、「154」、$term_bottom[1]には「67」が格納されている状態です。どちらのカテゴリーを優先するかというのは難しいので、配列の最初のデータを優先するようにしました。

パンくずリスト自体は、1記事につき1つの構造が望ましいと僕は思います。公開しているプラグインMarkup (JSON-LD) structured in schema.orgでもこの方式を採用しました。

ちょっと前の動画ですが、複数のパンくずをもたせること自体が悪ではないようです。あくまで構造的に正しければ問題ないとのこと。動画でも発言していますが、極端に20カテゴリーに属しているとかそういうのはなしだよねと言っています。ちょっと対応するかどうか悩みどころ。