阐述 WordPress `register_block_type()` 函数的源码:如何将区块定义注册到 `$wp_block_types` 全局变量。

各位同学,大家好!我是今天的主讲人,很高兴能和大家一起深入研究 WordPress 区块注册的核心机制。今天我们要聊的就是 register_block_type() 函数背后的故事,以及它如何巧妙地将区块定义放入 $wp_block_types 全局变量这个“大仓库”里。

准备好了吗?让我们开始这场代码探险之旅吧!

一、register_block_type():区块注册的“门面担当”

首先,我们来回顾一下 register_block_type() 这个函数。 它是 WordPress 官方提供的用于注册 Gutenberg 区块的函数,位于 wp-includes/block-registry.php 文件中。 它的作用很简单也很重要:告诉 WordPress "嘿,我这里有一个新的区块,它的名字、属性、行为是这样的!"

但它背后做了哪些事情,才能让 WordPress 真正“认识”这个区块呢? 这就需要我们深入源码一探究竟。

二、源码剖析:一步一步揭开神秘面纱

让我们从一个简化的 register_block_type() 函数的“骨架”开始:

<?php
/**
 * Registers a block type.
 *
 * @param string|WP_Block_Type $block_type Block type name or definition.
 * @param array                $args       Optional. An array of arguments for registering a block type.
 * @return WP_Block_Type|false The registered block type on success, or false on failure.
 */
function register_block_type( $block_type, $args = array() ) {
    global $wp_block_types;

    // 1. 参数验证和处理
    if ( empty( $block_type ) ) {
        return false;
    }

    if ( is_string( $block_type ) ) {
        $block_name = $block_type;
    } elseif ( $block_type instanceof WP_Block_Type ) {
        $block_name = $block_type->name;
    } else {
        return false; // Invalid block type
    }

    if ( isset( $wp_block_types[ $block_name ] ) ) {
        _doing_it_wrong(
            __FUNCTION__,
            sprintf(
                /* translators: %s: Block type name. */
                __( 'Block type "%s" is already registered.' ),
                $block_name
            ),
            '5.0.0'
        );
        return false;
    }

    // 2. 创建 WP_Block_Type 对象
    if ( ! ( $block_type instanceof WP_Block_Type ) ) {
        try {
            $block_type = new WP_Block_Type( $block_name, $args );
        } catch ( InvalidArgumentException $e ) {
            _doing_it_wrong(
                __FUNCTION__,
                $e->getMessage(),
                '5.0.0'
            );
            return false;
        }
    }

    // 3. 注册区块类型到全局变量 $wp_block_types
    $wp_block_types[ $block_name ] = $block_type;

    // 4. 返回注册的区块类型
    return $block_type;
}

现在,让我们逐个环节深入分析:

2.1 参数验证和处理

这部分代码主要负责检查传入的参数是否有效。

  • 检查区块类型名称是否为空:

    if ( empty( $block_type ) ) {
        return false;
    }

    如果传入的 $block_type 为空,那还注册个啥? 直接返回 false, 告诉调用者注册失败。

  • 确定区块名称:

    if ( is_string( $block_type ) ) {
        $block_name = $block_type;
    } elseif ( $block_type instanceof WP_Block_Type ) {
        $block_name = $block_type->name;
    } else {
        return false; // Invalid block type
    }

    这里允许传入两种类型的 $block_type

    • 字符串:直接将字符串作为区块名称。
    • WP_Block_Type 对象:从对象中获取区块名称。

    如果都不是,那肯定有问题,返回 false

  • 检查区块是否已经注册:

    if ( isset( $wp_block_types[ $block_name ] ) ) {
        _doing_it_wrong(
            __FUNCTION__,
            sprintf(
                /* translators: %s: Block type name. */
                __( 'Block type "%s" is already registered.' ),
                $block_name
            ),
            '5.0.0'
        );
        return false;
    }

    在注册之前,先看看 $wp_block_types 这个“大仓库”里是不是已经有同名的区块了。 如果有,就发出一个 _doing_it_wrong 警告,告诉开发者“你重复注册了!”,然后返回 false_doing_it_wrong 是一个调试函数,在开发环境下会显示警告信息,帮助开发者避免错误。

2.2 创建 WP_Block_Type 对象

如果传入的 $block_type 不是 WP_Block_Type 对象,就需要创建一个:

if ( ! ( $block_type instanceof WP_Block_Type ) ) {
    try {
        $block_type = new WP_Block_Type( $block_name, $args );
    } catch ( InvalidArgumentException $e ) {
        _doing_it_wrong(
            __FUNCTION__,
            $e->getMessage(),
            '5.0.0'
        );
        return false;
    }
}

这里使用 try...catch 块来捕获可能出现的 InvalidArgumentException 异常。 WP_Block_Type 类的构造函数会对传入的参数进行验证,如果参数不符合要求,就会抛出异常。 如果捕获到异常,同样使用 _doing_it_wrong 发出警告,并返回 false

WP_Block_Type 类是 WordPress 用于表示区块类型的核心类。它包含了区块的所有信息,例如名称、属性、渲染函数等等。 让我们简单看一下WP_Block_Type的构造函数会做什么:

<?php
/**
 * Class WP_Block_Type
 */
final class WP_Block_Type {

    /**
     * Block type name including namespace.
     *
     * @since 5.5.0
     * @var string
     */
    public $name;

    /**
     * Attributes for the block type.
     *
     * @since 5.5.0
     * @var array
     */
    public $attributes;

  // ... 其它属性

  public function __construct( $block_name, $args = array() ) {
    $this->name = $block_name;
    // 验证区块名称
    if ( ! is_string( $block_name ) ) {
      throw new InvalidArgumentException( 'Block name must be a string.' );
    }

    if ( ! preg_match( '/^[a-z][a-z0-9-]*/[a-z][a-z0-9-]*$/', $block_name ) ) {
      throw new InvalidArgumentException( 'Block name must match the required format namespace/block-name.' );
    }

    // 设置属性
    if ( isset( $args['attributes'] ) && is_array( $args['attributes'] ) ) {
      $this->attributes = $args['attributes'];
    } else {
      $this->attributes = array();
    }

    // ... 其它属性设置
  }
}

构造函数会验证区块名字是否符合 namespace/block-name 的格式, 并将传入的 $args 数组中的 attributes 赋值给 $this->attributes。 当然,还会处理其他很多属性,这里为了简化,只展示了关键的部分。

2.3 注册区块类型到全局变量 $wp_block_types

这才是最关键的一步!

$wp_block_types[ $block_name ] = $block_type;

这一行代码将刚刚创建的 WP_Block_Type 对象,以区块名称为键名,存储到全局变量 $wp_block_types 数组中。 $wp_block_types 就是一个存储所有已注册区块的“大仓库”。 当 WordPress 需要查找或使用某个区块时,就会从这个“大仓库”里查找。

2.4 返回注册的区块类型

return $block_type;

最后,函数返回注册成功的 WP_Block_Type 对象。 这可以让调用者在注册之后,立即使用这个对象进行一些操作。

三、$wp_block_types:区块的“中央数据库”

现在,我们来重点关注一下 $wp_block_types 这个全局变量。 它是一个全局数组,用于存储所有已注册的区块类型。 它的结构大致如下:

$wp_block_types = array(
    'core/paragraph' => WP_Block_Type 对象,
    'core/image' => WP_Block_Type 对象,
    'core/heading' => WP_Block_Type 对象,
    // ... 更多区块
);

每个元素的键名是区块的名称(例如 core/paragraph), 键值是对应的 WP_Block_Type 对象。

四、一个完整的例子

让我们通过一个完整的例子,来演示如何使用 register_block_type() 注册一个自定义区块:

<?php
/**
 * Registers the my-custom-block block.
 */
function register_my_custom_block() {
    register_block_type(
        'my-plugin/my-custom-block',
        array(
            'attributes' => array(
                'content' => array(
                    'type' => 'string',
                    'default' => 'Hello, world!',
                ),
            ),
            'render_callback' => 'render_my_custom_block',
        )
    );
}
add_action( 'init', 'register_my_custom_block' );

/**
 * Render the my-custom-block block.
 *
 * @param array $attributes The block attributes.
 * @return string The rendered HTML.
 */
function render_my_custom_block( $attributes ) {
    $content = isset( $attributes['content'] ) ? $attributes['content'] : 'Hello, world!';
    return '<p>' . esc_html( $content ) . '</p>';
}

在这个例子中:

  1. 我们定义了一个 register_my_custom_block() 函数,用于注册我们的自定义区块。
  2. register_block_type() 函数中,我们传入了两个参数:

    • 'my-plugin/my-custom-block':区块的名称。
    • 一个包含区块属性和渲染函数的回调函数数组。
  3. 我们使用 add_action( 'init', 'register_my_custom_block' )register_my_custom_block() 函数挂载到 init 钩子上,以便在 WordPress 初始化时注册我们的区块。
  4. 我们定义了一个 render_my_custom_block() 函数,用于渲染我们的区块。这个函数接收区块的属性作为参数,并返回渲染后的 HTML。

当 WordPress 初始化时,register_my_custom_block() 函数会被调用,register_block_type() 函数会将我们的自定义区块注册到 $wp_block_types 数组中。 这样,Gutenberg 编辑器就能识别并使用我们的自定义区块了。

五、一些需要注意的点

  • 区块名称的格式: 区块名称必须符合 namespace/block-name 的格式。 namespace 通常是你的插件或主题的名称,block-name 是区块的名称。 例如,my-plugin/my-custom-block
  • 属性的定义: 区块的属性定义非常重要,它决定了区块可以接受哪些用户输入,以及如何存储这些输入。 register_block_type() 函数的 $args 数组中的 attributes 字段用于定义区块的属性。
  • 渲染函数: 渲染函数负责将区块的属性转换为最终的 HTML。 register_block_type() 函数的 $args 数组中的 render_callback 字段用于指定渲染函数的回调函数。
  • init 钩子: 确保在 init 钩子上注册你的区块。 这样可以保证在 Gutenberg 编辑器加载之前,你的区块已经被注册。
  • 避免重复注册: 在注册区块之前,一定要检查是否已经注册了同名的区块。 可以使用 isset( $wp_block_types[ $block_name ] ) 来检查。

六、深入思考

  • register_block_type() 函数只是区块注册的“门面”,真正的核心是 WP_Block_Type 类和 $wp_block_types 全局变量。
  • $wp_block_types 全局变量是 WordPress 区块系统的“中央数据库”,所有已注册的区块都存储在这里。
  • 理解 register_block_type() 函数的源码,可以帮助我们更好地理解 WordPress 区块系统的工作原理,从而更好地开发自定义区块。

七、总结

今天,我们一起深入研究了 WordPress register_block_type() 函数的源码,了解了它如何将区块定义注册到 $wp_block_types 全局变量中。 希望通过今天的讲座,大家能够对 WordPress 区块系统有更深入的理解。

我们用一张表格来总结一下今天的内容:

函数/变量 作用
register_block_type() 注册一个区块类型,并将区块定义存储到 $wp_block_types 全局变量中。
$wp_block_types 全局变量,存储所有已注册的区块类型。 它是 WordPress 区块系统的“中央数据库”。
WP_Block_Type 类,用于表示一个区块类型。 包含了区块的所有信息,例如名称、属性、渲染函数等等。
attributes 区块属性,定义了区块可以接受哪些用户输入,以及如何存储这些输入。
render_callback 渲染函数,负责将区块的属性转换为最终的 HTML。
init WordPress 钩子,建议在此钩子上注册区块,以确保在 Gutenberg 编辑器加载之前,区块已经被注册。

希望这篇技术文章能够帮助大家更好地理解 WordPress 区块注册的机制。 如果大家还有什么问题,欢迎随时提问。

谢谢大家!

发表回复

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