# 2.1 WordPress 插件开发基础

简单来说,一个 WordPress 插件是一个带有 WordPress 插件头注释的 PHP 文件(头文件),强烈建议为插件创建一个目录,以便保持插件文件整齐有序、便于维护。

要创建一个新的 WordPress 插件,请按照以下步骤进行。

  • 找到 WordPress 站点的 wp-content/plugins 插件目录
  • 创建建一个新目录,并将其命名为插件的名称,e.g.(plugin-name)
  • 切换到你新建的目录(plugin-name)
  • 创建一个 PHP 文件 plugin-name.php

至此我们可以通过修改 plugin-name.php 开始开发 WordPress 插件,首先我们在当前文件中添加一个 头注释,头注释至少需要包含插件的名称供 WordPress 识别获取。

<?php
/*
Plugin Name: plugin-name 
*/
1
2
3
4

如上操作后,打开 WordPress 插件列表界面,我们就可以在 WordPress 已安装的插件中看到我们创建的插件了。

# 钩子:Action 和 Filter

WordPress 钩子可以让我们在特定的时机介入 WordPress 的代码执行流程,不需要改变核心文件就可以改变 WordPress 的行为。

WordPress 中提供了2中类型的钩子,Action 和 Filter,Action可以让我们添加修改 WordPress 功能,Fliter可以让我们修改用户提交或展示给用户的内容。

除了在 WordPress 插件中使用钩子,钩子也被广泛应用于 WordPress 核心的功能中以及主题下的功能(functions.php),WordPress 埋下了函数执行过程中一些动作钩子(e.g. init wp_head wp_footer etc.),以便我们开发的时候使用。

# 钩子:插件开发的基础钩子

创建插件时需要的3个基础钩子是 register_activation_hook(),register_deactivation_hook() 和 register_uninstall_hook()。

  • register_activation_hook 我们启用插件时会运行,我们可以使用这个钩子挂载一个函数来初始化的插件,e.g.在数据表中添加一些默认设置。
  • register_deactivation_hook 我们禁用插件时运行,我们可以挂载一个清理插件数据的函数来清理一些临时数据或者设置插件已禁用供以后插件再次开启时使用。
  • register_uninstall_hook 我们删除插件时运行,我们可以挂载一个清理插件所有数据的函数来清理数据库中不再需要的插件数据。

# 添加自定义钩子

使用 do_action() 函数来添加我们自定义钩子,通过我们的自定义钩子,其他开发者可以通过扩展或修改我们的插件来适应他们的个性化需求。

# 移除挂载到钩子上的函数

使用 remove_action() 函数来移除挂载到某个钩子上的函数,注意插件 action 的优先级。

# WordPress API

WordPress 提供了很多 核心应用编程接口(API),这些 API 优势如下

  • 核心API通过提供挂钩,动作,过滤器,帮助程序功能,使WordPress的构建工作更加轻松。
  • WordPress为您完成了“繁重的工作”(数据库调用,输入验证,安全性,表单构建)。
  • 使用核心API可确保您的代码既向后兼容又面向未来。
  • 这些API允许无缝集成到插件或主题选项页面。

总之,这些 API 可以大大减少我们在开发插件时需要的代码。我们不想重复发明轮子,特别是现有的轮子经过了很多开发和测试、已经足够稳定了之后。

# 2.2 启用/禁用钩子

启用和禁用钩子可以让我们在启用或禁用插件时进行一些操作。

启用时,我们可以挂载一个函数来添加 URL 重写规则,或者添加插件设置到数据库中。

禁用时,我们可以挂载一个函数来删除临时数据,e.g. 缓存和临时文件和目录。

# 启用钩子

添加一个函数到启用钩子上,使用 register_activation_hook() 函数。

<?php

register_activation_hook( __FILE__, 'pluginprefix_function' );

1
2
3
4

# 禁用钩子

添加一个函数到禁用钩子上,使用 register_deactivation_hook() 函数。

PS: 每个函数中的第一个参数都指向插件的主文件(也就是包含插件头注释的文件),这两个函数通常在插件主文件中被触发,当然,我们也可以把这两个函数放在其他文件中,这种情况下,我们必须更新第一个参数。

# e.g.

启用钩子最常见的用途之一就是在插件注册自定义文章类型时,刷新 WordPress 固定链接缓存,这样做可以让我们避免遇到 404 错误。

<?php

function my_custom_post_movie() {
  $labels = array(
    'name'               => _x( 'Movies', 'post type 名称' ),
    'singular_name'      => _x( 'Movie', 'post type 单个 item 时的名称,因为英文有复数' ),
    'add_new'            => _x( '新建电影', '添加新内容的链接名称' ),
    'add_new_item'       => __( '新建一个电影' ),
    'edit_item'          => __( '编辑电影' ),
    'new_item'           => __( '新电影' ),
    'all_items'          => __( '所有电影' ),
    'view_item'          => __( '查看电影' ),
    'search_items'       => __( '搜索电影' ),
    'not_found'          => __( '没有找到有关电影' ),
    'not_found_in_trash' => __( '回收站里面没有相关电影' ),
    'parent_item_colon'  => '',
    'menu_name'          => '电影'
  );
  $args = array(
    'labels'        => $labels,
    'description'   => '我们网站的电影信息',
    'public'        => true,
    'menu_position' => 5,
    'supports'      => array( 'title', 'editor', 'thumbnail', 'excerpt', 'comments' ),
    'has_archive'   => true
  );
  register_post_type( 'movie', $args );
}
add_action( 'init', 'my_custom_post_movie' );

function mla_install() {
    // 触发注册自定义文章类型操作
    my_custom_post_movie();
 
    // 注册文章类型之后,刷新固定链接缓存
    flush_rewrite_rules();
}
register_activation_hook( __FILE__, 'mla_install' );

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

上面的示例是激活插件时的操作,我们来看一下如何在插件禁用时,撤销上面的操作。

<?php

function mla_deactivation() {
    // 反注册文章类型
    unregister_post_type( 'movie' );
    // 刷新固定链接缓存
    flush_rewrite_rules();
}
register_deactivation_hook( __FILE__, 'mla_deactivation' );

1
2
3
4
5
6
7
8
9
10

# 2.3 删除/卸载插件

如果我们的插件添加了一些设置到数据库中,或者在用户使用插件时生成了一些缓存文件,当用户删除/卸载插件时,这些设置和文件很可能已经没用了。我们需要在用户删除/卸载插件时删除这些数据。下面的表格说明了停用和删除/卸载钩子之间的区别。

动作 禁用钩子 卸载钩子
刷新插件产生的缓存
刷新 Url 重写规则缓存
从 {$wpdb->prefix}_options 数据表中移除插件添加的数据
从 wpdb 中移除数据表

# 方法 1:register_uninstall_hook

挂载卸载函数到 register_uninstall_hook() 钩子上。

<?php

register_uninstall_hook(__FILE__, 'pluginprefix_function');

1
2
3
4

# 方法 2:uninstall.php

使用此方法,我们需要在插件根目录中创建一个名为 uninstall.php 的文件,这个文件中的函数会在用户卸载插件时自动运行。

# 2.4 最佳实践

以下是关于 WordPress 插件开发的一些最佳实践,可以帮助我们在开发插件时更合理的组织代码。

  • 避免命名冲突

当我们的插件和其他主题或插件使用了同一个函数或类名时,就会发生命名冲突。幸运的是,我们可以通过下面的方法来避免命名冲突。

  • 为什么会产品命名冲突

默认情况下,所有变量、函数和类全部在全局命名空间中定义,也就是说,一个插件或主题可以覆盖另外一个插件的变量、函数和类(在函数或类中定义的变量不会被覆盖)。

  • 为所有定义加上前缀

所有变量、函数和类都应该有一个唯一前缀,前缀可以帮助我们避免和其他插件使用同一个变量、函数和类名,也可以避免这些定义被其他插件或主题覆盖。

  • 检查所有的定义

PHP 提供了许多函数(变量: isset() 、函数: function_exists()、类: class_exists()、常量: defined())来验证变量、函数、类和常量是否存在,如果这些定义存在,这些函数将返回 true

<?php

if ( !function_exists( 'mla_init' ) ) {
    function mla_init() {
        return something;
    }
}

1
2
3
4
5
6
7
8
  • OOP 面向对象编程

解决命名空间冲突更简单的办法是使用类来组织插件的代码,使用这个方法时,我们仍然需要检查类名是否已经被其他插件或主题使用,相对于非 OOP 变成来说,我们只需要检查类名即可。

<?php

if ( !class_exists( 'WPOrg_Plugin' ) ) {
    class WPOrg_Plugin
    {
        public static function init() {
            register_setting( 'wporg_settings', 'wporg_option_foo' );
        }
 
        public static function get_foo() {
            return get_option( 'wporg_option_foo' );
        }
    }
 
    WPOrg_Plugin::init();
    WPOrg_Plugin::get_foo();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • 组织插件文件

插件根目录应该包含一个名为插件名称的 plugin-name.php 文件,如果需要在卸载时执行一些操作,还可以包含 uninstall.php 文件,其他文件尽可能组织到插件的子目录中,子目录名称应该尽量符合语义化规则。

  • 目录结构

一个清晰的目录结构应该把类型或功能相似的文件保存在同一个目录中,下面是一个示例目录结构。

/plugin-name
······/plugin-name.php
······/uninstall.php
······/languages
······/includes
······/admin
······/public
............/css
............/js
............/images
1
2
3
4
5
6
7
8
9
10
  • 按需加载

将插件的管理代码和公共代码分开是很有必要的,我们可以使用 WordPress 条件函数 is_admin 来实现

<?php

if ( is_admin() ) {
    // admin模式下加载
    require_once( dirname( __FILE__ ) . '/admin/plugin-name-admin.php' );
}

1
2
3
4
5
6
7
  • 入门样板插件

WordPress Plugin Boilerplate