外掛開發
外掛放在 app/Plugin/<外掛名>/ 下,目錄名就是外掛的標識,用大駝峰。
目錄結構
app/Plugin/Demo/
Config/
Info.php 外掛資訊(必需)
Config.php 預設配置值
Submit.js 後臺配置表單(也可用舊格式 Submit.php)
Hook/
Main.php 鉤子訂閱
Lifecycle.php 生命週期(可選,也可以並進 Main.php)
Controller/
Api.php 外掛自己的路由
View/
index.html 模板只有 Config/Info.php 是必需的,其餘按需要加。
Config/Info.php
<?php
declare(strict_types=1);
use App\Consts\Plugin;
return [
Plugin::NAME => '演示外掛',
Plugin::AUTHOR => '你的名字',
Plugin::WEB_SITE => 'https://example.com',
Plugin::DESCRIPTION => '一句話說明這個外掛幹什麼',
Plugin::VERSION => '1.0.0'
];Config/Config.php
外掛的預設配置值。STATUS 是約定的啟用狀態欄位:
<?php
declare(strict_types=1);
return [
'STATUS' => '0',
'api_key' => '',
'enable_notify' => '1',
];Config/Submit.js — 配置表單
後臺「配置」彈窗里長什麼樣,由這個檔案決定。
有兩種格式,Submit.js 優先。 兩個檔案同時存在時,Submit.js 會覆蓋 Submit.php。
| 型別 | 新格式(推薦) | 舊格式 |
|---|---|---|
| 通用外掛 | Config/Submit.js | Config/Submit.php |
| 支付外掛 | Config/Submit.js | Config/Submit.php |
| 主題 | Submit.js(主題根目錄,不在 Config/ 下) | Config 介面的 SUBMIT 常量 |
主題的路徑和外掛不一樣,別放進
Config/—— 放錯了不會報錯,只是永遠不生效。
寫法
檔案內容會被前端 eval,所以整份檔案必須是一個表示式。裸陣列最簡單:
[
{
name: `${util.icon("fa-duotone fa-regular fa-gear")} 基本配置`,
form: [
{
title: "商戶號",
name: "mch_id",
type: "input",
placeholder: "支付平臺分配的商戶號",
required: true
},
{
title: "開啟通知",
name: "enable_notify",
type: "switch",
text: "啟用"
}
]
},
{
name: "高階",
form: [
{ title: "超時時間", name: "timeout", type: "number", placeholder: "秒" }
]
}
]需要寫輔助函式就包一層 IIFE:
(() => {
const T = (s) => (typeof i18n === "function" ? i18n(s) : s);
const cfg = (k, d = "") => (assign && assign[k] != null && assign[k] !== "" ? assign[k] : d);
return [
{
name: T("基本配置"),
form: [
{ title: T("介面金鑰"), name: "api_key", type: "input", default: cfg("api_key") }
]
}
];
})()結構:分組 + 欄位
和 Submit.php 的扁平欄位陣列不同,Submit.js 是分組的 —— 每個分組在彈窗裡是一個頁籤,name 是頁籤標題(可以用 util.icon() 加圖示),form 才是欄位列表。
欄位型別
| type | 控制元件 |
|---|---|
input | 單行輸入框 |
password | 密碼框 |
number | 數字框 |
textarea | 多行文字域 |
select | 下拉框 |
radio | 單選 |
checkbox | 多選 |
switch | 開關 |
image | 圖片上傳 |
explain | 說明文字,不儲存 |
html | 直接插入一段 HTML |
custom | 自己渲染,見下 |
作用域裡能拿到什麼
| 變數 | 用途 |
|---|---|
assign | 當前已儲存的配置,用 assign.api_key 取值 |
util | 工具函式,最常用的是 util.icon() |
i18n(s) | 翻譯函式,做多語言外掛時用 |
layui | layui 例項 |
Submit.js不會自動填預設值。 走Submit.php時,核心會把Config.php裡的值注入到每個欄位的default;但走.js時核心只是把檔案當字串讀出去交給前端,這一步不執行。當前值要自己從assign裡取 —— 這就是各個外掛都寫一個cfg()小函式的原因。
custom:自己渲染
欄位型別給不了的東西,用 custom 自己畫:
{
title: false,
name: "gateway",
type: "custom",
complete: (form, dom) => {
dom.html(`<a href="/plugin/Demo/panel" class="btn btn-sm btn-primary">開啟面板</a>`);
}
}
complete()會被反覆呼叫(切頁籤、重建表單都會觸發)。裡面起定時器或輪詢的話,記得讓上一輪自動作廢,否則彈窗關掉後還在後臺空轉。
還能用 Submit.php 嗎
能,核心兩種都認,目前倉庫裡 .php 和 .js 都有不少外掛在用。
<?php
declare(strict_types=1);
return [
["title" => "介面金鑰", "name" => "api_key", "type" => "input", "placeholder" => "請輸入你的 API Key"],
["title" => "說明", "name" => "explain", "type" => "explain", "placeholder" => "這段文字只用來提示,不會儲存"],
["title" => "開啟通知", "name" => "enable_notify", "type" => "switch", "text" => "啟用"],
];Submit.php 是扁平的欄位陣列,沒有分組,另外支援 file、editor、json 三個 .js 裡沒有的型別。純欄位的簡單表單用它完全夠;需要分組頁籤、條件顯隱、自定義渲染時再上 .js。
別在配置裡傳 JSON 字串。
$_POST全域性被清洗過,用表單陣列的形式提交。
訂閱鉤子
<?php
declare(strict_types=1);
namespace App\Plugin\Demo\Hook;
use App\Controller\Base\View\UserPlugin;
use Kernel\Annotation\Hook;
class Main extends UserPlugin
{
#[Hook(point: \App\Consts\Hook::USER_VIEW_FOOTER)]
public function footer(): void
{
echo '<script>console.log("hello from Demo")</script>';
}
}基類按場景選:
| 基類 | 用在 |
|---|---|
App\Controller\Base\View\UserPlugin | 前臺 |
App\Controller\Base\View\ManagePlugin | 後臺 |
全部可用點位見 Hook 鉤子大全。
訂閱方請寫十六進位制字面量(
#[Hook(point: 0x2300)])而不是引用常量。註解引數在外掛啟用時求值,老版本核心上沒有這個常量會拋 Error,把外掛卡在「已執行 START、未寫入 STATUS」的半啟用狀態。
生命週期
<?php
declare(strict_types=1);
namespace App\Plugin\Demo\Hook;
use Kernel\Annotation\Plugin;
class Lifecycle
{
#[Plugin(state: Plugin::INSTALL)]
public function install(): void
{
// 安裝:建表
}
#[Plugin(state: Plugin::START)]
public function start(): void
{
// 每次啟用
}
#[Plugin(state: Plugin::STOP)]
public function stop(): void
{
// 停用
}
#[Plugin(state: Plugin::UNINSTALL)]
public function uninstall(): void
{
// 解除安裝:清資料
}
#[Plugin(state: Plugin::UPGRADE)]
public function upgrade(): void
{
// 升級
}
#[Plugin(state: Plugin::SAVE_CONFIG)]
public function saveConfig(): void
{
// 後臺儲存配置之後
}
}
state:這個命名引數不能省。 寫成位置引數#[Plugin(Plugin::INSTALL)]會靜默不觸發 —— 外掛顯示「啟用成功」,但建表根本沒跑,之後所有功能都在報表不存在。核心是按$arguments['state']取值的。
外掛自己的頁面
Controller/Api.php 裡的方法會對映成路由:
/plugin/<外掛名>/<控制器>/<方法>比如 app/Plugin/Demo/Controller/Api.php 裡的 login() 方法,訪問地址就是:
/plugin/Demo/api/login渲染模板:
echo $this->render("標題", "index.html", ['key' => 'value']);前臺注入 UI 的注意事項
外掛往前臺注入的 HTML/CSS,會和主題的樣式打架。兩條經驗:
- CSS 一律寫成
#你的根ID .你的類的形式提高特異性,別寫裸類名 - reset 用
:where()包起來,避免誤傷主題自己的元素
另外:scrollIntoView 會把 overflow:hidden 的祖先一起滾動,前臺彈窗裡慎用。
打包與釋出
用開發者中心提交外掛時,服務端會整目錄自動打包,只會剔掉 Config/Config.php。
所以提交前必須先把大檔案挪走 —— 二進位制、runtime.log、快取這些,否則包會大得離譜。
除錯
- 報錯看網站根目錄的
runtime.log - 改了 Hook 檔案後,停用再啟用外掛才會重建鉤子登錄檔
- 改了模板,刪掉
runtime/view/compile下的內容
