WordPress 主题模式中 PHP 的运用与实践

WordPress 块主题的 Patterns(模式)系统为开发者提供了一个独特的 PHP 执行环境。与传统的模板文件不同,Patterns 在注册阶段(init 钩子)就已经编译并保存为 HTML 块标记,这使得许多常见的 WordPress 函数(如 is_page()、get_post())在 Patterns 中无法正常工作。本文深入探讨了这一技术限制背后的原理,并详细讲解如何正确地在 Patterns 中使用 PHP 实现文本国际化、动态资源加载以及循环输出等功能。无论你是刚开始接触块主题开发,还是从经典主题过渡而来,这份指南都能帮你避开常见的坑,写出更优雅的 Pattern 代码。


理解 Patterns 的执行时机

在块主题开发中,Patterns 和普通模板文件大的区别在于执行时机的不同。普通模板文件是在页面加载时才执行 PHP 代码,而 Patterns 则是在 WordPress 初始化阶段(init 钩子)就被处理了。

具体来说,当你注册一个 Pattern 时,WordPress 会立即解析其中的 PHP 代码,并将生成的 HTML 块标记保存下来。这意味着:

  • 注册时执行:Pattern 中的 PHP 代码在 init 钩子触发时运行
  • 渲染时展示:实际展示给用户的是早已生成好的静态 HTML

这个特性带来一个重要限制:在 Pattern 注册时,WordPress 的全局数据(如当前查询对象、文章数据等)还没有准备好。所以像下面这样的代码是无法工作的:

php

<?php if ( is_page() ) : ?>
    <!-- wp:paragraph -->
    <p><?php the_title(); ?></p>
    <!-- /wp:paragraph -->
<?php endif; ?>

is_page() 和 the_title() 都依赖于当前页面的查询数据,但在 Pattern 注册阶段这些数据根本不存在,所以这段代码无论放在哪里都不会按预期执行。

如果你之前做过经典主题开发,这个特点确实需要适应。在经典主题中,模板文件是在页面请求时才被加载执行的,所有全局数据都已经就绪。而 Patterns 更像是”提前编译好的模板片段”,在编译时就要确定所有内容。


从实际案例开始

为了更好地理解 Patterns 中 PHP 的用法,我们从一个简单的 Hero(英雄区域)Pattern 开始。创建一个 patterns/hero.php 文件,放入以下代码:

php

<?php
/**
 * Title: Hero
 * Slug: themeslug/hero
 * Categories: featured
 */
?>
<!-- wp:cover {"overlayColor":"contrast","isUserOverlayColor":true,"align":"full"} -->
<div class="wp-block-cover alignfull">
    <span aria-hidden="true" class="wp-block-cover__background has-contrast-background-color has-background-dim-100 has-background-dim"></span>
    <div class="wp-block-cover__inner-container">
        
        <!-- wp:heading {"textAlign":"center"} -->
        <h2 class="wp-block-heading has-text-align-center">欢迎来到我的网站</h2>
        <!-- /wp:heading -->

        <!-- wp:paragraph {"align":"center"} -->
        <p class="has-text-align-center">这是我的温馨小窝。</p>
        <!-- /wp:paragraph -->

        <!-- wp:buttons {"layout":{"type":"flex","justifyContent":"center"}} -->
        <div class="wp-block-buttons">
            <!-- wp:button {"className":"wp-block-button is-style-outline"} -->
            <div class="wp-block-button is-style-outline"><a class="wp-block-button__link wp-element-button">按钮 A</a></div>
            <!-- /wp:button -->
            <!-- wp:button {"className":"wp-block-button is-style-outline"} -->
            <div class="wp-block-button is-style-outline"><a class="wp-block-button__link wp-element-button">按钮 B</a></div>
            <!-- /wp:button -->
        </div>
        <!-- /wp:buttons -->

    </div>
</div>
<!-- /wp:cover -->

这个 Pattern 在编辑器中会呈现为一个带有标题、描述文字和两个按钮的封面区域。接下来,我们将逐步为它添加 PHP 动态功能。


文本国际化处理

Patterns 大的优势之一就是可以轻松处理文本国际化。在模板或模板部件中,如果你需要翻译文本,的办法就是把内容迁移到 Pattern 中,因为 Patterns 支持 PHP 的国际化函数。

WordPress 提供了一套完整的国际化函数:

函数用途
__()返回翻译后的字符串
_e()直接输出翻译后的字符串
esc_html__()返回经过转义的翻译字符串
esc_html_e()输出经过转义的翻译字符串

以我们 Hero Pattern 中的标题为例,原先的写法是硬编码的:

html

<h2 class="wp-block-heading has-text-align-center">欢迎来到我的网站</h2>

这种写法无法被翻译工具识别。改成使用 esc_html_e() 后:

html

<h2 class="wp-block-heading has-text-align-center"><?php esc_html_e( '欢迎来到我的网站', 'themeslug' ); ?></h2>

同样的方法应用到段落和两个按钮上。修改后的完整代码如下:

php

<?php
/**
 * Title: Hero
 * Slug: themeslug/hero
 * Categories: featured
 */
?>
<!-- wp:cover {"overlayColor":"contrast","isUserOverlayColor":true,"align":"full"} -->
<div class="wp-block-cover alignfull">
    <span aria-hidden="true" class="wp-block-cover__background has-contrast-background-color has-background-dim-100 has-background-dim"></span>
    <div class="wp-block-cover__inner-container">
        
        <!-- wp:heading {"textAlign":"center"} -->
        <h2 class="wp-block-heading has-text-align-center"><?php esc_html_e( '欢迎来到我的网站', 'themeslug' ); ?></h2>
        <!-- /wp:heading -->

        <!-- wp:paragraph {"align":"center"} -->
        <p class="has-text-align-center"><?php esc_html_e( '这是我的温馨小窝。', 'themeslug' ); ?></p>
        <!-- /wp:paragraph -->

        <!-- wp:buttons {"layout":{"type":"flex","justifyContent":"center"}} -->
        <div class="wp-block-buttons">
            <!-- wp:button {"className":"wp-block-button is-style-outline"} -->
            <div class="wp-block-button is-style-outline"><a class="wp-block-button__link wp-element-button"><?php esc_html_e( '按钮 A', 'themeslug' ); ?></a></div>
            <!-- /wp:button -->
            <!-- wp:button {"className":"wp-block-button is-style-outline"} -->
            <div class="wp-block-button is-style-outline"><a class="wp-block-button__link wp-element-button"><?php esc_html_e( '按钮 B', 'themeslug' ); ?></a></div>
            <!-- /wp:button -->
        </div>
        <!-- /wp:buttons -->

    </div>
</div>
<!-- /wp:cover -->

注意 themeslug 是文本域(Text Domain),它必须与主题中声明的文本域一致,否则翻译文件无法正确加载。


动态资源引用

Patterns 中另一个常见需求是引用主题内的资源文件,比如图片、字体或 CSS 文件。在硬编码的情况下,图片 URL 可能是这样的:

text

http://localhost/wp-content/uploads/2023/10/hero-background.jpg

这种写法有两个问题:

  1. 不可移植:迁移到其他环境时 URL 会失效
  2. 依赖用户上传:图片必须被导入媒体库

更好的做法是使用 get_theme_file_uri() 函数,它返回主题目录的 URL:

php

<?php echo esc_url( get_theme_file_uri( 'assets/images/hero-background.jpg' ) ); ?>

将这个应用到 Cover 块的背景图片上。假设你从 WordPress 图片目录下载了一张图片,保存到主题的 /assets/images/hero-background.jpg,那么修改后的 Cover 块代码如下:

php

<!-- wp:cover {"url":"<?php echo esc_url( get_theme_file_uri( 'assets/images/hero-background.jpg' ) ); ?>","dimRatio":50,"overlayColor":"contrast","align":"full"} -->
<div class="wp-block-cover alignfull">
    <span aria-hidden="true" class="wp-block-cover__background has-contrast-background-color has-background-dim"></span>
    <img class="wp-block-cover__image-background" alt="" src="<?php echo esc_url( get_theme_file_uri( 'assets/images/hero-background.jpg' ) ); ?>" data-object-fit="cover"/>
    <div class="wp-block-cover__inner-container">
        <!-- 内容省略 -->
    </div>
</div>
<!-- /wp:cover -->

esc_url() 用于对 URL 进行转义,防止潜在的安全问题。这是一个必须养成的好习惯。


使用循环来避免重复代码

在软件开发中,”不要重复自己”(DRY)是一个重要的原则。观察我们的 Hero Pattern,两个按钮的结构完全相同,只是文字不同:

php

<!-- wp:button {"className":"wp-block-button is-style-outline"} -->
<div class="wp-block-button is-style-outline"><a class="wp-block-button__link wp-element-button"><?php esc_html_e( '按钮 A', 'themeslug' ); ?></a></div>
<!-- /wp:button -->

<!-- wp:button {"className":"wp-block-button is-style-outline"} -->
<div class="wp-block-button is-style-outline"><a class="wp-block-button__link wp-element-button"><?php esc_html_e( '按钮 B', 'themeslug' ); ?></a></div>
<!-- /wp:button -->

使用 foreach 循环可以优雅地解决这个问题。首先在 Pattern 文件顶部定义按钮数据:

php

$buttons = array(
    __( '创业故事', 'themeslug' ),
    __( '加入我们', 'themeslug' )
);

然后替换原有的两个按钮块:

php

<?php foreach ( $buttons as $button ) : ?>
    <!-- wp:button {"className":"wp-block-button is-style-outline"} -->
    <div class="wp-block-button is-style-outline"><a class="wp-block-button__link wp-element-button"><?php echo esc_html( $button ); ?></a></div>
    <!-- /wp:button -->
<?php endforeach; ?>

终的完整 Pattern 代码:

php

<?php
/**
 * Title: Hero
 * Slug: themeslug/hero
 * Categories: featured
 */

$buttons = array(
    __( '创业故事', 'themeslug' ),
    __( '加入我们', 'themeslug' )
);

?>
<!-- wp:cover {"url":"<?php echo esc_url( get_theme_file_uri( 'assets/images/hero-background.jpg' ) ); ?>","dimRatio":50,"overlayColor":"contrast","align":"full"} -->
<div class="wp-block-cover alignfull">
    <span aria-hidden="true" class="wp-block-cover__background has-contrast-background-color has-background-dim"></span>
    <img class="wp-block-cover__image-background" alt="" src="<?php echo esc_url( get_theme_file_uri( 'assets/images/hero-background.jpg' ) ); ?>" data-object-fit="cover"/>
    <div class="wp-block-cover__inner-container">
        
        <!-- wp:heading {"textAlign":"center"} -->
        <h2 class="wp-block-heading has-text-align-center"><?php esc_html_e( '欢迎来到我的网站', 'themeslug' ); ?></h2>
        <!-- /wp:heading -->

        <!-- wp:paragraph {"align":"center"} -->
        <p class="has-text-align-center"><?php esc_html_e( '这是我的温馨小窝。', 'themeslug' ); ?></p>
        <!-- /wp:paragraph -->

        <!-- wp:buttons {"layout":{"type":"flex","justifyContent":"center"}} -->
        <div class="wp-block-buttons">
            <?php foreach ( $buttons as $button ) : ?>
                <!-- wp:button {"className":"wp-block-button is-style-outline"} -->
                <div class="wp-block-button is-style-outline"><a class="wp-block-button__link wp-element-button"><?php echo esc_html( $button ); ?></a></div>
                <!-- /wp:button -->
            <?php endforeach; ?>
        </div>
        <!-- /wp:buttons -->

    </div>
</div>
<!-- /wp:cover -->

这样做的好处显而易见:

  • 易于维护:修改按钮文字只需改数组
  • 易于扩展:添加新按钮只需在数组中新增一项
  • 代码更简洁:减少了重复的 HTML 结构

实战小结

Patterns 中的 PHP 使用有其独特的规则和限制:

你可以在 Patterns 中做的:

  • 使用国际化函数(__()、_e()、esc_html_e() 等)
  • 使用 get_theme_file_uri() 等资源路径函数
  • 使用 PHP 的控制结构(循环、条件判断)
  • 定义和使用变量、数组
  • 调用任何不依赖全局查询数据的 WordPress 函数

你不能在 Patterns 中做的:

  • 使用 is_page()、is_single()、is_home() 等条件标签
  • 使用 get_post()、get_queried_object() 等获取当前文章数据的函数
  • 直接访问 $post、$wp_query 等全局变量

这个限制的本质在于 Patterns 的”编译时”与”运行时”分离。一旦你理解了这一点,就能在设计 Pattern 时做出正确的判断。

对于更复杂的动态需求(比如需要在渲染时获取实时数据),可以考虑使用 WordPress 的 Block Bindings API 或自定义块。但这些属于更高级的话题,超出了 Patterns 本身的能力范围。

掌握好 Patterns 中 PHP 的使用,你的块主题开发效率会有显著提升。从文本国际化到资源管理,再到代码复用,PHP 为 Patterns 带来的可能性远不止于此。