【WordPress】Walker クラスを使用してツリー構造を走査する

Walker は、WordPress コアがツリー状のデータ構造をレンダリングする際の動作を定義するための「抽象クラス」です。

WordPress コアではデータの種類ごとに Walker を継承したクラスが定義されており、デベロッパーも Walker を継承することで独自の動作を定義することができます。

Walker のプロパティとメソッド

Walker は抽象クラスです。

タップして Walker 抽象クラスのソースコードを確認…

以下、WorePress コアの中で定義されている Walker クラスのソースコードを転載しています。ファイルは、wp-includes/ の中にあります。

class-wp-walker.php
  1. <?php
  2.  
  3. /**
  4. * A class for displaying various tree-like structures.
  5. *
  6. * Extend the Walker class to use it, see examples below. Child classes
  7. * do not need to implement all of the abstract methods in the class. The child
  8. * only needs to implement the methods that are needed.
  9. *
  10. * @since 2.1.0
  11. *
  12. * @package WordPress
  13. * @abstract
  14. */
  15. class Walker
  16. {
  17. /**
  18. * What the class handles.
  19. *
  20. * @since 2.1.0
  21. * @var string
  22. */
  23. public $tree_type;
  24.  
  25. /**
  26. * DB fields to use.
  27. *
  28. * @since 2.1.0
  29. * @var array
  30. */
  31. public $db_fields;
  32.  
  33. /**
  34. * Max number of pages walked by the paged walker
  35. *
  36. * @since 2.7.0
  37. * @var int
  38. */
  39. public $max_pages = 1;
  40.  
  41. /**
  42. * Whether the current element has children or not.
  43. *
  44. * To be used in start_el().
  45. *
  46. * @since 4.0.0
  47. * @var bool
  48. */
  49. public $has_children;
  50.  
  51. /**
  52. * Starts the list before the elements are added.
  53. *
  54. * The $args parameter holds additional values that may be used with the child
  55. * class methods. This method is called at the start of the output list.
  56. *
  57. * @since 2.1.0
  58. * @abstract
  59. *
  60. * @param string $output Used to append additional content (passed by reference).
  61. * @param int $depth Depth of the item.
  62. * @param array $args An array of additional arguments.
  63. */
  64. public function start_lvl(&$output, $depth = 0, $args = [])
  65. {
  66. }
  67.  
  68. /**
  69. * Ends the list of after the elements are added.
  70. *
  71. * The $args parameter holds additional values that may be used with the child
  72. * class methods. This method finishes the list at the end of output of the elements.
  73. *
  74. * @since 2.1.0
  75. * @abstract
  76. *
  77. * @param string $output Used to append additional content (passed by reference).
  78. * @param int $depth Depth of the item.
  79. * @param array $args An array of additional arguments.
  80. */
  81. public function end_lvl(&$output, $depth = 0, $args = [])
  82. {
  83. }
  84.  
  85. /**
  86. * Start the element output.
  87. *
  88. * The $args parameter holds additional values that may be used with the child
  89. * class methods. Includes the element output also.
  90. *
  91. * @since 2.1.0
  92. * @abstract
  93. *
  94. * @param string $output Used to append additional content (passed by reference).
  95. * @param object $object The data object.
  96. * @param int $depth Depth of the item.
  97. * @param array $args An array of additional arguments.
  98. * @param int $current_object_id ID of the current item.
  99. */
  100. public function start_el(&$output, $object, $depth = 0, $args = [], $current_object_id = 0)
  101. {
  102. }
  103.  
  104. /**
  105. * Ends the element output, if needed.
  106. *
  107. * The $args parameter holds additional values that may be used with the child class methods.
  108. *
  109. * @since 2.1.0
  110. * @abstract
  111. *
  112. * @param string $output Used to append additional content (passed by reference).
  113. * @param object $object The data object.
  114. * @param int $depth Depth of the item.
  115. * @param array $args An array of additional arguments.
  116. */
  117. public function end_el(&$output, $object, $depth = 0, $args = [])
  118. {
  119. }
  120.  
  121. /**
  122. * Traverse elements to create list from elements.
  123. *
  124. * Display one element if the element doesn't have any children otherwise,
  125. * display the element and its children. Will only traverse up to the max
  126. * depth and no ignore elements under that depth. It is possible to set the
  127. * max depth to include all depths, see walk() method.
  128. *
  129. * This method should not be called directly, use the walk() method instead.
  130. *
  131. * @since 2.5.0
  132. *
  133. * @param object $element Data object.
  134. * @param array $children_elements List of elements to continue traversing (passed by reference).
  135. * @param int $max_depth Max depth to traverse.
  136. * @param int $depth Depth of current element.
  137. * @param array $args An array of arguments.
  138. * @param string $output Used to append additional content (passed by reference).
  139. */
  140. public function display_element($element, &$children_elements, $max_depth, $depth, $args, &$output)
  141. {
  142. if (!$element) {
  143. return;
  144. }
  145.  
  146. $id_field = $this->db_fields['id'];
  147. $id = $element->$id_field;
  148.  
  149. //display this element
  150. $this->has_children = !empty($children_elements[$id]);
  151. if (isset($args[0]) && is_array($args[0])) {
  152. $args[0]['has_children'] = $this->has_children; // Back-compat.
  153. }
  154.  
  155. $cb_args = array_merge([&$output, $element, $depth], $args);
  156. call_user_func_array([$this, 'start_el'], $cb_args);
  157.  
  158. // descend only when the depth is right and there are childrens for this element
  159. if (($max_depth == 0 || $max_depth > $depth + 1) && isset($children_elements[$id])) {
  160.  
  161. foreach ($children_elements[$id] as $child) {
  162.  
  163. if (!isset($newlevel)) {
  164. $newlevel = true;
  165. //start the child delimiter
  166. $cb_args = array_merge([&$output, $depth], $args);
  167. call_user_func_array([$this, 'start_lvl'], $cb_args);
  168. }
  169. $this->display_element($child, $children_elements, $max_depth, $depth + 1, $args, $output);
  170. }
  171. unset($children_elements[$id]);
  172. }
  173.  
  174. if (isset($newlevel) && $newlevel) {
  175. //end the child delimiter
  176. $cb_args = array_merge([&$output, $depth], $args);
  177. call_user_func_array([$this, 'end_lvl'], $cb_args);
  178. }
  179.  
  180. //end this element
  181. $cb_args = array_merge([&$output, $element, $depth], $args);
  182. call_user_func_array([$this, 'end_el'], $cb_args);
  183. }
  184.  
  185. /**
  186. * Display array of elements hierarchically.
  187. *
  188. * Does not assume any existing order of elements.
  189. *
  190. * $max_depth = -1 means flatly display every element.
  191. * $max_depth = 0 means display all levels.
  192. * $max_depth > 0 specifies the number of display levels.
  193. *
  194. * @since 2.1.0
  195. *
  196. * @param array $elements An array of elements.
  197. * @param int $max_depth The maximum hierarchical depth.
  198. *
  199. * @return string The hierarchical item output.
  200. */
  201. public function walk($elements, $max_depth)
  202. {
  203. $args = array_slice(func_get_args(), 2);
  204. $output = '';
  205.  
  206. //invalid parameter or nothing to walk
  207. if ($max_depth < -1 || empty($elements)) {
  208. return $output;
  209. }
  210.  
  211. $parent_field = $this->db_fields['parent'];
  212.  
  213. // flat display
  214. if (-1 == $max_depth) {
  215. $empty_array = [];
  216. foreach ($elements as $e) {
  217. $this->display_element($e, $empty_array, 1, 0, $args, $output);
  218. }
  219.  
  220. return $output;
  221. }
  222.  
  223. /*
  224. * Need to display in hierarchical order.
  225. * Separate elements into two buckets: top level and children elements.
  226. * Children_elements is two dimensional array, eg.
  227. * Children_elements[10][] contains all sub-elements whose parent is 10.
  228. */
  229. $top_level_elements = [];
  230. $children_elements = [];
  231. foreach ($elements as $e) {
  232. if (empty($e->$parent_field)) {
  233. $top_level_elements[] = $e;
  234. } else {
  235. $children_elements[$e->$parent_field][] = $e;
  236. }
  237. }
  238.  
  239. /*
  240. * When none of the elements is top level.
  241. * Assume the first one must be root of the sub elements.
  242. */
  243. if (empty($top_level_elements)) {
  244.  
  245. $first = array_slice($elements, 0, 1);
  246. $root = $first[0];
  247.  
  248. $top_level_elements = [];
  249. $children_elements = [];
  250. foreach ($elements as $e) {
  251. if ($root->$parent_field == $e->$parent_field) {
  252. $top_level_elements[] = $e;
  253. } else {
  254. $children_elements[$e->$parent_field][] = $e;
  255. }
  256. }
  257. }
  258.  
  259. foreach ($top_level_elements as $e) {
  260. $this->display_element($e, $children_elements, $max_depth, 0, $args, $output);
  261. }
  262.  
  263. /*
  264. * If we are displaying all levels, and remaining children_elements is not empty,
  265. * then we got orphans, which should be displayed regardless.
  266. */
  267. if (($max_depth == 0) && count($children_elements) > 0) {
  268. $empty_array = [];
  269. foreach ($children_elements as $orphans) {
  270. foreach ($orphans as $op) {
  271. $this->display_element($op, $empty_array, 1, 0, $args, $output);
  272. }
  273. }
  274. }
  275.  
  276. return $output;
  277. }
  278.  
  279. /**
  280. * paged_walk() - produce a page of nested elements
  281. *
  282. * Given an array of hierarchical elements, the maximum depth, a specific page number,
  283. * and number of elements per page, this function first determines all top level root elements
  284. * belonging to that page, then lists them and all of their children in hierarchical order.
  285. *
  286. * $max_depth = 0 means display all levels.
  287. * $max_depth > 0 specifies the number of display levels.
  288. *
  289. * @since 2.7.0
  290. *
  291. * @param array $elements
  292. * @param int $max_depth The maximum hierarchical depth.
  293. * @param int $page_num The specific page number, beginning with 1.
  294. * @param int $per_page
  295. *
  296. * @return string XHTML of the specified page of elements
  297. */
  298. public function paged_walk($elements, $max_depth, $page_num, $per_page)
  299. {
  300. if (empty($elements) || $max_depth < -1) {
  301. return '';
  302. }
  303.  
  304. $args = array_slice(func_get_args(), 4);
  305. $output = '';
  306.  
  307. $parent_field = $this->db_fields['parent'];
  308.  
  309. $count = -1;
  310. if (-1 == $max_depth) {
  311. $total_top = count($elements);
  312. }
  313. if ($page_num < 1 || $per_page < 0) {
  314. // No paging
  315. $paging = false;
  316. $start = 0;
  317. if (-1 == $max_depth) {
  318. $end = $total_top;
  319. }
  320. $this->max_pages = 1;
  321. } else {
  322. $paging = true;
  323. $start = ((int)$page_num - 1) * (int)$per_page;
  324. $end = $start + $per_page;
  325. if (-1 == $max_depth) {
  326. $this->max_pages = ceil($total_top / $per_page);
  327. }
  328. }
  329.  
  330. // flat display
  331. if (-1 == $max_depth) {
  332. if (!empty($args[0]['reverse_top_level'])) {
  333. $elements = array_reverse($elements);
  334. $oldstart = $start;
  335. $start = $total_top - $end;
  336. $end = $total_top - $oldstart;
  337. }
  338.  
  339. $empty_array = [];
  340. foreach ($elements as $e) {
  341. $count++;
  342. if ($count < $start) {
  343. continue;
  344. }
  345. if ($count >= $end) {
  346. break;
  347. }
  348. $this->display_element($e, $empty_array, 1, 0, $args, $output);
  349. }
  350.  
  351. return $output;
  352. }
  353.  
  354. /*
  355. * Separate elements into two buckets: top level and children elements.
  356. * Children_elements is two dimensional array, e.g.
  357. * $children_elements[10][] contains all sub-elements whose parent is 10.
  358. */
  359. $top_level_elements = [];
  360. $children_elements = [];
  361. foreach ($elements as $e) {
  362. if (0 == $e->$parent_field) {
  363. $top_level_elements[] = $e;
  364. } else {
  365. $children_elements[$e->$parent_field][] = $e;
  366. }
  367. }
  368.  
  369. $total_top = count($top_level_elements);
  370. if ($paging) {
  371. $this->max_pages = ceil($total_top / $per_page);
  372. } else {
  373. $end = $total_top;
  374. }
  375.  
  376. if (!empty($args[0]['reverse_top_level'])) {
  377. $top_level_elements = array_reverse($top_level_elements);
  378. $oldstart = $start;
  379. $start = $total_top - $end;
  380. $end = $total_top - $oldstart;
  381. }
  382. if (!empty($args[0]['reverse_children'])) {
  383. foreach ($children_elements as $parent => $children) {
  384. $children_elements[$parent] = array_reverse($children);
  385. }
  386. }
  387.  
  388. foreach ($top_level_elements as $e) {
  389. $count++;
  390.  
  391. // For the last page, need to unset earlier children in order to keep track of orphans.
  392. if ($end >= $total_top && $count < $start) {
  393. $this->unset_children($e, $children_elements);
  394. }
  395.  
  396. if ($count < $start) {
  397. continue;
  398. }
  399.  
  400. if ($count >= $end) {
  401. break;
  402. }
  403.  
  404. $this->display_element($e, $children_elements, $max_depth, 0, $args, $output);
  405. }
  406.  
  407. if ($end >= $total_top && count($children_elements) > 0) {
  408. $empty_array = [];
  409. foreach ($children_elements as $orphans) {
  410. foreach ($orphans as $op) {
  411. $this->display_element($op, $empty_array, 1, 0, $args, $output);
  412. }
  413. }
  414. }
  415.  
  416. return $output;
  417. }
  418.  
  419. /**
  420. * Calculates the total number of root elements.
  421. *
  422. * @since 2.7.0
  423. *
  424. * @param array $elements Elements to list.
  425. *
  426. * @return int Number of root elements.
  427. */
  428. public function get_number_of_root_elements($elements)
  429. {
  430. $num = 0;
  431. $parent_field = $this->db_fields['parent'];
  432.  
  433. foreach ($elements as $e) {
  434. if (0 == $e->$parent_field) {
  435. $num++;
  436. }
  437. }
  438.  
  439. return $num;
  440. }
  441.  
  442. /**
  443. * Unset all the children for a given top level element.
  444. *
  445. * @since 2.7.0
  446. *
  447. * @param object $e
  448. * @param array $children_elements
  449. */
  450. public function unset_children($e, &$children_elements)
  451. {
  452. if (!$e || !$children_elements) {
  453. return;
  454. }
  455.  
  456. $id_field = $this->db_fields['id'];
  457. $id = $e->$id_field;
  458.  
  459. if (!empty($children_elements[$id]) && is_array($children_elements[$id])) {
  460. foreach ((array)$children_elements[$id] as $child) {
  461. $this->unset_children($child, $children_elements);
  462. }
  463. }
  464.  
  465. unset($children_elements[$id]);
  466. }
  467.  
  468. } // Walker

Walker には以下のプロパティやメソッドが含まれています。

プロパティ

$tree_type
クラスが処理するもの。例えば、categorycomment など。
$db_fields
親 ID、項目 IDの指定。
$max_pages
paged_walk() メソッドに使用される。
$has_children
display_element() メソッドに使用される。

カスタマイズの際、これら項目を意識することはほとんどないでしょう。意味が理解できなくても問題ありません。

抽象メソッド

4つの抽象メソッドをどのように実装するかで動作が決まります(なので重要です!)。それぞれのメソッドは、Walker がツリーを移動する際に、レベルまたは要素に応じて開始また終了のタイミングでレンダリングを行います。

レベルはすなわちブランチです。HTMLでいうところの <ul><ol> になりますが、レンダリングはこれらの要素でなくてもOKです。

一方、要素はツリーの項目です。HTMLでいうところの <li> になります。レベル同様こちらも <li> 以外を使うことができます。

function start_lvl(&$output, $depth = 0, $args = [])
開始レベル。一般的には、<ul><ol><div>
function end_lvl(&$output, $depth = 0, $args = [])
終了レベル。一般的には、</ul></ol></div>
function start_el(&$output, $object, $depth = 0, $args = [], $current_object_id = 0)
開始要素。一般的には、<li> <span><a>
function end_el(&$output, $object, $depth = 0, $args = [])
終了要素。一般的には、</li></span></a>

パブリックメソッド

自らパブリックメソッドを使用することも可能ですが、基本的に使用しないでしょう。プロパティと同様に重要ではありません。

function walk($elements, $max_depth)
ツリー構造を表示します。
function paged_walk( $elements, $max_depth, $page_num, $per_page )
ツリー構造を表示します。これは、ページ機能を含みます。
function get_number_of_root_elements( $elements )
ルート要素の総数を計算します。
function unset_children( $e, &$children_elements )
指定されたトップレベル要素のすべての子を削除します。

コアで定義されている Walker 派生のクラス

WordPressコアでは既にいくつかの Walker から派生したクラスが定義されており、WordPress 関数で利用されています。

class Walker_Category
テンプレートタグ wp_list_categories() での走査を定義しており、独自の Walker を指定することもできます。
class Walker_CategoryDropdown
テンプレートタグ wp_dropdown_categories() での走査を定義しています。
class Walker_Comment
テンプレートタグ wp_list_comments() での走査を定義しており、独自の Walker を指定することもできます。
class Walker_Nav_Menu
テンプレートタグ wp_nav_menu() での走査を定義しており、独自の Walker を指定することもできます。
class Walker_Page
テンプレートタグ wp_list_pages() での走査を定義しており、独自の Walker を指定することもできます。
class Walker_PageDropdown
テンプレートタグ wp_dropdown_pages() での走査を定義しています。

独自の Walker を作成して使う手順

前述のとおり、いくつかのテンプレートタグはコアが定義している Walker を内部で使用しており、引数で独自の Walker を指定して動作を変更できるようになっています。ここでは、ナビゲーションメニューを表示するテンプレートタグ wp_nav_menu() を、独自の Walker を作成して動作を変更する流れを紹介します。

テンプレートタグ/wp nav menu

WordPress Codex 日本語版のドキュメントページに詳細が載っていますが、wp_nav_menu() は引数に配列で様々なパラメーターを指定することでカスタマイズできます。そのうち、'walker' パラメーターに Walker オブジェクトを指定するとツリー走査の動作を変更することができます。

デフォルトの wp_nav_menu()
  1. <?php
  2. wp_nav_menu([
  3. 'menu' => '',
  4. 'menu_class' => 'menu',
  5. 'menu_id' => '{メニューのスラッグ}-{連番}',
  6. 'container' => 'div',
  7. 'container_class' => 'menu-{メニューのスラッグ}-container',
  8. 'container_id' => '',
  9. 'fallback_cb' => 'wp_page_menu',
  10. 'before' => '',
  11. 'after' => '',
  12. 'link_before' => '',
  13. 'link_after' => '',
  14. 'echo' => true,
  15. 'depth' => 0,
  16. 'walker' => new Walker_Nav_Menu(),
  17. 'theme_location' => '',
  18. 'items_wrap' => '<ul id="%1$s" class="%2$s">%3$s</ul>',
  19. ]);

カスタムウォーカーは Walker クラスを継承して作成してもいいのですが、Walker_Nav_Menu を継承してオーバーライドしていった方が賢明です。なぜなら、4つのメソッドを記述するだけで済むからです。

このように…

  1. <?php
  2.  
  3. class MyWalkerNavMenu extends Walker_Nav_Menu
  4. {
  5. public function start_lvl(&$output, $depth = 0, $args = [])
  6. {
  7. $output .= '<ul class="nav-list">';
  8. }
  9.  
  10. public function end_lvl(&$output, $depth = 0, $args = [])
  11. {
  12. $output .= '</ul>';
  13. }
  14.  
  15. function start_el(&$output, $item, $depth = 0, $args = [], $id = 0)
  16. {
  17. $output .= '<li class="nav-item">';
  18. $output .= sprintf(
  19. '%1$s<a%2$s>%3$s%4$s%5$s</a>%6$s',
  20. $args->before,
  21. ' href="' . esc_url(($item->url)) . '"',
  22. $args->link_before,
  23. $item->title,
  24. $args->link_after,
  25. $args->after
  26. );
  27. }
  28.  
  29. public function end_el(&$output, $item, $depth = 0, $args = [])
  30. {
  31. $output .= '</li>';
  32. }
  33. }

これは実用性がない単純な例ですが、記述の仕方が理解し易くなっていると思います。

ここで、実際のWalker_Nav_Menu のソースコードが気になる場合は下記を参照してください。もっと複雑な作りになっていますが、基本は上記のものと同じです。

タップして Walker_Nav_Menu クラスのソースコードを確認…
class-walker-nav-menu.php
  1. <?php
  2. /**
  3. * Nav Menu API: Walker_Nav_Menu class
  4. *
  5. * @package WordPress
  6. * @subpackage Nav_Menus
  7. * @since 4.6.0
  8. */
  9.  
  10. /**
  11. * Core class used to implement an HTML list of nav menu items.
  12. *
  13. * @since 3.0.0
  14. *
  15. * @see Walker
  16. */
  17. class Walker_Nav_Menu extends Walker {
  18. /**
  19. * What the class handles.
  20. *
  21. * @since 3.0.0
  22. * @var string
  23. *
  24. * @see Walker::$tree_type
  25. */
  26. public $tree_type = array( 'post_type', 'taxonomy', 'custom' );
  27.  
  28. /**
  29. * Database fields to use.
  30. *
  31. * @since 3.0.0
  32. * @todo Decouple this.
  33. * @var array
  34. *
  35. * @see Walker::$db_fields
  36. */
  37. public $db_fields = array(
  38. 'parent' => 'menu_item_parent',
  39. 'id' => 'db_id',
  40. );
  41.  
  42. /**
  43. * Starts the list before the elements are added.
  44. *
  45. * @since 3.0.0
  46. *
  47. * @see Walker::start_lvl()
  48. *
  49. * @param string $output Used to append additional content (passed by reference).
  50. * @param int $depth Depth of menu item. Used for padding.
  51. * @param stdClass $args An object of wp_nav_menu() arguments.
  52. */
  53. public function start_lvl( &$output, $depth = 0, $args = null ) {
  54. if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
  55. $t = '';
  56. $n = '';
  57. } else {
  58. $t = "\t";
  59. $n = "\n";
  60. }
  61. $indent = str_repeat( $t, $depth );
  62.  
  63. // Default class.
  64. $classes = array( 'sub-menu' );
  65.  
  66. /**
  67. * Filters the CSS class(es) applied to a menu list element.
  68. *
  69. * @since 4.8.0
  70. *
  71. * @param string[] $classes Array of the CSS classes that are applied to the menu `<ul>` element.
  72. * @param stdClass $args An object of `wp_nav_menu()` arguments.
  73. * @param int $depth Depth of menu item. Used for padding.
  74. */
  75. $class_names = join( ' ', apply_filters( 'nav_menu_submenu_css_class', $classes, $args, $depth ) );
  76. $class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
  77.  
  78. $output .= "{$n}{$indent}<ul$class_names>{$n}";
  79. }
  80.  
  81. /**
  82. * Ends the list of after the elements are added.
  83. *
  84. * @since 3.0.0
  85. *
  86. * @see Walker::end_lvl()
  87. *
  88. * @param string $output Used to append additional content (passed by reference).
  89. * @param int $depth Depth of menu item. Used for padding.
  90. * @param stdClass $args An object of wp_nav_menu() arguments.
  91. */
  92. public function end_lvl( &$output, $depth = 0, $args = null ) {
  93. if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
  94. $t = '';
  95. $n = '';
  96. } else {
  97. $t = "\t";
  98. $n = "\n";
  99. }
  100. $indent = str_repeat( $t, $depth );
  101. $output .= "$indent</ul>{$n}";
  102. }
  103.  
  104. /**
  105. * Starts the element output.
  106. *
  107. * @since 3.0.0
  108. * @since 4.4.0 The {@see 'nav_menu_item_args'} filter was added.
  109. *
  110. * @see Walker::start_el()
  111. *
  112. * @param string $output Used to append additional content (passed by reference).
  113. * @param WP_Post $item Menu item data object.
  114. * @param int $depth Depth of menu item. Used for padding.
  115. * @param stdClass $args An object of wp_nav_menu() arguments.
  116. * @param int $id Current item ID.
  117. */
  118. public function start_el( &$output, $item, $depth = 0, $args = null, $id = 0 ) {
  119. if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
  120. $t = '';
  121. $n = '';
  122. } else {
  123. $t = "\t";
  124. $n = "\n";
  125. }
  126. $indent = ( $depth ) ? str_repeat( $t, $depth ) : '';
  127.  
  128. $classes = empty( $item->classes ) ? array() : (array) $item->classes;
  129. $classes[] = 'menu-item-' . $item->ID;
  130.  
  131. /**
  132. * Filters the arguments for a single nav menu item.
  133. *
  134. * @since 4.4.0
  135. *
  136. * @param stdClass $args An object of wp_nav_menu() arguments.
  137. * @param WP_Post $item Menu item data object.
  138. * @param int $depth Depth of menu item. Used for padding.
  139. */
  140. $args = apply_filters( 'nav_menu_item_args', $args, $item, $depth );
  141.  
  142. /**
  143. * Filters the CSS classes applied to a menu item's list item element.
  144. *
  145. * @since 3.0.0
  146. * @since 4.1.0 The `$depth` parameter was added.
  147. *
  148. * @param string[] $classes Array of the CSS classes that are applied to the menu item's `<li>` element.
  149. * @param WP_Post $item The current menu item.
  150. * @param stdClass $args An object of wp_nav_menu() arguments.
  151. * @param int $depth Depth of menu item. Used for padding.
  152. */
  153. $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) );
  154. $class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
  155.  
  156. /**
  157. * Filters the ID applied to a menu item's list item element.
  158. *
  159. * @since 3.0.1
  160. * @since 4.1.0 The `$depth` parameter was added.
  161. *
  162. * @param string $menu_id The ID that is applied to the menu item's `<li>` element.
  163. * @param WP_Post $item The current menu item.
  164. * @param stdClass $args An object of wp_nav_menu() arguments.
  165. * @param int $depth Depth of menu item. Used for padding.
  166. */
  167. $id = apply_filters( 'nav_menu_item_id', 'menu-item-' . $item->ID, $item, $args, $depth );
  168. $id = $id ? ' id="' . esc_attr( $id ) . '"' : '';
  169.  
  170. $output .= $indent . '<li' . $id . $class_names . '>';
  171.  
  172. $atts = array();
  173. $atts['title'] = ! empty( $item->attr_title ) ? $item->attr_title : '';
  174. $atts['target'] = ! empty( $item->target ) ? $item->target : '';
  175. if ( '_blank' === $item->target && empty( $item->xfn ) ) {
  176. $atts['rel'] = 'noopener noreferrer';
  177. } else {
  178. $atts['rel'] = $item->xfn;
  179. }
  180. $atts['href'] = ! empty( $item->url ) ? $item->url : '';
  181. $atts['aria-current'] = $item->current ? 'page' : '';
  182.  
  183. /**
  184. * Filters the HTML attributes applied to a menu item's anchor element.
  185. *
  186. * @since 3.6.0
  187. * @since 4.1.0 The `$depth` parameter was added.
  188. *
  189. * @param array $atts {
  190. * The HTML attributes applied to the menu item's `<a>` element, empty strings are ignored.
  191. *
  192. * @type string $title Title attribute.
  193. * @type string $target Target attribute.
  194. * @type string $rel The rel attribute.
  195. * @type string $href The href attribute.
  196. * @type string $aria_current The aria-current attribute.
  197. * }
  198. * @param WP_Post $item The current menu item.
  199. * @param stdClass $args An object of wp_nav_menu() arguments.
  200. * @param int $depth Depth of menu item. Used for padding.
  201. */
  202. $atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args, $depth );
  203.  
  204. $attributes = '';
  205. foreach ( $atts as $attr => $value ) {
  206. if ( is_scalar( $value ) && '' !== $value && false !== $value ) {
  207. $value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
  208. $attributes .= ' ' . $attr . '="' . $value . '"';
  209. }
  210. }
  211.  
  212. /** This filter is documented in wp-includes/post-template.php */
  213. $title = apply_filters( 'the_title', $item->title, $item->ID );
  214.  
  215. /**
  216. * Filters a menu item's title.
  217. *
  218. * @since 4.4.0
  219. *
  220. * @param string $title The menu item's title.
  221. * @param WP_Post $item The current menu item.
  222. * @param stdClass $args An object of wp_nav_menu() arguments.
  223. * @param int $depth Depth of menu item. Used for padding.
  224. */
  225. $title = apply_filters( 'nav_menu_item_title', $title, $item, $args, $depth );
  226.  
  227. $item_output = $args->before;
  228. $item_output .= '<a' . $attributes . '>';
  229. $item_output .= $args->link_before . $title . $args->link_after;
  230. $item_output .= '</a>';
  231. $item_output .= $args->after;
  232.  
  233. /**
  234. * Filters a menu item's starting output.
  235. *
  236. * The menu item's starting output only includes `$args->before`, the opening `<a>`,
  237. * the menu item's title, the closing `</a>`, and `$args->after`. Currently, there is
  238. * no filter for modifying the opening and closing `<li>` for a menu item.
  239. *
  240. * @since 3.0.0
  241. *
  242. * @param string $item_output The menu item's starting HTML output.
  243. * @param WP_Post $item Menu item data object.
  244. * @param int $depth Depth of menu item. Used for padding.
  245. * @param stdClass $args An object of wp_nav_menu() arguments.
  246. */
  247. $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
  248. }
  249.  
  250. /**
  251. * Ends the element output, if needed.
  252. *
  253. * @since 3.0.0
  254. *
  255. * @see Walker::end_el()
  256. *
  257. * @param string $output Used to append additional content (passed by reference).
  258. * @param WP_Post $item Page data object. Not used.
  259. * @param int $depth Depth of page. Not Used.
  260. * @param stdClass $args An object of wp_nav_menu() arguments.
  261. */
  262. public function end_el( &$output, $item, $depth = 0, $args = null ) {
  263. if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
  264. $t = '';
  265. $n = '';
  266. } else {
  267. $t = "\t";
  268. $n = "\n";
  269. }
  270. $output .= "</li>{$n}";
  271. }
  272.  
  273. } // Walker_Nav_Menu

自作のウォーカーを作成したらパラメーターに指定して使用します。

  1. <?php
  2. wp_nav_menu([
  3. 'walker' => new MyWalkerNavMenu(),
  4. ]);

まとめ

Walker は、WordPress がツリー状のデータ構造を操作する際に使用する抽象クラスです。WordPress コアは、Walker を拡張したクラスをいくつか定義しており、特定のテンプレートタグでそれを使用しています。デベロッパーは、独自の Walker(カスタムウォーカー)を作成することで、それらテンプレートタグの動作を変更することができます。カスタムウォーカーは基本的に4つのメソッドをオーバーライドするだけで作成することができます。