WP DEVELOP

EP038. “双向显示 Event 与 Program 的关联”

首页 WordPress 开发课程 PROGRAM POST TYPE 与关系 · EP038
约 18 分钟· #EP038#PROGRAM POST TYPE 与关系
🔒 登录后可标记已读

上一讲用 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 数组是关键:keyrelated_programs(ACF 关系字段名),compareLIKE(模糊匹配,因为字段值是序列化字符串,不能用 = 精确比较),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