搜索全站
插件 13 分钟阅读

WordPress 插件目录结构与主文件头部注释详解

写第一个插件前必须搞清楚主文件头部能写哪些字段、各自有什么用。本文逐个讲解 Plugin Name、Version、Text Domain、Requires PHP、Update URI 等字段,附目录结构规范与读取头部的代码。

U
UFO_PF 2020-12-10 · 阅读 13 分钟
Wo 插件
阅读提示
本文约 5,090 字,预计阅读 13 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:插件不被识别,问题往往出在头部

第一次写 WordPress 插件的人,最常见的第一步失败是:把 PHP 文件上传到 wp-content/plugins/,后台插件列表里却什么都没有。检查权限、检查文件名、检查编码,全都正常。

原因几乎总是同一个:主文件缺少规范的头部注释。WordPress 识别插件的方式,是扫描插件目录下的 PHP 文件,读取文件开头的 PHPDoc 注释块,从中提取 Plugin Name: 这一行。没有这一行,这个文件在 WordPress 眼里就不是插件,不管里面的代码写得有多正确。

而且头部注释不只是「让插件被识别」这么简单。它同时是版本管理、翻译加载、兼容性检查、更新机制的数据来源。比如 Text Domain: 写错了,翻译文件永远加载不了;Requires PHP: 没写,用户在 PHP 7.2 上装了你的插件会得到一堆致命错误而不是一句友好提示。

本文逐个讲清每个字段的作用、哪些是必填、哪些容易写错,并给出推荐的目录结构。

原理分析:WordPress 怎么读取插件头部

WordPress 用 get_plugin_data()(定义在 wp-admin/includes/plugin.php)解析插件主文件。它的行为有几个关键细节:

细节一:只读文件开头的一部分。 默认读取前 8KB 内容(早期版本是第一个 8KB),从中提取注释块。所以头部注释必须放在文件最开始,前面不能有别的 PHP 代码。

细节二:只认 PHPDoc 风格。 必须是 /** ... */ 这种块注释,且每行以 字段名: 值 的格式书写。用 // 行注释无效。

细节三:字段顺序无所谓,字段名大小写不敏感。 但惯例是固定的顺序,遵守惯例便于别人阅读。

细节四:只有主文件的头部会被读取。 一个插件可以有几十个 PHP 文件,但只有一个文件的头部会被解析成插件元信息。WordPress 决定「哪个是主文件」的规则是:优先取插件根目录下与主目录同名的 PHP 文件,否则取根目录下的第一个 PHP 文件。

完整字段清单:

字段 必填 作用
Plugin Name 是 后台显示的名称,也决定插件是否被识别
Plugin URI 否 插件主页,后台会显示为链接
Version 强烈建议 当前版本号,影响更新检测与缓存 busting
Description 否 后台列表里的描述
Author / Author URI 否 作者信息
License / License URI 建议 授权声明,WordPress 官方目录要求 GPL 兼容
Text Domain 翻译必需 国际化文本域,必须与插件 slug 一致
Domain Path 否 翻译文件相对路径,默认 /languages
Requires at least 建议 最低 WordPress 版本,低于此版本不允许安装
Requires PHP 建议 最低 PHP 版本
Network 否 设为 true 表示只能在多站点网络层面激活
Update URI 否 WordPress 6.1 起支持,用于避免同名插件误匹配官方目录更新

其中 Update URI 值得单独说明。它的出现是为了解决一个真实问题:如果你开发了一个内部插件,恰好和官方目录里的某个插件同名,WordPress 会拿你的插件去官方目录查更新,可能弹出错误的「有更新可用」。在头部写上 Update URI: https://example.com/my-plugin 或者干脆 Update URI: false,就能切断这个误匹配。

保姆级实操

第一步:建立规范的目录结构

wp-content/plugins/mmf-demo/
├── mmf-demo.php          # 主文件,头部注释在这里
├── readme.txt            # 官方目录用,本地插件可选
├── uninstall.php         # 卸载时执行(可选)
├── includes/
│   ├── class-admin.php
│   └── class-frontend.php
├── assets/
│   ├── css/admin.css
│   └── js/admin.js
├── languages/
│   └── mmf-demo-zh_CN.po
└── templates/
    └── settings-page.php

规则:主文件名与目录名一致;主文件在根目录;includes 放类文件;assets 放静态资源;languages 放翻译。

第二步:写一份完整的主文件头部

<?php
/**
 * Plugin Name:       MMF 演示插件
 * Plugin URI:        https://mimangfei.com/plugins/mmf-demo
 * Description:       演示插件头部注释的标准写法与目录结构。
 * Version:           1.2.0
 * Requires at least: 6.4
 * Requires PHP:      7.4
 * Author:            mimangfei
 * Author URI:        https://mimangfei.com
 * License:           GPL-2.0-or-later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       mmf-demo
 * Domain Path:       /languages
 * Update URI:        https://mimangfei.com/plugins/mmf-demo
 *
 * @package MMF_Demo
 */

defined( 'ABSPATH' ) || exit;

// 插件常量:后续所有路径都基于它们,避免硬编码
define( 'MMF_DEMO_VERSION', '1.2.0' );
define( 'MMF_DEMO_FILE', __FILE__ );
define( 'MMF_DEMO_DIR', plugin_dir_path( __FILE__ ) );
define( 'MMF_DEMO_URL', plugin_dir_url( __FILE__ ) );

// 加载翻译
add_action( 'plugins_loaded', function () {
    load_plugin_textdomain( 'mmf-demo', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
} );

// 引入功能模块
require_once MMF_DEMO_DIR . 'includes/class-admin.php';
require_once MMF_DEMO_DIR . 'includes/class-frontend.php';

第三步:确认头部被正确解析

# 命令行查看 WordPress 实际读到的元信息
wp plugin get mmf-demo --fields=name,version,description,author,status
<?php
// 在代码里读取任意插件的头部(不需要该插件处于激活状态)
require_once ABSPATH . 'wp-admin/includes/plugin.php';
$data = get_plugin_data( WP_PLUGIN_DIR . '/mmf-demo/mmf-demo.php', false, false );
print_r( $data );

get_plugin_data() 的第二个参数 $markup 控制是否对输出做 HTML 格式化,第三个参数 $translate 控制是否翻译。读取来做判断时两个都应传 false。

第四步:验证 Text Domain 是否真的生效

# 检查翻译文件命名是否与 Text Domain 匹配
ls -1 wp-content/plugins/mmf-demo/languages/
# 正确命名:mmf-demo-zh_CN.mo / mmf-demo-zh_CN.po

# 用 WP-CLI 检查代码里有没有漏写文本域
wp i18n make-pot wp-content/plugins/mmf-demo wp-content/plugins/mmf-demo/languages/mmf-demo.pot --slug=mmf-demo

命名格式必须是 {text-domain}-{locale}.mo,写错一个字符翻译就不会加载,而且没有任何报错。

第五步:声明兼容性并用工具验证

# 检查插件在当前 PHP 版本下有没有语法错误
php -l wp-content/plugins/mmf-demo/mmf-demo.php

# 用 WordPress 编码规范检查代码风格
phpcs --standard=WordPress wp-content/plugins/mmf-demo/

第六步:版本号更新的规范

改代码后必须同步改 Version 字段和对应的常量,否则浏览器会继续用旧版本的静态资源(如果你的资源 URL 里带了版本号):

wp_enqueue_style(
    'mmf-demo-admin',
    MMF_DEMO_URL . 'assets/css/admin.css',
    array(),
    MMF_DEMO_VERSION // 版本变化时资源 URL 变化,强制刷新缓存
);

常见踩坑

  1. 头部前面有空行或 BOM:文件开头必须是 <?php,前面有 UTF-8 BOM 或空行都会导致头部解析失败。用编辑器确认保存为「UTF-8 无 BOM」。

  2. 用 // 写头部:必须是 /** */ 块注释。

  3. Text Domain 与插件 slug 不一致:翻译文件会静默加载失败,且没有任何错误提示。

  4. Version 改了但常量没同步:静态资源缓存不刷新,表现为「改了 CSS 但前台没变」。

  5. 主文件放在子目录:WordPress 只在插件根目录找主文件,放 includes/ 里不会被识别。

  6. 案例: 某开发者写了一个内部插件,目录名 report,主文件名 index.php,头部齐全但没有 Update URI。上线后后台持续提示「有更新可用」,点更新后插件被替换成了官方目录里同名的另一个插件,功能全部丢失。加上 Update URI: false 后提示消失。教训是:内部插件一定要写 Update URI,否则可能被官方目录的同名插件劫持更新通道。

  7. 头部写了 Network: true 却在单站点使用:该插件会只能在网络层激活,单站点后台看不到激活按钮。

总结

必记四条:Plugin Name 是唯一决定插件能否被识别的字段;Version 必须与实际代码同步且用于静态资源版本;Text Domain 必须等于插件 slug,翻译文件名格式为 {domain}-{locale}.mo;内部插件加 Update URI 防止更新劫持。

推荐做法:主文件名与目录名一致、用 plugin_dir_path() / plugin_dir_url() 定义路径常量、每个文件开头加 defined( 'ABSPATH' ) || exit;。

写完头部之后,下一步就是让它真正跑起来——从零写一个 WordPress 插件 会带你走完从建文件到后台启用的全过程。而如果插件不再需要,卸载时的清理规范见 uninstall.php 清理数据,那是很多开发者会漏掉的一环。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。