プラグイン開発
プラグインは 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 => '何をするプラグインかを 1 行で',
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 — 設定フォーム
管理画面の設定ダイアログの見た目は、このファイルが決めます。
形式が 2 つあり、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: "加盟店 ID",
name: "mch_id",
type: "input",
placeholder: "決済事業者から発行された加盟店 ID",
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("API キー"), name: "api_key", type: "input", default: cfg("api_key") }
]
}
];
})()構造:グループとフィールド
Submit.php のフラットな配列と違い、Submit.js はグループ構造です。グループがダイアログのタブになり、name がタブのタイトル(util.icon() が使えます)、form がフィールドの配列です。
フィールドの種類
| type | コントロール |
|---|---|
input | 1 行テキスト |
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 を使っているプラグインも多数あります。
<?php
declare(strict_types=1);
return [
["title" => "API キー", "name" => "api_key", "type" => "input", "placeholder" => "API キーを入力してください"],
["title" => "説明", "name" => "explain", "type" => "explain", "placeholder" => "この文章は案内用で、保存されません"],
["title" => "通知を有効にする", "name" => "enable_notify", "type" => "switch", "text" => "有効"],
];Submit.php はグループのないフラットなフィールド配列で、.js にはない file、editor、json が使えます。単純なフォームならこれで十分です。タブ、条件付き表示、独自描画が必要になったら .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 | 管理画面 |
利用できるポイントの一覧はフック一覧にあります。
購読側は 16 進リテラル(
#[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 は、テーマ側のスタイルと衝突します。経験則が 2 つあります。
- CSS は必ず
#自分のルートID .自分のクラスの形で書き、詳細度を上げること。裸のクラス名は使わない - リセットは
:where()で包み、テーマ側の要素を巻き込まないようにする
もう 1 つ:scrollIntoView は overflow:hidden の祖先要素まで一緒にスクロールさせます。ストアフロントのモーダル内では注意してください。
パッケージングと公開
開発者センターからプラグインを提出すると、サーバー側でディレクトリ全体が自動的にパッケージ化されます。除外されるのは Config/Config.php だけです。
そのため提出前に大きなファイルを退避してください。バイナリ、runtime.log、キャッシュなどです。そうしないとパッケージが極端に大きくなります。
デバッグ
- エラーはサイトルートの
runtime.logに出ます - フックファイルを変更したら、プラグインを停止して再度有効化し、フック登録表を再構築します
- テンプレートを変更したら、
runtime/view/compileの中身を削除します
