wordpress-plugin-dev
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWordPress 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.mdKey Points:
核心要点:
- Root level has development tools (phpcs, phpunit, phpstan)
- Plugin lives in subdirectory (e.g., )
fullworks-support-diagnostics/ - Use namespaced classes in directory
src/ - 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/: (matching class name)
ClassName.php
- Namespaced Classes:
- 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:
- - general text
sanitize_text_field() - - email addresses
sanitize_email() - - URLs
sanitize_url() - - positive integers
absint() - - integers
intval() - /
wp_kses()- HTML contentwp_kses_post()
- 始终清理输入:
- - 通用文本
sanitize_text_field() - - 邮箱地址
sanitize_email() - - URL
sanitize_url() - - 正整数
absint() - - 整数
intval() - /
wp_kses()- HTML内容wp_kses_post()
Output Escaping
输出转义
- Always escape output:
- - HTML content
esc_html() - - HTML attributes
esc_attr() - - URLs
esc_url() - - JavaScript strings
esc_js() - - Post content with safe HTML
wp_kses_post()
- 始终转义输出:
- - HTML内容
esc_html() - - HTML属性
esc_attr() - - URL
esc_url() - - JavaScript字符串
esc_js() - - 包含安全HTML的文章内容
wp_kses_post()
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 for all SQL queries
$wpdb->prepare() - 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
钩子时机
- - After plugins loaded
plugins_loaded - - Initialize plugin features
init - - Admin-specific initialization
admin_init - - Enqueue frontend assets
wp_enqueue_scripts - - Enqueue admin assets
admin_enqueue_scripts
- - 插件加载完成后
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 autoloadphp
// 获取选项并设置默认值
$value = get_option( 'prefix_option_name', 'default_value' );
// 更新选项
update_option( 'prefix_option_name', $value );
// 自动加载设置
add_option( 'prefix_option_name', $value, '', 'no' ); // 不自动加载Custom Tables
自定义表
- Use for table names
$wpdb->prefix - 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 functions for object caching
wp_cache_* - Enqueue minified assets in production
- 使用Transients进行缓存:,
set_transient()get_transient() - 尽可能实现懒加载
- 减少数据库查询次数
- 使用函数进行对象缓存
wp_cache_* - 生产环境中注册压缩后的资源
Testing with wp-env
使用wp-env进行测试
We use (wp-env) for local development and testing:
@wordpress/env我们使用(wp-env)进行本地开发和测试:
@wordpress/envSetup
配置
bash
undefinedbash
undefinedInstall 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
undefinedwp-env clean
undefinedRunning Tests
运行测试
bash
undefinedbash
undefinedSet 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
undefinedwp-env run tests-cli --env-cwd=wp-content/plugins/plugin-name vendor/bin/phpunit
undefinedPHPUnit Configuration
PHPUnit配置
Our defines two test suites:
phpunit.xml.dist- unit: Unit tests in (no WordPress dependencies)
tests/Unit/ - integration: Integration tests in (with WordPress)
tests/Integration/
我们的定义了两个测试套件:
phpunit.xml.dist- unit:位于的单元测试(无WordPress依赖)
tests/Unit/ - integration:位于的集成测试(依赖WordPress)
tests/Integration/
Test Bootstrap
测试引导文件
The file:
tests/bootstrap.php- Loads Composer autoloader
- Checks for environment variable
WP_PHPUNIT__TESTS_CONFIG - 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 analysisConfiguration in with WordPress-specific rules via .
phpstan.neon.distszepeviktor/phpstan-wordpressbash
composer phpstan # 运行静态分析phpstan.neon.distszepeviktor/phpstan-wordpressBuild and Distribution
构建与分发
Building Plugin
构建插件
bash
composer buildThis will:
- Clean directory
zipped - Install production dependencies in plugin directory
- Create zip file in directory
zipped/
bash
composer build该命令会:
- 清理目录
zipped - 在插件目录中安装生产依赖
- 在目录中创建压缩包
zipped/
Composer Scripts
Composer脚本
- - Run coding standards check
composer phpcs - - Run PHP compatibility checks (7.4-8.3) + WPCS
composer check - - Auto-fix coding standards issues
composer phpcbf - - Run static analysis
composer phpstan - - Run PHPUnit tests
composer test - - Build distribution zip
composer build
- - 运行编码规范检查
composer phpcs - - 运行PHP兼容性检查(7.4-8.3)+ WPCS检查
composer check - - 自动修复编码规范问题
composer phpcbf - - 运行静态分析
composer phpstan - - 运行PHPUnit测试
composer test - - 构建分发压缩包
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