EP038. “双向显示 Event 与 Program 的关联”
🔒 登录后可标记已读上一讲用 ACF 建好了 event → program 的关系字段 related_programs,这一讲把这份关系数据显示到前端,而且做成双向:single-event.php 底部输出「这个 event 关联了哪些 program」,single-program.php 底部反过来查出「哪些 event 关联了当前这个 program」。第一个方向很直接,get_field() 拿到的就是关联 program 的 post object 数组,遍历输出即可;第二个方向没有现成字段可读(program 文章不知道自己被哪些 event 关联),只能靠数据库层面的 meta_query 查询,而且因为 ACF 关系字段存进数据库时会被序列化成字符串,比较时要用 LIKE 并手动给数值包上引号,否则会有像 12 匹配到 120、1200 这样的误报。
涉及文件
wp-content/themes/fictional-university-theme/single-event.php(修改,generic-content区块后面新增 related programs 列表)wp-content/themes/fictional-university-theme/single-program.php(修改,generic-content区块后面新增反向查询 upcoming events)
代码实现
wp-content/themes/fictional-university-theme/single-event.php (修改)
加在 <div class="generic-content">...</div> 这个 div 后面:
<?php
$relatedPrograms = get_field('related_programs');
if ($relatedPrograms) {
echo '<hr class="section-break">';
echo '<h2 class="headline headline--medium">Related Program(s)</h2>';
echo '<ul class="link-list min-list">';
foreach($relatedPrograms as $program) { ?>
<li><a href="<?php echo get_the_permalink($program); ?>"><?php echo get_the_title($program); ?></a></li>
<?php }
echo '</ul>';
}
?>
要点:
get_field('related_programs')返回的是数组(EP038 一开始用print_r()打印确认过,输出以array开头),数组里每一项都是一个 WordPress post object。- 用
foreach ($relatedPrograms as $program)遍历,$program就是每一个关联 program 的 post object。 - 因为不在标准的 WordPress Loop 里,不能用裸的
the_title()/the_permalink()(那些只对"当前 Loop 里当前这篇文章"生效),要用get_the_title($program)/get_the_permalink($program),显式把 post object 当参数传进去。 - 整段用
if ($relatedPrograms)包起来:如果这个 event 没有关联任何 program,get_field()会返回空值,$relatedPrograms判断为 false,跳过整个输出(标题、分隔线、列表都不会出现)。
[截图:前台某个 event 详情页底部"Related Program(s)"区块,列出关联的 program 链接的画面]
wp-content/themes/fictional-university-theme/single-program.php (修改)
同样加在 generic-content 区块后面。这一段是从 front-page.php 里 "Upcoming Events" 那个 WP_Query 查询和 while 循环整段复制过来的(首页那个查询本来就是查"最近的 2 场活动"),再加一个 meta_query 过滤条件,只留下关联到当前 program 的 event:
<?php
$today = date('Ymd');
$homepageEvents = new WP_Query(array(
'posts_per_page' => 2,
'post_type' => 'event',
'meta_key' => 'event_date',
'orderby' => 'meta_value_num',
'order' => 'ASC',
'meta_query' => array(
array(
'key' => 'event_date',
'compare' => '>=',
'value' => $today,
'type' => 'numeric'
),
// 新增:只要 related_programs 字段里包含当前 program ID 的 event
array(
'key' => 'related_programs',
'compare' => 'LIKE',
'value' => '"' . get_the_ID() . '"'
)
)
));
while($homepageEvents->have_posts()) {
$homepageEvents->the_post(); ?>
<div class="event-summary">
<a class="event-summary__date t-center" href="#">
<span class="event-summary__month"><?php
$eventDate = new DateTime(get_field('event_date'));
echo $eventDate->format('M')
?></span>
<span class="event-summary__day"><?php echo $eventDate->format('d') ?></span>
</a>
<div class="event-summary__content">
<h5 class="event-summary__title headline headline--tiny"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h5>
<p><?php if (has_excerpt()) {
echo get_the_excerpt();
} else {
echo wp_trim_words(get_the_content(), 18);
} ?> <a href="<?php the_permalink(); ?>" class="nu gray">Learn more</a></p>
</div>
</div>
<?php }
?>
要点:
- 查询变量还是叫
$homepageEvents——因为整段代码是从首页复制过来的,这一讲没有改名,功能上其实已经是"当前 program 关联的 upcoming events",跟首页无关,只是命名沿用下来了。 - 新增的第二个
meta_query数组是关键:key是related_programs(ACF 关系字段名),compare用LIKE(模糊匹配,因为字段值是序列化字符串,不能用=精确比较),value是当前 program 的 ID,用get_the_ID()动态取,不能写死数字。 - ACF 把关系字段这种数组存进数据库前会先序列化成一长串文本,每个 post ID 会被序列化时自带的引号包起来(类似
"12"这样)。所以value手动拼接成'"' . get_the_ID() . '"'(前后各加一个双引号),让LIKE查询精确匹配带引号的那个数字,避免比如当前 program ID 是 12 时,误匹配到 120、1200 这些实际上不相关但字符串开头也是 "12" 的 ID。
Hook / Function 速查
| 名称 | 类型 | 用途 |
|---|---|---|
get_field('related_programs') | ACF function(返回值) | 读取 event 文章的关系字段,返回关联 program 的 post object 数组 |
print_r($var) | PHP 内建 function(调试用) | 打印变量的完整结构,本讲用来确认 get_field() 返回的确实是数组,调试完就删掉 |
foreach ($array as $item) | PHP 语法 | 遍历 get_field() 返回的 post object 数组,一个个处理 |
get_the_title($post) / get_the_permalink($post) | WP 内建 function(可传入 post ID 或 post object) | 不在标准 Loop 里、或者要取"另一篇文章"(不是当前正在循环的这篇)的标题/链接时用,跟裸的 the_title() / the_permalink() 的区别是必须显式传参数 |
meta_query 里用 'compare' => 'LIKE' 匹配序列化字段 | WP_Query 参数用法 | 反向查询"哪些文章的某个数组字段里包含指定值",因为字段存的是序列化字符串,要手动给比较值包引号避免数字前缀误匹配 |
常见坑
get_field()拿到的关系字段值直接foreach,不先判断是否为空:如果这个 event 没有关联任何 program,get_field()返回的不是空数组,foreach会对着一个不存在/非数组的值循环,PHP 报错。必须先用if ($relatedPrograms)判断再进foreach(transcript 里演示了 "Math Meetup" 这个没有关联 program 的 event 会报错的例子)。- 用
meta_query反查序列化字段时如果直接拿数字比较(比如直接用get_the_ID()不加引号),会连带匹配到以这个数字开头的其他 ID(12 会连带匹配到 120、1200),必须手动拼接双引号模拟 ACF 存储格式。 get_the_title()/get_the_permalink()忘记传参数(写成裸的the_title())在这种场景下会拿到错误的数据,因为这里根本不在处理"当前 Loop 当前文章"的上下文里。
延伸 / 后续讲座会用到
这一讲功能已经跑通,但 single-program.php 那块反向查询在没有关联 event 的 program 页面上,标题和分隔线还是会跑出来(因为没有包在判断里),样式和这个空状态问题留到 EP039 处理。
Sources
Udemy:
- Become a WordPress Developer: Unlocking Power With Code — Section 8, EP038