深入理解 WordPress `WP_List_Table` 类的源码:如何通过继承它来构建可扩展的后台列表页面。

大家好!今天咱们来聊聊 WordPress 后台列表的那些事儿!

先来个开场白:有没有觉得 WordPress 后台那些文章列表、用户列表看起来很统一、很舒服?想不想自己也做一个? 今天就带大家深入了解一下 WP_List_Table 这个神奇的类,看看怎么用它来轻松构建可扩展的后台列表页面。

一、 WP_List_Table 是个啥?

WP_List_Table 是 WordPress 内核提供的一个抽象类,专门用来生成后台列表页面的。它封装了分页、排序、批量操作等常见的功能,咱们只需要继承它,然后定义一些特定的方法,就能快速生成一个功能完善的列表页面,省时省力!

二、 准备工作:先建个插件目录!

俗话说,巧妇难为无米之炊,咱们先创建一个插件目录,把代码放进去:

  1. wp-content/plugins/ 目录下创建一个名为 my-custom-list-table 的文件夹。
  2. 在这个文件夹里创建一个名为 my-custom-list-table.php 的文件。

然后在 my-custom-list-table.php 文件里写上插件的基本信息:

<?php
/*
Plugin Name: My Custom List Table
Description: A custom list table example.
Version: 1.0
Author: Your Name
*/

// 安全检查,防止直接访问
if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly.
}

// 引入列表类 (稍后创建)
require_once plugin_dir_path( __FILE__ ) . 'class-my-list-table.php';

// 添加菜单项
add_action( 'admin_menu', 'my_custom_list_table_menu' );
function my_custom_list_table_menu() {
    add_menu_page(
        'My List Table', // 页面标题
        'My List Table', // 菜单标题
        'manage_options', // 权限
        'my-list-table', // 菜单 slug
        'my_custom_list_page' // 回调函数
    );
}

// 列表页面回调函数
function my_custom_list_page() {
    $my_list_table = new My_List_Table();
    echo '<div class="wrap">';
    echo '<h2>My Custom List Table</h2>';
    $my_list_table->prepare_items(); // 准备数据
    $my_list_table->display(); // 显示列表
    echo '</div>';
}

三、 核心代码:继承 WP_List_Table 创建自己的列表类

接下来,才是重头戏!在 my-custom-list-table 目录下创建一个名为 class-my-list-table.php 的文件,然后开始撸代码!

<?php
if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly.
}

if ( ! class_exists( 'WP_List_Table' ) ) {
    require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}

class My_List_Table extends WP_List_Table {

    /**
     * 构造函数,初始化列表
     */
    public function __construct() {
        parent::__construct( [
            'singular' => 'book', // 单数形式的名称,用于消息提示
            'plural'   => 'books', // 复数形式的名称,用于消息提示
            'ajax'     => false  // 是否使用 AJAX 分页
        ] );
    }

    /**
     * 定义列
     *
     * @return array 列的数组,键是列的 slug,值是列的标题
     */
    public function get_columns() {
        $columns = [
            'cb'      => '<input type="checkbox" />', // 复选框列
            'title'   => 'Title',
            'author'  => 'Author',
            'isbn'    => 'ISBN',
            'actions' => 'Actions',
        ];
        return $columns;
    }

    /**
     * 定义可排序的列
     *
     * @return array 可排序列的数组,键是列的 slug,值是排序的参数 (orderby, asc/desc)
     */
    public function get_sortable_columns() {
        $sortable_columns = [
            'title'  => [ 'title', true ],  // true means it's already sorted
            'author' => [ 'author', false ], // false means not sorted
            'isbn'   => [ 'isbn', false ]
        ];
        return $sortable_columns;
    }

    /**
     * 定义批量操作
     *
     * @return array 批量操作的数组,键是操作的 slug,值是操作的标题
     */
    public function get_bulk_actions() {
        $actions = [
            'delete' => 'Delete'
        ];
        return $actions;
    }

    /**
     * 处理复选框列
     *
     * @param object $item 当前行的数据
     *
     * @return string 复选框的 HTML
     */
    public function column_cb( $item ) {
        return sprintf(
            '<input type="checkbox" name="book[]" value="%s" />', $item['ID']
        );
    }

    /**
     * 处理默认列的显示
     *
     * @param object $item        当前行的数据
     * @param string $column_name 列的 slug
     *
     * @return mixed 列的内容
     */
    public function column_default( $item, $column_name ) {
        switch ( $column_name ) {
            case 'author':
            case 'isbn':
                return $item[ $column_name ];
            default:
                return print_r( $item, true ); //Show the whole array for troubleshooting purposes
        }
    }

    /**
     * 处理 'title' 列的显示,添加编辑链接
     *
     * @param object $item 当前行的数据
     *
     * @return string 列的内容
     */
    public function column_title( $item ) {
        $edit_url   = admin_url( 'admin.php?page=my-list-table&action=edit&id=' . $item['ID'] );
        $delete_url = wp_nonce_url( admin_url( 'admin.php?page=my-list-table&action=delete&id=' . $item['ID'] ), 'delete_book_' . $item['ID'] );

        $actions = [
            'edit'   => sprintf( '<a href="%s">Edit</a>', $edit_url ),
            'delete' => sprintf( '<a href="%s">Delete</a>', $delete_url ),
        ];

        return sprintf( '%1$s %2$s',
            /*$1%s*/ $item['title'],
            /*$2%s*/ $this->row_actions( $actions )
        );
    }

    /**
     * 处理 'actions' 列的显示,添加自定义操作链接
     *
     * @param object $item 当前行的数据
     *
     * @return string 列的内容
     */
    public function column_actions( $item ) {
        $view_url = '#'; // 替换成实际的查看链接
        return sprintf( '<a href="%s">View</a>', $view_url );
    }

    /**
     * 处理批量操作
     */
    public function process_bulk_action() {
        if ( 'delete' === $this->current_action() ) {
            $ids = isset( $_REQUEST['book'] ) ? $_REQUEST['book'] : array();
            if ( is_array( $ids ) ) {
                foreach ( $ids as $id ) {
                    // 安全检查,验证 nonce
                    if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'bulk_' . $this->_args['plural'] ) ) {
                        wp_die( 'Security check failed!' );
                    }

                    // 在这里执行删除操作,例如:
                    // delete_book( $id );
                    error_log("Deleting book ID: ".$id); // For debugging purposes.  Replace with actual deletion code.
                }
                echo '<div class="updated"><p>Items deleted!</p></div>';
            }
        }
    }

    /**
     * 准备列表项,包括数据、分页、排序等
     */
    public function prepare_items() {
        $columns  = $this->get_columns();
        $hidden   = array();
        $sortable = $this->get_sortable_columns();

        $this->_column_headers = array( $columns, $hidden, $sortable );

        $this->process_bulk_action();

        $per_page     = 5; // 每页显示的数量
        $current_page = $this->get_pagenum(); // 当前页码

        // 获取数据,这里用模拟数据
        $data = $this->get_data();

        // 排序
        usort( $data, array( $this, 'usort_reorder' ) );

        // 分页
        $total_items = count( $data );
        $data        = array_slice( $data, ( ( $current_page - 1 ) * $per_page ), $per_page );

        $this->items = $data;

        $this->set_pagination_args( [
            'total_items' => $total_items, // 总记录数
            'per_page'    => $per_page,    // 每页显示的数量
            'total_pages' => ceil( $total_items / $per_page ) // 总页数
        ] );
    }

    /**
     * 获取模拟数据
     *
     * @return array 数据数组
     */
    public function get_data() {
        $data = [
            [
                'ID'     => 1,
                'title'  => 'The Hitchhiker's Guide to the Galaxy',
                'author' => 'Douglas Adams',
                'isbn'   => '978-0345391803'
            ],
            [
                'ID'     => 2,
                'title'  => 'The Lord of the Rings',
                'author' => 'J.R.R. Tolkien',
                'isbn'   => '978-0618260264'
            ],
            [
                'ID'     => 3,
                'title'  => 'Pride and Prejudice',
                'author' => 'Jane Austen',
                'isbn'   => '978-0141439518'
            ],
            [
                'ID'     => 4,
                'title'  => 'Nineteen Eighty-Four',
                'author' => 'George Orwell',
                'isbn'   => '978-0451524935'
            ],
            [
                'ID'     => 5,
                'title'  => 'To Kill a Mockingbird',
                'author' => 'Harper Lee',
                'isbn'   => '978-0446310789'
            ],
            [
                'ID'     => 6,
                'title'  => 'The Great Gatsby',
                'author' => 'F. Scott Fitzgerald',
                'isbn'   => '978-0743273565'
            ],
            [
                'ID'     => 7,
                'title'  => 'Moby Dick',
                'author' => 'Herman Melville',
                'isbn'   => '978-0142437243'
            ],
            [
                'ID'     => 8,
                'title'  => 'War and Peace',
                'author' => 'Leo Tolstoy',
                'isbn'   => '978-0140444179'
            ],
            [
                'ID'     => 9,
                'title'  => 'One Hundred Years of Solitude',
                'author' => 'Gabriel García Márquez',
                'isbn'   => '978-0061120036'
            ],
            [
                'ID'     => 10,
                'title'  => 'Don Quixote',
                'author' => 'Miguel de Cervantes',
                'isbn'   => '978-0060930814'
            ]
        ];
        return $data;
    }

    /**
     * 排序回调函数
     *
     * @param array $a 第一个元素
     * @param array $b 第二个元素
     *
     * @return int 排序结果
     */
    public function usort_reorder( $a, $b ) {
        // If no sort, leave as is
        $orderby = ( ! empty( $_GET['orderby'] ) ) ? $_GET['orderby'] : 'title';
        // If no order, default to asc
        $order = ( ! empty( $_GET['order'] ) ) ? $_GET['order'] : 'asc';
        // Determine sort order
        $result = strcmp( $a[ $orderby ], $b[ $orderby ] );
        // Send final sort direction to usort
        return ( ( $order === 'asc' ) ? $result : -$result );
    }
}

四、 代码详解:逐行解读!

咱们来一行一行地看看代码,搞清楚每个部分的作用:

  1. __construct(): 构造函数,初始化列表。 重要的是定义 singularplural ,用于显示消息, 以及 ajax 是否使用 AJAX 分页。
  2. get_columns(): 定义列表的列。 返回一个数组,键是列的 slug(唯一标识符),值是列的标题。 cb 列是复选框列,用于批量操作。
  3. get_sortable_columns(): 定义可排序的列。 返回一个数组,键是列的 slug,值是一个包含两个元素的数组,第一个元素是排序的参数(一般是列的 slug),第二个元素表示是否默认升序排列 true 升序, false 降序。
  4. get_bulk_actions(): 定义批量操作。 返回一个数组,键是操作的 slug,值是操作的标题。
  5. column_cb(): 处理复选框列的显示。 返回复选框的 HTML 代码。 $item 是当前行的数据。
  6. column_default(): 处理默认列的显示。 $item 是当前行的数据,$column_name 是列的 slug。 根据 $column_name 返回对应列的内容。
  7. column_title(): 处理 ‘title’ 列的显示,添加编辑链接。 $item 是当前行的数据。 返回列的内容,包括标题和操作链接。这里用到了 $this->row_actions() 方法,它会自动生成操作链接的 HTML 代码。
  8. process_bulk_action(): 处理批量操作。 判断当前执行的操作,然后执行相应的操作。 这里只是简单地输出了删除的 ID,实际应用中需要执行数据库删除操作。
  9. prepare_items(): 准备列表项,包括数据、分页、排序等。
    • 获取列、隐藏列、可排序列的信息。
    • 处理批量操作。
    • 设置分页参数,包括总记录数、每页显示的数量、总页数。
    • 获取数据,并进行排序和分页。
    • 将数据赋值给 $this->items,以便列表显示。
  10. get_data(): 获取模拟数据。 实际应用中需要从数据库中获取数据。
  11. usort_reorder(): 排序回调函数。 用于对数据进行排序。

五、 运行结果:见证奇迹的时刻!

  1. 激活 my-custom-list-table 插件。
  2. 登录 WordPress 后台,点击 "My List Table" 菜单,就能看到咱们自定义的列表页面啦!

六、 扩展功能:让列表更强大!

WP_List_Table 的强大之处在于它的可扩展性。咱们可以根据自己的需求,添加各种各样的功能:

  • 自定义列: 添加更多的列,显示更多的数据。
  • 自定义操作: 添加更多的操作链接,例如查看、下载等。
  • 搜索功能: 添加搜索框,方便用户查找数据。
  • 过滤功能: 添加过滤器,让用户可以根据条件过滤数据。
  • AJAX 分页: 使用 AJAX 分页,提高用户体验。
  • 自定义 CSS: 修改 CSS 样式,让列表看起来更美观。

七、 进阶技巧:更上一层楼!

  • 使用 WordPress API: 尽量使用 WordPress 提供的 API 来操作数据,例如 WP_QueryWPDB 等。
  • 安全第一: 注意安全,防止 SQL 注入、XSS 攻击等。
  • 代码规范: 遵循 WordPress 代码规范,让代码更易读、易维护。
  • 性能优化: 对列表进行性能优化,提高加载速度。

八、 举一反三:更多应用场景!

WP_List_Table 不仅仅可以用来显示图书列表,还可以用来显示各种各样的数据:

  • 用户列表
  • 订单列表
  • 评论列表
  • 自定义文章类型列表
  • 插件列表
  • 主题列表

只要你想,就能用它来构建各种各样的后台列表页面!

九、 表格总结:核心方法一览

方法名 作用
__construct() 构造函数,初始化列表。定义单复数名称和是否使用AJAX。
get_columns() 定义列表的列。返回一个数组,键是列的 slug,值是列的标题。
get_sortable_columns() 定义可排序的列。返回一个数组,键是列的 slug,值是一个包含两个元素的数组,第一个元素是排序的参数,第二个元素表示是否默认升序排列。
get_bulk_actions() 定义批量操作。返回一个数组,键是操作的 slug,值是操作的标题。
column_cb() 处理复选框列的显示。返回复选框的 HTML 代码。
column_default() 处理默认列的显示。根据 $column_name 返回对应列的内容。
column_{$column_name} 处理特定列的显示。例如,column_title() 处理 ‘title’ 列的显示。
process_bulk_action() 处理批量操作。判断当前执行的操作,然后执行相应的操作。
prepare_items() 准备列表项,包括数据、分页、排序等。这是最重要的方法,负责获取数据、排序、分页,并将数据赋值给 $this->items
get_data() 获取数据。实际应用中需要从数据库中获取数据。
usort_reorder() 排序回调函数。用于对数据进行排序。

十、 最后的忠告:实践出真知!

光说不练假把式,光看不做等于零! 赶紧动手试试吧,只有在实践中才能真正理解 WP_List_Table 的强大之处!

今天的分享就到这里,希望对大家有所帮助! 祝大家编程愉快,早日成为 WordPress 大神! 咱们下次再见!

发表回复

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