wordpress-plugin-dev

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

WordPress Plugin Development

WordPress插件开发

Our Project Structure

我们的项目结构

We use a specific structure with a root project directory and nested plugin directory:
plugin-project-name/               # Root project directory
├── composer.json                  # Dev dependencies (phpcs, phpunit, phpstan)
├── phpcs.xml                      # WordPress-Extra ruleset
├── phpcs_sec.xml                  # Security-specific checks
├── phpstan.neon.dist              # Static analysis config
├── phpunit.xml.dist               # PHPUnit configuration
├── tests/                         # Tests at root level
│   ├── bootstrap.php              # PHPUnit bootstrap
│   ├── Unit/                      # Unit tests
│   └── Integration/               # Integration tests
├── vendor/                        # Composer dev dependencies
└── plugin-name/                   # Actual plugin directory
    ├── plugin-name.php            # Main plugin file
    ├── composer.json              # Plugin-specific dependencies
    ├── src/                       # Namespaced source code
    │   ├── Core/
    │   │   └── Main.php           # Main initialization class
    │   ├── Admin/
    │   │   └── AdminPage.php
    │   ├── Data/
    │   └── REST/
    │       └── DiagnosticsEndpoint.php
    ├── vendor/                    # Plugin dependencies
    ├── languages/                 # Translation files
    └── README.md
我们采用特定的结构,包含根项目目录和嵌套的插件目录:
plugin-project-name/               # 根项目目录
├── composer.json                  # 开发依赖(phpcs, phpunit, phpstan)
├── phpcs.xml                      # WordPress-Extra规则集
├── phpcs_sec.xml                  # 安全专项检查
├── phpstan.neon.dist              # 静态分析配置
├── phpunit.xml.dist               # PHPUnit配置
├── tests/                         # 根级别的测试目录
│   ├── bootstrap.php              # PHPUnit引导文件
│   ├── Unit/                      # 单元测试
│   └── Integration/               # 集成测试
├── vendor/                        # Composer开发依赖
└── plugin-name/                   # 实际插件目录
    ├── plugin-name.php            # 主插件文件
    ├── composer.json              # 插件专属依赖
    ├── src/                       # 命名空间源代码
    │   ├── Core/
    │   │   └── Main.php           # 主初始化类
    │   ├── Admin/
    │   │   └── AdminPage.php
    │   ├── Data/
    │   └── REST/
    │       └── DiagnosticsEndpoint.php
    ├── vendor/                    # 插件依赖
    ├── languages/                 # 翻译文件
    └── README.md

Key Points:

核心要点:

  • Root level has development tools (phpcs, phpunit, phpstan)
  • Plugin lives in subdirectory (e.g.,
    fullworks-support-diagnostics/
    )
  • Use namespaced classes in
    src/
    directory
  • Composer autoloading for both dev tools and plugin classes
  • Tests at root level, outside plugin directory
  • Separate composer.json for plugin distribution vs development
  • 根目录包含开发工具(phpcs, phpunit, phpstan)
  • 插件位于子目录中(例如:
    fullworks-support-diagnostics/
  • src/
    目录中使用命名空间类
  • 为开发工具和插件类启用Composer自动加载
  • 测试目录位于根级别,插件目录之外
  • 插件分发与开发使用独立的composer.json

Main Plugin File Pattern

主插件文件模板

php
<?php
/**
 * Plugin Name: Your Plugin Name
 * Description: Brief description
 * Version: 1.0.0
 * Author: Your Name
 * License: GPL-2.0+
 * Text Domain: plugin-text-domain
 * Requires at least: 5.8
 * Requires PHP: 7.4
 */

// Prevent direct access
if (!defined('WPINC')) {
    die;
}

// Define plugin constants
define('PREFIX_PLUGIN_VERSION', '1.0.0');
define('PREFIX_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('PREFIX_PLUGIN_URL', plugin_dir_url(__FILE__));

// Load Composer autoloader
require_once PREFIX_PLUGIN_DIR . 'vendor/autoload.php';

// Initialize the plugin
function prefix_initialize_plugin() {
    new \Vendor\PluginName\Core\Main();
}

add_action('plugins_loaded', 'prefix_initialize_plugin', 5);
php
<?php
/**
 * Plugin Name: 你的插件名称
 * Description: 简要描述
 * Version: 1.0.0
 * Author: 你的名字
 * License: GPL-2.0+
 * Text Domain: plugin-text-domain
 * Requires at least: 5.8
 * Requires PHP: 7.4
 */

// 禁止直接访问
if (!defined('WPINC')) {
    die;
}

// 定义插件常量
define('PREFIX_PLUGIN_VERSION', '1.0.0');
define('PREFIX_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('PREFIX_PLUGIN_URL', plugin_dir_url(__FILE__));

// 加载Composer自动加载器
require_once PREFIX_PLUGIN_DIR . 'vendor/autoload.php';

// 初始化插件
function prefix_initialize_plugin() {
    new \Vendor\PluginName\Core\Main();
}

add_action('plugins_loaded', 'prefix_initialize_plugin', 5);

Namespaced Class Structure

命名空间类结构

php
<?php
namespace Vendor\PluginName\Core;

use Vendor\PluginName\Admin\AdminPage;
use Vendor\PluginName\REST\DiagnosticsEndpoint;

class Main {
    const VERSION = '1.0.0';
    const OPTION_NAME = 'prefix_settings';

    private $settings;
    private $admin_page;

    public function __construct() {
        $this->settings = get_option(self::OPTION_NAME, []);
        $this->init_components();
        $this->setup_hooks();
    }

    private function init_components() {
        $this->admin_page = new AdminPage($this->settings);
    }

    private function setup_hooks() {
        add_action('admin_menu', [$this->admin_page, 'add_admin_menu']);
    }
}
php
<?php
namespace Vendor\PluginName\Core;

use Vendor\PluginName\Admin\AdminPage;
use Vendor\PluginName\REST\DiagnosticsEndpoint;

class Main {
    const VERSION = '1.0.0';
    const OPTION_NAME = 'prefix_settings';

    private $settings;
    private $admin_page;

    public function __construct() {
        $this->settings = get_option(self::OPTION_NAME, []);
        $this->init_components();
        $this->setup_hooks();
    }

    private function init_components() {
        $this->admin_page = new AdminPage($this->settings);
    }

    private function setup_hooks() {
        add_action('admin_menu', [$this->admin_page, 'add_admin_menu']);
    }
}

WordPress Coding Standards (WPCS)

WordPress编码规范(WPCS)

Code Style

代码风格

  • Use tabs for indentation (not spaces)
  • Use proper PHPDoc blocks for all classes, methods, and functions
  • Follow WordPress naming conventions:
    • Namespaced Classes:
      Vendor\PluginName\Feature\ClassName
    • Functions:
      prefix_function_name()
    • Files in src/:
      ClassName.php
      (matching class name)
  • Use Yoda conditions:
    if ( true === $value )
  • Space after control structures:
    if ( condition ) {
  • Single quotes for strings unless variables or special characters needed
  • 使用制表符缩进(而非空格)
  • 为所有类、方法和函数添加合适的PHPDoc注释块
  • 遵循WordPress命名约定:
    • 命名空间类:
      Vendor\PluginName\Feature\ClassName
    • 函数:
      prefix_function_name()
    • src/目录下的文件:
      ClassName.php
      (与类名匹配)
  • 使用Yoda条件:
    if ( true === $value )
  • 控制结构后加空格:
    if ( condition ) {
  • 字符串使用单引号,除非需要包含变量或特殊字符

Our PHPCS Configuration

我们的PHPCS配置

We use WordPress-Extra with custom exclusions:
xml
<rule ref="WordPress-Extra">
    <exclude name="WordPress.Files.FileName.NotHyphenatedLowercase"/>
    <exclude name="Generic.WhiteSpace"/>
    <exclude name="PEAR.NamingConventions"/>
    <exclude name="Universal.Files.SeparateFunctionsFromOO"/>
    <exclude name="Universal.Operators.StrictComparisons"/>
</rule>
Run checks with:
bash
composer phpcs        # Full WPCS check
composer check        # PHP compatibility + WPCS
composer phpcbf       # Auto-fix issues
我们使用WordPress-Extra规则集并自定义排除项:
xml
<rule ref="WordPress-Extra">
    <exclude name="WordPress.Files.FileName.NotHyphenatedLowercase"/>
    <exclude name="Generic.WhiteSpace"/>
    <exclude name="PEAR.NamingConventions"/>
    <exclude name="Universal.Files.SeparateFunctionsFromOO"/>
    <exclude name="Universal.Operators.StrictComparisons"/>
</rule>
运行检查命令:
bash
composer phpcs        # 完整WPCS检查
composer check        # PHP兼容性检查 + WPCS检查
composer phpcbf       # 自动修复问题

Security Best Practices

安全最佳实践

Input Validation and Sanitization

输入验证与清理

  • Always sanitize input:
    • sanitize_text_field()
      - general text
    • sanitize_email()
      - email addresses
    • sanitize_url()
      - URLs
    • absint()
      - positive integers
    • intval()
      - integers
    • wp_kses()
      /
      wp_kses_post()
      - HTML content
  • 始终清理输入:
    • sanitize_text_field()
      - 通用文本
    • sanitize_email()
      - 邮箱地址
    • sanitize_url()
      - URL
    • absint()
      - 正整数
    • intval()
      - 整数
    • wp_kses()
      /
      wp_kses_post()
      - HTML内容

Output Escaping

输出转义

  • Always escape output:
    • esc_html()
      - HTML content
    • esc_attr()
      - HTML attributes
    • esc_url()
      - URLs
    • esc_js()
      - JavaScript strings
    • wp_kses_post()
      - Post content with safe HTML
  • 始终转义输出:
    • esc_html()
      - HTML内容
    • esc_attr()
      - HTML属性
    • esc_url()
      - URL
    • esc_js()
      - JavaScript字符串
    • wp_kses_post()
      - 包含安全HTML的文章内容

Nonce Verification

Nonce验证

php
// Creating nonce
wp_nonce_field( 'action_name', 'nonce_field_name' );

// Verifying nonce
if ( ! isset( $_POST['nonce_field_name'] ) ||
     ! wp_verify_nonce( $_POST['nonce_field_name'], 'action_name' ) ) {
    wp_die( 'Security check failed' );
}
php
// 创建nonce
wp_nonce_field( 'action_name', 'nonce_field_name' );

// 验证nonce
if ( ! isset( $_POST['nonce_field_name'] ) ||
     ! wp_verify_nonce( $_POST['nonce_field_name'], 'action_name' ) ) {
    wp_die( '安全检查失败' );
}

Capability Checks

权限检查

php
if ( ! current_user_can( 'manage_options' ) ) {
    wp_die( 'Unauthorized access' );
}
php
if ( ! current_user_can( 'manage_options' ) ) {
    wp_die( '未授权访问' );
}

SQL Security

SQL安全

  • Use
    $wpdb->prepare()
    for all SQL queries
  • Never concatenate user input into SQL
php
$wpdb->get_results( $wpdb->prepare(
    "SELECT * FROM {$wpdb->prefix}table WHERE id = %d AND name = %s",
    $id,
    $name
) );
  • 所有SQL查询均使用
    $wpdb->prepare()
  • 绝不要将用户输入直接拼接进SQL
php
$wpdb->get_results( $wpdb->prepare(
    "SELECT * FROM {$wpdb->prefix}table WHERE id = %d AND name = %s",
    $id,
    $name
) );

WordPress Hooks System

WordPress钩子系统

Actions vs Filters

动作(Actions)与过滤器(Filters)

  • Actions: Execute code at specific points (side effects)
  • Filters: Modify data before returning
  • 动作: 在特定执行点运行代码(产生副作用)
  • 过滤器: 返回前修改数据

Best Practices

最佳实践

php
// Use unique prefixes to avoid conflicts
add_action( 'init', 'prefix_init_function' );
add_filter( 'the_content', 'prefix_modify_content', 10, 1 );

// Always specify priority (default: 10) and accepted args
add_action( 'save_post', 'prefix_save_post_action', 10, 2 );

// Remove actions/filters when needed
remove_action( 'init', 'prefix_init_function' );
php
// 使用唯一前缀避免冲突
add_action( 'init', 'prefix_init_function' );
add_filter( 'the_content', 'prefix_modify_content', 10, 1 );

// 始终指定优先级(默认:10)和接受的参数数量
add_action( 'save_post', 'prefix_save_post_action', 10, 2 );

// 必要时移除动作/过滤器
remove_action( 'init', 'prefix_init_function' );

Hook Timing

钩子时机

  • plugins_loaded
    - After plugins loaded
  • init
    - Initialize plugin features
  • admin_init
    - Admin-specific initialization
  • wp_enqueue_scripts
    - Enqueue frontend assets
  • admin_enqueue_scripts
    - Enqueue admin assets
  • plugins_loaded
    - 插件加载完成后
  • init
    - 初始化插件功能
  • admin_init
    - 后台专属初始化
  • wp_enqueue_scripts
    - 注册前端资源
  • admin_enqueue_scripts
    - 注册后台资源

Database Operations

数据库操作

Options API

选项API

php
// Get option with default
$value = get_option( 'prefix_option_name', 'default_value' );

// Update option
update_option( 'prefix_option_name', $value );

// Autoload consideration
add_option( 'prefix_option_name', $value, '', 'no' ); // Don't autoload
php
// 获取选项并设置默认值
$value = get_option( 'prefix_option_name', 'default_value' );

// 更新选项
update_option( 'prefix_option_name', $value );

// 自动加载设置
add_option( 'prefix_option_name', $value, '', 'no' ); // 不自动加载

Custom Tables

自定义表

  • Use
    $wpdb->prefix
    for table names
  • Create tables on plugin activation with
    dbDelta()
  • Include charset and collation
  • 表名使用
    $wpdb->prefix
    前缀
  • 在插件激活时使用
    dbDelta()
    创建表
  • 包含字符集和排序规则

Internationalization (i18n)

国际化(i18n)

php
// Text domain should match plugin slug
__( 'Text to translate', 'plugin-text-domain' );
_e( 'Text to echo', 'plugin-text-domain' );
esc_html__( 'Text to translate and escape', 'plugin-text-domain' );
esc_html_e( 'Text to echo and escape', 'plugin-text-domain' );

// With variables
sprintf(
    /* translators: %s: user name */
    __( 'Hello, %s!', 'plugin-text-domain' ),
    $user_name
);
php
// 文本域需与插件slug匹配
__( '待翻译文本', 'plugin-text-domain' );
_e( '直接输出的待翻译文本', 'plugin-text-domain' );
esc_html__( '待翻译并转义的文本', 'plugin-text-domain' );
esc_html_e( '直接输出的待翻译并转义的文本', 'plugin-text-domain' );

// 包含变量的情况
sprintf(
    /* translators: %s: 用户名 */
    __( '你好,%s!', 'plugin-text-domain' ),
    $user_name
);

REST API

REST API

php
add_action( 'rest_api_init', 'prefix_register_routes' );

function prefix_register_routes() {
    register_rest_route( 'plugin/v1', '/endpoint', array(
        'methods'             => 'POST',
        'callback'            => 'prefix_endpoint_callback',
        'permission_callback' => 'prefix_permission_check',
    ) );
}
php
add_action( 'rest_api_init', 'prefix_register_routes' );

function prefix_register_routes() {
    register_rest_route( 'plugin/v1', '/endpoint', array(
        'methods'             => 'POST',
        'callback'            => 'prefix_endpoint_callback',
        'permission_callback' => 'prefix_permission_check',
    ) );
}

Error Handling

错误处理

php
// Use WP_Error for error handling
if ( is_wp_error( $result ) ) {
    return $result;
}

// Create errors
return new WP_Error( 'error_code', __( 'Error message', 'text-domain' ) );
php
// 使用WP_Error进行错误处理
if ( is_wp_error( $result ) ) {
    return $result;
}

// 创建错误
return new WP_Error( 'error_code', __( '错误信息', 'text-domain' ) );

Performance Optimization

性能优化

  • Use transients for caching:
    set_transient()
    ,
    get_transient()
  • Lazy load when possible
  • Minimize database queries
  • Use
    wp_cache_*
    functions for object caching
  • Enqueue minified assets in production
  • 使用Transients进行缓存:
    set_transient()
    ,
    get_transient()
  • 尽可能实现懒加载
  • 减少数据库查询次数
  • 使用
    wp_cache_*
    函数进行对象缓存
  • 生产环境中注册压缩后的资源

Testing with wp-env

使用wp-env进行测试

We use
@wordpress/env
(wp-env) for local development and testing:
我们使用
@wordpress/env
(wp-env)进行本地开发和测试:

Setup

配置

bash
undefined
bash
undefined

Install wp-env globally (if not already installed)

全局安装wp-env(如果尚未安装)

npm -g install @wordpress/env
npm -g install @wordpress/env

Start WordPress environment

启动WordPress环境

wp-env start
wp-env start

Stop environment

停止环境

wp-env stop
wp-env stop

Clean/reset environment

清理/重置环境

wp-env clean
undefined
wp-env clean
undefined

Running Tests

运行测试

bash
undefined
bash
undefined

Set up test environment variable

设置测试环境变量

export WP_PHPUNIT__TESTS_CONFIG=/path/to/wp-tests-config.php
export WP_PHPUNIT__TESTS_CONFIG=/path/to/wp-tests-config.php

Run PHPUnit tests

运行PHPUnit测试

composer test
composer test

Or run directly with wp-env

或者通过wp-env直接运行

wp-env run tests-cli --env-cwd=wp-content/plugins/plugin-name vendor/bin/phpunit
undefined
wp-env run tests-cli --env-cwd=wp-content/plugins/plugin-name vendor/bin/phpunit
undefined

PHPUnit Configuration

PHPUnit配置

Our
phpunit.xml.dist
defines two test suites:
  • unit: Unit tests in
    tests/Unit/
    (no WordPress dependencies)
  • integration: Integration tests in
    tests/Integration/
    (with WordPress)
我们的
phpunit.xml.dist
定义了两个测试套件:
  • unit:位于
    tests/Unit/
    的单元测试(无WordPress依赖)
  • integration:位于
    tests/Integration/
    的集成测试(依赖WordPress)

Test Bootstrap

测试引导文件

The
tests/bootstrap.php
file:
  • Loads Composer autoloader
  • Checks for
    WP_PHPUNIT__TESTS_CONFIG
    environment variable
  • Requires WordPress test configuration
tests/bootstrap.php
文件:
  • 加载Composer自动加载器
  • 检查
    WP_PHPUNIT__TESTS_CONFIG
    环境变量
  • 引入WordPress测试配置

Writing Tests

编写测试

php
<?php
namespace Tests\Unit;

use PHPUnit\Framework\TestCase;
use Yoast\PHPUnitPolyfills\Polyfills\AssertEqualsCanonicalizing;

class SampleTest extends TestCase {
    use AssertEqualsCanonicalizing;

    public function test_sample() {
        $this->assertTrue(true);
    }
}
php
<?php
namespace Tests\Unit;

use PHPUnit\Framework\TestCase;
use Yoast\PHPUnitPolyfills\Polyfills\AssertEqualsCanonicalizing;

class SampleTest extends TestCase {
    use AssertEqualsCanonicalizing;

    public function test_sample() {
        $this->assertTrue(true);
    }
}

Static Analysis

静态分析

PHPStan

PHPStan

bash
composer phpstan      # Run static analysis
Configuration in
phpstan.neon.dist
with WordPress-specific rules via
szepeviktor/phpstan-wordpress
.
bash
composer phpstan      # 运行静态分析
phpstan.neon.dist
中的配置通过
szepeviktor/phpstan-wordpress
引入了WordPress专属规则。

Build and Distribution

构建与分发

Building Plugin

构建插件

bash
composer build
This will:
  1. Clean
    zipped
    directory
  2. Install production dependencies in plugin directory
  3. Create zip file in
    zipped/
    directory
bash
composer build
该命令会:
  1. 清理
    zipped
    目录
  2. 在插件目录中安装生产依赖
  3. zipped/
    目录中创建压缩包

Composer Scripts

Composer脚本

  • composer phpcs
    - Run coding standards check
  • composer check
    - Run PHP compatibility checks (7.4-8.3) + WPCS
  • composer phpcbf
    - Auto-fix coding standards issues
  • composer phpstan
    - Run static analysis
  • composer test
    - Run PHPUnit tests
  • composer build
    - Build distribution zip
  • composer phpcs
    - 运行编码规范检查
  • composer check
    - 运行PHP兼容性检查(7.4-8.3)+ WPCS检查
  • composer phpcbf
    - 自动修复编码规范问题
  • composer phpstan
    - 运行静态分析
  • composer test
    - 运行PHPUnit测试
  • composer build
    - 构建分发压缩包

Testing Checklist

测试检查清单

  • All tests pass (
    composer test
    )
  • No coding standards violations (
    composer check
    )
  • No static analysis errors (
    composer phpstan
    )
  • Test in wp-env with WP_DEBUG enabled
  • Test with multiple PHP versions (7.4-8.3)
  • Verify compatibility with latest WordPress version
  • Check that plugin works after building (
    composer build
    )
  • 所有测试通过(
    composer test
  • 无编码规范违规(
    composer check
  • 无静态分析错误(
    composer phpstan
  • 在开启WP_DEBUG的wp-env环境中测试
  • 在多个PHP版本(7.4-8.3)中测试
  • 验证与最新WordPress版本的兼容性
  • 确认构建后的插件可正常工作(
    composer build