Zsens Admin 插件本地调试:我用"入口文件三件套"把每次改代码都手动传FTP的蠢事戒掉了
刚摸插件开发那会儿,我的 workflow 堪称原始人:改一行代码 → 打包 → 传 FTP → 刷新后台 → 白屏 → 猜错在哪 → 重复二十遍。直到我把本地环境整明白,才发现之前浪费的时间够写三个功能模块了。这篇只聊最基础的骨架:目录怎么摆、入口文件怎么写、本地怎么做到保存即生效。
一、目录结构:别一上来就摊大饼
我见过太多新手把全部文件怼在根目录,结果激活插件时加载顺序全乱。我的做法是按"生命周期"切分,让 WordPress 的加载器一眼能找到该找的:
zsens-admin/ ├── zsens-admin.php # 唯一入口,只做一件事:引导 ├── bootstrap.php # 实际初始化:常量、自动加载、容器 ├── src/ │ ├── Core/ # 插件内核,不依赖具体业务 │ │ ├── Plugin.php # 单例容器,管注册、管生命周期 │ │ └── Loader.php # 钩子批量注册器 │ ├── Admin/ # 后台专属 │ │ ├── Menu/ │ │ ├── Pages/ │ │ └── Assets/ │ └── Frontend/ # 如果有前台部分 ├── assets/ # 构建后的静态资源 ├── languages/ └── .env.local # 本地调试开关,绝不进版本库
关键点:zsens-admin.php 和 bootstrap.php 分离。前者是 WordPress 的"门面",后者才是你的"里子"。这样本地调试时,我可以只换里子不动门面,避免重新激活插件。
二、入口文件:三行代码的讲究
主入口文件我控制在 50 行以内,只做三件事:
<?php
/**
* Plugin Name: Zsens Admin
* ...
*/
// 1. 拒绝直接访问
if (!defined('ABSPATH')) {
exit('Cheatin\' uh?');
}
// 2. 定义基础常量(路径、URL、版本)
define('ZSENS_ADMIN_PATH', plugin_dir_path(__FILE__));
define('ZSENS_ADMIN_URL', plugin_dir_url(__FILE__));
define('ZSENS_ADMIN_VERSION', '1.0.0');
// 3. 引导启动
require_once ZSENS_ADMIN_PATH . 'bootstrap.php';
\Zsens\Admin\Core\Plugin::instance()->run();
注意第三行的 run() 不是立即执行所有逻辑,而是"注册"所有钩子。WordPress 的插件加载是多点触发的,你的 admin_init、wp_enqueue_scripts 都要等到对应时机才点火。很多新手在入口文件直接跑业务代码,结果 is_admin() 还没准备好就判断环境,逻辑全乱。
三、本地调试环境:我用 Docker Compose 搭的"一键沙盘"
我的 docker-compose.yml 核心就这些:
version: '3.8'
services:
wordpress:
image: wordpress:6.4-php8.2-apache
volumes:
- ./zsens-admin:/var/www/html/wp-content/plugins/zsens-admin
- ./debug.log:/var/www/html/wp-content/debug.log
environment:
WORDPRESS_DEBUG: 1
ports:
- "8080:80"
重点是 volumes 的绑定挂载:本地 ./zsens-admin 直接映射到容器里的插件目录。改代码保存瞬间生效,不用传 FTP,不用重启容器。
但这里有个坑:PHP 的 opcache 在 Docker 里默认开启,文件改了但缓存没刷新,你会怀疑人生。我的解法是在 bootstrap.php 里加一段"本地开发保险":
if (defined('ZSENS_LOCAL_DEV') && ZSENS_LOCAL_DEV) {
ini_set('opcache.enable', 0);
ini_set('opcache.revalidate_freq', 0);
}
然后在 .env.local 里定义常量,通过 wp-config.php 的 require 引入。生产环境没有这个文件,自然不走这段逻辑。
四、让错误尖叫:调试器接不上的兜底方案
Xdebug 配不好的时候,我靠三样东西活命:
1. WP_DEBUG_LOG + 实时 tail:tail -f debug.log | grep ZSENS,所有我的日志带前缀过滤出来。
2. 在 bootstrap.php 里注册一个谁都没法躲的异常捕获:
set_exception_handler(function ($e) {
if (defined('ZSENS_LOCAL_DEV')) {
wp_die(sprintf(
'<pre>Zsens Fatal: %s\n%s</pre>',
$e->getMessage(),
$e->getTraceAsString()
));
}
// 生产环境走正常日志通道
});
3. 浏览器插件"Query Monitor"看钩子执行顺序,比 var_dump 堆满屏幕清爽一百倍。
五、一个让我纠结过的细节:入口文件到底要不要做自动加载
Composer 的 autoload 很香,但 WordPress 生态里不是所有环境都有 Composer。我的妥协方案:本地开发用 Composer + PSR-4,打包发布时 composer dump-autoload -o 生成优化后的类映射,连同 vendor/autoload.php 一起进版本。入口文件里这样写:
$autoload = ZSENS_ADMIN_PATH . 'vendor/autoload.php';
if (file_exists($autoload)) {
require_once $autoload;
} else {
// 兜底:手动 require 核心类,保证裸环境能跑
require_once ZSENS_ADMIN_PATH . 'src/Core/Plugin.php';
require_once ZSENS_ADMIN_PATH . 'src/Core/Loader.php';
}
这样 GitHub 下载的 ZIP 包直接装也能用,Composer 用户享受自动加载,两头不耽误。
六、我现在保存代码后的完整流程
1. IDE 里 Ctrl+S
2. 浏览器刷新(偶尔要硬刷新清 JS 缓存)
3. 有问题 → debug.log 秒定位 / 异常捕获直接看栈
4. 没问题 → 继续写下一个功能
从原来的五分钟循环压缩到十秒以内。不是工具多高级,是目录和入口文件把"该在哪加载、该什么时候加载"理清楚了,调试器才能跟上你的思路。
你们本地调试还有什么野路子?我试过 Vagrant、Lando、DDEV,最后留在 Docker Compose 主要是因为配置够薄、够透明。如果有更轻的方案欢迎砸过来。

