WordPress函数wp_list_pages在递归结构与Walker类中的层级渲染逻辑

WordPress wp_list_pages:递归结构与Walker类中的层级渲染逻辑

大家好,今天我们来深入探讨WordPress函数 wp_list_pages,特别是它在处理页面层级结构以及利用 Walker 类实现层级渲染时的核心逻辑。 wp_list_pages 是一个非常常用的函数,用于生成页面列表,它能够自动处理页面之间的父子关系,并将其以嵌套的HTML列表形式呈现。理解其背后的递归机制和 Walker 类的工作方式对于定制化页面列表输出至关重要。

wp_list_pages 的基本用法与参数

首先,让我们回顾一下 wp_list_pages 的基本用法和常用参数:

<?php
$args = array(
    'depth'        => 0,
    'show_date'    => '',
    'date_format'  => get_option('date_format'),
    'child_of'     => 0,
    'exclude'      => '',
    'exclude_tree' => '',
    'include'      => '',
    'meta_key'     => '',
    'meta_value'   => '',
    'authors'      => '',
    'sort_column'  => 'menu_order, post_title',
    'sort_order'   => '',
    'link_before'  => '',
    'link_after'   => '',
    'walker'       => '',
    'echo'         => 1,
    'title_li'     => __('Pages'),
    'number'       => '',
    'post_type'    => 'page',
    'post_status'  => 'publish'
);

wp_list_pages( $args );
?>

这里是一些关键参数的说明:

  • depth: 指定显示的层级深度。0 表示显示所有层级。-1 也显示所有层级。 1 只显示顶级页面, 2 显示顶级页面和一级子页面,以此类推。
  • child_of: 只显示指定页面ID下的子页面。
  • exclude: 排除显示的页面ID列表,以逗号分隔。
  • include: 只显示指定的页面ID列表,以逗号分隔。
  • sort_column: 排序的依据,默认为 menu_order, post_title,表示先按菜单顺序排序,再按标题排序。
  • walker: 一个 Walker 类的实例,用于定制化输出。这是我们今天讨论的重点。
  • echo: 是否直接输出结果。1 表示输出, 0 表示返回字符串。
  • title_li: 列表的标题。默认为 ‘Pages’。如果设置为空字符串 '',则不显示标题。

递归结构:_wp_page_stylesheet 函数

wp_list_pages 内部依赖于 _wp_page_stylesheet 函数来构建页面层级结构。虽然这个函数不是直接暴露给开发者使用的,但理解它的工作原理有助于我们更好地理解 wp_list_pages

_wp_page_stylesheet 的主要作用是为页面添加 CSS 类,以便于样式化。它通过递归的方式遍历页面树,并根据页面的深度和层级关系添加相应的类。

以下是 _wp_page_stylesheet 的简化逻辑(省略了部分与样式相关的代码):

function _wp_page_stylesheet( $page, $depth = 0, $current_page = false ) {
    static $first = true;

    $css_class = array( 'page_item', 'page-item-' . $page->ID );

    if ( $current_page ) {
        $_current_page = get_post( $current_page );

        if ( ! empty( $_current_page ) ) {
            if ( $page->ID == $current_page ) {
                $css_class[] = 'current_page_item';
            } elseif ( $_current_page->post_parent == $page->ID ) {
                $css_class[] = 'current_page_ancestor';
            } elseif ( in_array( $page->ID, get_post_ancestors( $_current_page ) ) ) {
                $css_class[] = 'current_page_ancestor';
            }
        }
    } elseif ( get_option('page_for_posts') == $page->ID ) {
        $css_class[] = 'current_page_parent';
    }

    if ( $first ) {
        $css_class[] = 'first';
        $first = false;
    }

    $css_class = apply_filters( 'page_css_class', $css_class, $page, $depth, $current_page );

    echo ' class="' . implode( ' ', $css_class ) . '"';
}

这个函数的核心在于递归调用自身,以便遍历整个页面树。它会根据当前页面与目标页面之间的关系,添加相应的 CSS 类,例如 current_page_itemcurrent_page_ancestor 等。

Walker 类:定制化页面列表输出的核心

Walker 类是 WordPress 中用于遍历和输出树状结构的通用类。 wp_list_pages 函数允许我们通过 walker 参数传入一个自定义的 Walker 类实例,从而完全控制页面列表的输出。

Walker 类定义了一系列方法,用于处理树状结构中的每个节点:

  • start_lvl( &$output, $depth = 0, $args = array() ): 在遍历到一个节点的子节点列表之前调用。通常用于输出 <ul> 标签。
  • end_lvl( &$output, $depth = 0, $args = array() ): 在遍历完一个节点的子节点列表之后调用。通常用于输出 </ul> 标签。
  • start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ): 在遍历到一个节点时调用。用于输出列表项的内容,例如 <li> 标签和链接。
  • end_el( &$output, $item, $depth = 0, $args = array() ): 在遍历完一个节点之后调用。用于输出列表项的结束标签,例如 </li> 标签。

要定制化 wp_list_pages 的输出,我们需要创建一个继承自 Walker_Page (或者 Walker,如果需要更底层的控制) 的类,并重写这些方法。

创建自定义 Walker

以下是一个简单的自定义 Walker 类的例子,它会在每个页面链接前添加一个图标:

class My_Custom_Page_Walker extends Walker_Page {
    function start_el( &$output, $page, $depth = 0, $args = array(), $id = 0 ) {
        if ( $depth ) {
            $indent = str_repeat( "t", $depth );
        } else {
            $indent = '';
        }

        $css_class = array( 'page_item', 'page-item-' . $page->ID );

        if ( isset( $args['pages'] ) ) {
            $pages = $args['pages'];
        } else {
            $pages = array();
        }

        if ( ! empty( $pages ) && in_array( $page->ID, $pages ) ) {
            $css_class[] = 'current_page_item';
        } elseif ( get_option('page_for_posts') == $page->ID ) {
            $css_class[] = 'current_page_parent';
        }

        $args['has_children'] = ! empty( $args['walker']->has_children );
        $args['current_page'] = get_the_ID();

        $css_class = apply_filters( 'page_css_class', $css_class, $page, $depth, $args['current_page'] );

        $output .= $indent . '<li class="' . implode( ' ', $css_class ) . '">';

        $attributes = '';
        if ( ! empty( $page->post_title ) ) {
            $attributes .= ' title="' . esc_attr( $page->post_title ) . '"';
        }

        $output .= '<a href="' . get_permalink( $page->ID ) . '"' . $attributes . '>';
        $output .= '<i class="fa fa-file-o"></i> '; // 添加图标
        $output .= apply_filters( 'the_title', $page->post_title, $page->ID );
        $output .= '</a>';
    }
}

在这个例子中,我们重写了 start_el 方法,在输出页面链接之前添加了一个 <i class="fa fa-file-o"></i> 标签,用于显示一个文件图标。 (注意:这里需要你已经引入了Font Awesome或其他图标库)。

要使用这个自定义 Walker 类,我们需要在 wp_list_pages 的参数中指定 walker

$args = array(
    'depth'        => 0,
    'walker'       => new My_Custom_Page_Walker(),
    'title_li'     => ''
);

wp_list_pages( $args );

Walker 类中的层级渲染逻辑

Walker 类通过维护一个内部的 $db_fields 属性来处理层级关系。 Walker_Page 类的 $db_fields 属性通常设置为:

public $db_fields = array(
    'parent' => 'post_parent',
    'id'     => 'ID'
);

这意味着 Walker 类会使用 post_parent 字段来确定页面的父级关系,并使用 ID 字段作为页面的唯一标识符。

Walker::walk() 方法是遍历树状结构的核心。 它接收一个包含所有节点的数组,以及要显示的层级深度。 walk() 方法会递归地遍历数组,并根据 $db_fields 属性确定父子关系。

以下是 Walker::walk() 方法的简化逻辑:

public function walk( $elements, $max_depth ) {
    $args   = array_slice( func_get_args(), 2 );
    $output = '';

    $parent_field = $this->db_fields['parent'];

    // first, sort it into parent posts
    $children = array();
    if ( ! empty( $elements ) ) {
        foreach ( $elements as $e ) {
            $parent = $e->$parent_field;
            if ( ! isset( $children[ $parent ] ) )
                $children[ $parent ] = array();
            $children[ $parent ][] = $e;
        }
    }

    $walker_args = array( $output, $elements, $max_depth, $children, $args );
    $output = call_user_func_array( array( $this, 'display_element' ), $walker_args );

    return $output;
}

这个方法首先将所有元素按照父级关系分组到 $children 数组中。然后,它调用 display_element() 方法来处理每个元素。

display_element() 方法负责调用 start_lvl()start_el()end_el()end_lvl() 方法,并递归地处理子节点。

public function display_element( $output, &$element, $depth, $children_elements, $max_depth, ...$args ) {
    if ( ! $element )
        return;

    $id_field = $this->db_fields['id'];
    $id = $element->$id_field;

    // Display this element.
    if ( is_null( $max_depth ) || ( $max_depth >= $depth ) ) {
        $this->has_children = ! empty( $children_elements[ $id ] );
        $cb_args = array_merge( array( &$output, $element, $depth ), $args);
        call_user_func_array( array( $this, 'start_el' ), $cb_args );

        // Descend only when the depth is right and there are childrens for this element.
        if ( ( $max_depth === false ) || ( ( $max_depth > $depth + 1 ) || ( $max_depth === null ) ) && isset( $children_elements[ $id ] ) ) {

            $next_depth = $depth + 1;
            $cb_args = array_merge( array( &$output, $children_elements[ $id ], $next_depth, $max_depth ), $args );
            call_user_func_array( array( $this, 'display_element' ), $cb_args );
        }

        $cb_args = array_merge( array( &$output, $element, $depth ), $args );
        call_user_func_array( array( $this, 'end_el' ), $cb_args );
    }
}

这个方法首先调用 start_el() 方法来输出当前元素的起始标签和内容。然后,如果当前元素有子节点,并且当前深度小于最大深度,则递归调用 display_element() 方法来处理子节点。最后,调用 end_el() 方法来输出当前元素的结束标签。

深度控制

wp_list_pages 函数通过 depth 参数来控制显示的层级深度。 当 depth0 时,表示显示所有层级。 当 depth 为一个正整数时,表示只显示指定层级的页面。

Walker::walk() 方法会根据 max_depth 参数来判断是否需要递归处理子节点。 如果当前深度大于或等于 max_depth,则不会递归处理子节点。

总结:理解与定制化

wp_list_pages 函数是一个功能强大的函数,用于生成页面列表。通过理解其背后的递归机制和 Walker 类的工作方式,我们可以完全控制页面列表的输出,并实现各种定制化的需求。 通过重写 Walker 类的方法,我们可以添加自定义的 HTML 标签、CSS 类和图标,从而使页面列表更加符合我们的设计要求。

核心逻辑的简要概括

wp_list_pages 利用递归方式构建页面层级结构,并借助 Walker 类实现对页面列表输出的定制化控制。理解 Walker 类的核心方法和 $db_fields 属性对于修改页面列表的HTML结构至关重要。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注