从零开始搭一个能本地热重载的 Zsens Admin 插件骨架:我的目录不是拍脑袋定的,是调试器逼出来的

小助手
小助手 版主圣羽星庭 勋望元宿志愿先锋
社区管理
插件开发 56 浏览 0 回复

第一次写 Zsens Admin 插件时,我把所有文件往一个文件夹里一塞,require 写得到处都是,本地改一行代码就得手动刷新后台看效果,效率低得想弃坑。后来把目录拆开、入口理顺、配上本地调试链路,才算真正进入开发状态。这篇记录我现在的起手式,给同样刚入门的兄弟参考。

一、目录结构:按"加载顺序"而不是"功能模块"来分层

早期我按 controller/model/view/ 这种 MVC 思维分,结果入口文件里一堆 require 路径错到怀疑人生。现在我的目录长这样:

zsens-admin-demo/
├── zsens-admin-demo.php          # 唯一入口,只做一件事:bootstrap
├── bootstrap/
│   └── Kernel.php                # 注册自动加载、初始化容器、挂载钩子
├── config/
│   └── plugin.php                # 版本号、依赖检查、最小 WP 版本
├── src/
│   ├── Contracts/                # 接口定义,先写这里再写实现
│   ├── Services/                 # 业务逻辑,不直接碰 WP 函数
│   ├── Http/
│   │   ├── Controllers/          # 只负责收参数、调 Service、返回响应
│   │   └── Middleware/           # 权限校验、CSRF 过滤
│   └── Admin/
│       ├── Menus/                # 菜单注册与路由绑定
│       └── Assets/               # css/js 的 enqueue 逻辑
├── resources/
│   ├── views/                    # 模板文件,用原生 PHP 不用引擎
│   └── assets/                   # 源码 scss/ts,构建后不进版本控制
├── routes/
│   └── admin.php                 # 后台路由表,纯数组配置
└── tests/
    └── local/                    # 本地调试专用脚本,见下文

关键变化:bootstrap/Kernel.php 是实际入口,zsens-admin-demo.php 只负责 require __DIR__ . '/bootstrap/Kernel.php'; 然后 (new Kernel)->boot();。这样 WP 激活插件时只做最小启动,避免解析错误直接白屏。

二、入口文件的三条铁律

我的 zsens-admin-demo.php 现在固定这么写,多了没有:

<?php
/**
 * Plugin Name: Zsens Admin Demo
 * Version: 0.1.0
 * Requires at least: 6.0
 */

if (!defined('ABSPATH')) {
    exit; // 直接访问断掉,这条不能省
}

// 环境探测:本地开发时走热重载分支
$isLocal = (defined('WP_DEBUG') && WP_DEBUG && $_SERVER['HTTP_HOST'] ?? '' === 'zsens.test');

require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/bootstrap/Kernel.php';

$kernel = new \ZsensAdminDemo\Bootstrap\Kernel($isLocal);
$kernel->boot();

注意 $isLocal 这个开关,后面调试全靠它。

三、本地调试环境:不是装个 Xdebug 就完事

我踩过的坑:Xdebug 配好了,但插件代码改完还得手动刷新后台,断点打在入口文件发现 WP 已经跑完了,自己的逻辑还没加载。

现在的解法分三层:

1. 文件监听 + 软链接热替换

tests/local/ 下放一个 watcher.php,用 inotifywait(Linux/Mac)或 fswatch 监听 src/ 变动,触发时:

// tests/local/watcher.php
$pluginDir = '/var/www/zsens.test/wp-content/plugins/zsens-admin-demo';
$sourceDir = '/home/你的项目路径/zsens-admin-demo';

// 只同步 src/ 和 resources/views/,vendor 不动
$syncCmd = "rsync -av --delete {$sourceDir}/src/ {$pluginDir}/src/";
exec($syncCmd);

配合 browser-sync 代理本地站点,保存文件后 200ms 内后台自动刷新,断点能命中最新代码。

2. 容器替换:本地用 Mock 实现

Kernel 里根据 $isLocal 换绑定:

// bootstrap/Kernel.php
public function registerBindings(): void
{
    if ($this->isLocal) {
        // 本地不走真实 WP 数据库,用内存数组测流程
        $this->container->bind(
            \ZsensAdminDemo\Contracts\ConfigRepository::class,
            \ZsensAdminDemo\Tests\Local\InMemoryConfig::class
        );
        return;
    }

    $this->container->bind(
        \ZsensAdminDemo\Contracts\ConfigRepository::class,
        \ZsensAdminDemo\Services\WpOptionConfig::class
    );
}

这样本地调试配置保存逻辑时,不用真的写 wp_options,速度差一个数量级。

3. 路由直连:绕过 WP 菜单体系

后台菜单注册后调试麻烦,本地我加了一条"后门"路由:

// routes/admin.php
return [
    // 正常菜单路由
    ['page' => 'zsens-demo', 'controller' => 'DashboardController@index'],

    // 本地专用:直接访问 ?page=zsens-dev&route=dashboard/edit/3
    ['page' => 'zsens-dev', 'controller' => 'DevController@dispatch', 'local_only' => true],
];

DevController 里解析 $_GET['route'],直接调目标 Controller,跳过 admin_menu 钩子的权限检查,专注测业务逻辑。

四、一个能跑的最小验证

按上面搭完后,我的第一个可调试入口是这样一个 Controller:

// src/Http/Controllers/DashboardController.php
namespace ZsensAdminDemo\Http\Controllers;

class DashboardController
{
    public function index(): void
    {
        $version = apply_filters('zsens_demo_version', '0.1.0');

        // 本地调试用:直接抛异常看堆栈
        if (defined('ZSENS_LOCAL_THROW') && ZSENS_LOCAL_THROW) {
            throw new \RuntimeException("调试点命中:版本号 {$version}");
        }

        require ZSENS_DEMO_PATH . '/resources/views/dashboard.php';
    }
}

本地 wp-config.php 里加 define('ZSENS_LOCAL_THROW', true);,访问后台页面直接看到异常堆栈,确认自动加载、容器绑定、视图路径全通。

五、现在还在纠结的点

资源文件(JS/CSS)的热重载还没找到完美方案。目前是用 wp_enqueue_script 时判断 $isLocal,指向 browser-sync 的代理端口,但偶尔有缓存穿透。兄弟们本地怎么处理的?

评论0
回复 · 0
还没有回复
微信客服 微信客服