各位观众老爷们,晚上好!欢迎来到今天的 WordPress 源码解读小课堂。今天咱们聊聊 pre_get_posts
这个神奇的钩子,它就像一个在厨师做菜之前,你可以偷偷往锅里加点料的魔法开关。咱们保证让你听得懂、学得会、用得上!
开场白:WP_Query
,WordPress 的心脏
在深入 pre_get_posts
之前,咱得先了解 WP_Query
是个啥。简单来说,WP_Query
是 WordPress 里负责从数据库里捞数据的核心类。 无论是你访问首页、分类页、搜索结果页,还是自定义的页面,背后都离不开 WP_Query
在默默干活。
pre_get_posts
:截胡的艺术
pre_get_posts
钩子,就给了我们一个机会,在 WP_Query
真正执行数据库查询之前,拦截并修改它的查询参数。想象一下,你原本想吃红烧肉,但通过 pre_get_posts
,你可以让厨师把红烧肉变成糖醋里脊,是不是很棒?
pre_get_posts
的源码在哪里?
pre_get_posts
本身并不是一个函数,而是一个 action hook。它在 WP_Query::get_posts()
方法里被触发。 咱们简单追踪一下:
WP_Query::get_posts()
: 这个方法是WP_Query
类里真正执行查询的地方。do_action_ref_array( 'pre_get_posts', array( &$this ) );
: 在get_posts()
方法的开头,你会看到这行代码。do_action_ref_array()
就是触发 action hook 的函数。pre_get_posts
就是我们今天要研究的钩子,而&$this
则是把当前的WP_Query
对象以引用的方式传递给所有挂载到pre_get_posts
的函数。
举个栗子:修改首页的文章数量
假设你想在首页只显示 5 篇文章,而不是默认的 10 篇。你可以这样写:
function my_custom_pre_get_posts( $query ) {
if ( $query->is_home() && $query->is_main_query() ) {
$query->set( 'posts_per_page', 5 );
}
}
add_action( 'pre_get_posts', 'my_custom_pre_get_posts' );
这段代码做了什么?
my_custom_pre_get_posts( $query )
: 这是一个自定义函数,它接收一个$query
对象,这个对象就是当前的WP_Query
实例。$query->is_home()
: 检查当前是否是首页。$query->is_main_query()
: 检查当前是否是主查询。这个非常重要,因为 WordPress 里可能有很多WP_Query
实例,我们通常只想修改主查询。$query->set( 'posts_per_page', 5 )
: 使用set()
方法修改查询参数。这里我们把posts_per_page
设置为 5。add_action( 'pre_get_posts', 'my_custom_pre_get_posts' )
: 把我们的自定义函数挂载到pre_get_posts
钩子上。
几个重要的函数和方法
在 pre_get_posts
里,你经常会用到以下几个函数和方法:
函数/方法 | 作用 |
---|---|
$query->is_home() |
检查是否是首页 |
$query->is_single() |
检查是否是单篇文章页 |
$query->is_archive() |
检查是否是文章归档页(分类、标签、作者等) |
$query->is_category() |
检查是否是分类页 |
$query->is_tag() |
检查是否是标签页 |
$query->is_search() |
检查是否是搜索结果页 |
$query->is_main_query() |
检查是否是主查询 |
$query->set( $key, $value ) |
设置查询参数。 $key 是参数名,$value 是参数值。 |
$query->get( $key ) |
获取查询参数。 $key 是参数名。 |
更高级的用法:根据用户角色修改查询
假设你想让管理员看到所有文章,而普通用户只能看到已发布的文章。你可以这样做:
function my_custom_pre_get_posts( $query ) {
if ( ! is_admin() && $query->is_main_query() ) {
$current_user = wp_get_current_user();
if ( ! in_array( 'administrator', (array) $current_user->roles ) ) {
$query->set( 'post_status', 'publish' );
}
}
}
add_action( 'pre_get_posts', 'my_custom_pre_get_posts' );
这段代码做了什么?
! is_admin()
: 确保代码不在后台运行。wp_get_current_user()
: 获取当前用户信息。! in_array( 'administrator', (array) $current_user->roles )
: 检查当前用户是否是管理员。$query->set( 'post_status', 'publish' )
: 如果用户不是管理员,就把post_status
设置为publish
,只显示已发布的文章。
再来一个例子:自定义文章类型的排序
假设你有一个自定义文章类型 product
,你想按照价格(自定义字段 price
)排序。
function my_custom_pre_get_posts( $query ) {
if ( ! is_admin() && $query->is_main_query() && $query->is_post_type_archive( 'product' ) ) {
$query->set( 'orderby', 'meta_value_num' );
$query->set( 'meta_key', 'price' );
$query->set( 'order', 'ASC' ); // 升序
}
}
add_action( 'pre_get_posts', 'my_custom_pre_get_posts' );
这段代码做了什么?
$query->is_post_type_archive( 'product' )
: 检查当前是否是product
这种文章类型的归档页。$query->set( 'orderby', 'meta_value_num' )
: 设置排序方式为按照数字类型的自定义字段排序。$query->set( 'meta_key', 'price' )
: 设置自定义字段的键名为price
。$query->set( 'order', 'ASC' )
: 设置排序顺序为升序。
pre_get_posts
的使用注意事项
is_main_query()
的重要性: 一定要检查是否是主查询。否则,你可能会影响到 WordPress 后台或者其他地方的查询,导致意想不到的问题。- 性能问题:
pre_get_posts
会在每次查询之前都执行,所以你的代码要尽量简洁高效。避免执行复杂的逻辑或者数据库查询。 - 调试: 如果你的代码没有生效,可以使用
var_dump( $query )
或者wp_die( print_r( $query, true ) )
来查看$query
对象的内容,看看你的修改是否正确。
进阶技巧:使用 parse_query
钩子
除了 pre_get_posts
,还有一个类似的钩子叫做 parse_query
。它们的主要区别在于:
parse_query
: 在WP_Query
对象初始化之后,但是在解析查询字符串之前触发。你可以在这里访问原始的查询字符串,并进行修改。pre_get_posts
: 在WP_Query
对象解析完查询字符串之后,但在真正执行查询之前触发。你在这里可以修改已经解析好的查询参数。
通常情况下,pre_get_posts
更常用,因为它更方便修改查询参数。但是,如果你需要访问或修改原始的查询字符串,parse_query
可能会更适合你。
案例分析:实现一个自定义的排序选项
假设你想在文章归档页添加一个自定义的排序选项,允许用户按照文章标题的长度排序。
-
前端添加排序选项: 首先,你需要在文章归档页添加一个下拉菜单,让用户选择排序方式。这部分涉及到 HTML 和 PHP,这里只给出 HTML 部分的示例:
<select name="orderby_title_length"> <option value="default">默认排序</option> <option value="title_length_asc">标题长度升序</option> <option value="title_length_desc">标题长度降序</option> </select>
-
获取用户选择的排序方式: 当用户选择排序方式并提交表单时,你需要获取用户选择的值。
$orderby_title_length = isset( $_GET['orderby_title_length'] ) ? $_GET['orderby_title_length'] : 'default';
-
使用
pre_get_posts
修改查询: 接下来,你可以使用pre_get_posts
钩子,根据用户选择的排序方式修改查询。function my_custom_pre_get_posts( $query ) { if ( ! is_admin() && $query->is_main_query() && $query->is_archive() && isset( $_GET['orderby_title_length'] ) ) { $orderby_title_length = $_GET['orderby_title_length']; if ( $orderby_title_length === 'title_length_asc' ) { add_filter( 'posts_orderby', 'my_custom_orderby_title_length_asc' ); } elseif ( $orderby_title_length === 'title_length_desc' ) { add_filter( 'posts_orderby', 'my_custom_orderby_title_length_desc' ); } } } add_action( 'pre_get_posts', 'my_custom_pre_get_posts' ); function my_custom_orderby_title_length_asc( $orderby ) { global $wpdb; return "LENGTH({$wpdb->posts}.post_title) ASC"; } function my_custom_orderby_title_length_desc( $orderby ) { global $wpdb; return "LENGTH({$wpdb->posts}.post_title) DESC"; }
这段代码做了什么?
isset( $_GET['orderby_title_length'] )
: 检查orderby_title_length
参数是否存在。add_filter( 'posts_orderby', 'my_custom_orderby_title_length_asc' )
: 根据用户选择的排序方式,添加相应的posts_orderby
过滤器。posts_orderby
是另一个钩子,允许你修改 SQL 查询语句的ORDER BY
部分。my_custom_orderby_title_length_asc( $orderby )
和my_custom_orderby_title_length_desc( $orderby )
: 这两个函数分别返回按照标题长度升序和降序排序的 SQL 语句。
总结
pre_get_posts
钩子是 WordPress 里一个非常强大和灵活的工具,它可以让你在 WP_Query
执行之前,对查询参数进行各种各样的修改。 掌握 pre_get_posts
,你就可以轻松地实现各种自定义的查询需求,让你的 WordPress 网站更加个性化和强大。 但请记住,能力越大,责任越大,使用 pre_get_posts
时一定要小心谨慎,避免影响网站的性能和稳定性。
今天的课程就到这里,希望大家有所收获! 下课!