本地调试环境搭到一半,我却被入口文件的加载顺序"偷袭"了:一份给插件新手的目录结构避坑笔记

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

上周带一个刚转插件开发的后端同事跑最小可运行环境,他卡在"为什么改完代码刷新没反应"两小时。最后发现是 register_activation_hook 里的建表逻辑只在激活时跑一次,而他一直在改文件后点保存,压根没重新激活插件。这种"环境对了,认知没对上"的坑,我觉得比代码 bug 更值得先聊。

一、我的目录结构:不是越标准越好,是"能一眼找到埋坑点"才好

看过很多教程推荐 includes/admin/public/ 三层分法,实际项目里我常缩成更扁平的结构,减少 require 时的路径心智负担:

my-plugin/
├── my-plugin.php          ← 入口文件,只做"接线员"
├── uninstall.php          ← 独立存在,避免激活钩子误触发
├── assets/
│   ├── css/
│   └── js/
├── src/
│   ├── Core/
│   │   ├── Bootstrap.php  ← 真正的初始化逻辑
│   │   └── Container.php  ← 简易 DI,后期不重构也能活
│   ├── Admin/
│   │   └── Settings.php
│   ├── Frontend/
│   │   └── Shortcode.php
│   └── Database/
│       └── Schema.php     ← dbDelta 封装,和 Bootstrap 解耦
└── vendor/                ← 如果用了 composer,gitignore 掉

关键决策:入口文件 my-plugin.php 里我只放三样东西——插件头注释、常量定义、require_once 引入 Bootstrap.php。曾经我把 add_action('init', ...) 直接写在入口文件,结果本地用 WP-CLI 跑命令时,某些钩子还没注册完就触发了,报错位置在入口第 38 行,实际根因在 includes/functions.php 第 200 行的时序依赖。扁平结构能让这种"跨文件甩锅"更快定位。

二、入口文件的"双面人生":被直接访问 vs 被 WordPress 加载

新手常漏的安全检查,不是 ABSPATH 那行,而是理解它为什么存在:

<?php
/**
 * Plugin Name: My Plugin
 */

if (!defined('ABSPATH')) {
    exit; // 被直接访问时,比如有人猜你路径 http://site/wp-content/plugins/my-plugin/my-plugin.php
}

define('MY_PLUGIN_VERSION', '1.0.0');
define('MY_PLUGIN_PATH', plugin_dir_path(__FILE__));

require_once MY_PLUGIN_PATH . 'src/Core/Bootstrap.php';

// 这里绝不写业务逻辑,Bootstrap 里再决定什么时候干活
MyPlugin\Core\Bootstrap::init();

本地调试时我会故意直接访问入口文件 URL,看是不是返回 403 或白屏——这是验证 ABSPATH 防线是否生效的最快方式。别等上线后被扫描器利用才补。

三、本地环境:我用三种"假 WordPress"来加速反馈循环

完整 LAMP 堆栈太重,我根据场景切三种工具,核心目标是"改文件后 3 秒内看到结果":

1. wp-env(官方 Docker 封装)

适合需要测多版本兼容的场景。.wp-env.json 里指定 "core": "WordPress/WordPress#6.4",一行命令起环境。坑点:默认挂载的是插件目录的软链接,Windows 下偶发文件变更不感知,需要显式配置 "mappings" 用 bind mount。

2. Local / Flywheel(GUI 工具)

给设计师或产品经理演示时用,一键分享公网 URL。但别依赖它的"Live Link"做真机调试——延迟高,且某些 admin-ajax.php 请求会超时。我的做法:本地起完后,用 ngrok 自己透,可控性高。

3. 裸 PHP 内置服务器 + 最小 WordPress 安装

最轻量,适合纯逻辑验证。在 WordPress 根目录:

php -S localhost:8080 -t . router.php

router.php 需要处理 WordPress 的伪静态:

<?php
$uri = urldecode(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
if ($uri !== '/' && file_exists(__DIR__ . $uri)) {
    return false; // 静态文件直接服务
}
require_once __DIR__ . '/index.php';

这种环境下,wp-content/plugins/my-plugin/ 里改完 PHP,刷新即生效,没有 Docker 缓存层。但注意:内置服务器不支持 .htaccess,如果你的插件依赖 mod_rewrite 规则,这里测不了,得切回 Apache/Nginx。

四、调试技巧:让错误"开口说话"的三个开关

本地 wp-config.php 里我必开:

define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false); // 前端不爆错,写日志,保持演示体面
define('SCRIPT_DEBUG', true);      // 加载未压缩的 JS/CSS,调前端交互时省时间

额外加一个自定义的"插件专属日志",避免和 WordPress 核心日志混在一锅粥:

// 在 Bootstrap.php 或通用函数里
function my_plugin_log($message) {
    if (WP_DEBUG) {
        error_log('[MyPlugin] ' . print_r($message, true) . "\n", 3, WP_CONTENT_DIR . '/my-plugin-debug.log');
    }
}

配合 tail -f wp-content/my-plugin-debug.log 实时看输出,比 var_dump 后刷新页面高效十倍。

五、一个我踩过的"本地正常,上传就炸"的坑

本地 Windows 开发,文件名大小写不敏感;Linux 生产环境严格区分。src/Core/Bootstrap.phpuse MyPlugin\core\bootstrap; 本地能跑,上传后 Class not found。现在我的 CI 里加了一步:

find . -name "*.php" -exec php -l {} \;

但这只能检语法,检不了大小写。更狠的招:本地直接用 WSL2 跑 Linux 环境,从根上消灭差异。

你们本地调插件时,有没有遇到过"代码明明对了,环境在撒谎"的情况?我目前卡在 wp-env 的 Xdebug 端口映射偶尔抽风,还没找到稳定复现规律。

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