Twig でプラグイン(プラグイン(拡張アプリ))の管理画面を実装する


プラグイン(拡張アプリ)の管理画面を Twig テンプレートで組み立てられます。本ドキュメントでは、仕組みの全体像から、最小構成の画面を表示する手順、設定値の保存・バリデーションを伴う設定画面の作り方までを通して解説します。

これまで管理画面への差し込みは 標準テンプレートエンジンが中心でしたが、Twig で部分的に描画して差し込めるようになりました。テンプレートの継承(extends)やマクロといった Twig の表現力を、管理画面の拡張でもそのまま使えます。


対応バージョン: Ver. 3.2.4 以上で利用できます。


2 つの Twig 経路を区別する

a-blog cms には Twig を使う経路が 2 つあり、本ドキュメントで扱うのは後者です。両者は独立していて、片方を使うのにもう片方の設定は要りません。

経路

有効化の方法

主な用途

公開側テーマ全体の Twig 化

コンフィグ tpl_twig を「有効」にする

公開ページのテンプレートを .twig で書く

プラグイン(拡張アプリ)からの部分描画

フック・InjectTemplate から呼ぶ

管理画面に独自の UI / 設定画面を差し込む

tpl_twig を有効にしていなくても、プラグイン側の経路は使えます。

全体像

プラグインの ServiceProvider::init() で各種登録を行い、管理画面のレンダリング時に差し込みポイントへ HTML を流し込みます。

ServiceProvider::init()
  ├─ Twig::addTemplatePath()         … 自前テンプレートディレクトリを名前空間付きで登録
  ├─ InjectTemplate::add($type, …)   … 差し込みポイントに「文字列パス」または「callable」を登録
  └─ (Filter / Function / Extension は extendsTwig フックで登録)

       │  管理画面リクエスト
       ▼
Admin_InjectTemplate モジュール(差し込みポイント)
  └─ 登録された callable を実行
        └─ Twig::renderTemplate('@namespace/...') の結果 HTML を差し込む

差し込みポイントは、システムテーマ側のテンプレートに置かれた Admin_InjectTemplate モジュールです。プラグインはそこに対して「この $type のときはこの HTML を出す」と登録します。

実装は次の API を組み合わせます。詳細は各リファレンスを参照してください。

同梱の SamplePluginextension/plugins/SamplePlugin/)が、本機能の動作するサンプルです。合わせて参照してください。

チュートリアル: 最小構成の管理画面

まずは、プラグインの管理メニューを開いたときに Twig で書いた画面を表示するところまでを作ります。

1. テンプレートを用意する

プラグイン内にテンプレートディレクトリを作り、Twig ファイルを置きます。

extension/plugins/MyPlugin/
├─ ServiceProvider.php
└─ template/
   └─ admin/
      └─ main.twig

差し込みポイントには、管理画面シェル(ヘッダー・メニューなど)の内側に HTML が挿入されます。そのため main.twig にはページ全体ではなく、表示したい中身の断片だけを書きます。

<header>
  <h1 class="acms-admin-admin-title">MyPlugin 設定</h1>
</header>

<p>はじめての Twig 管理画面です。</p>

2. テンプレートディレクトリを登録する

ServiceProvider::init() で、テンプレートディレクトリを名前空間付きで登録します。Twig::addTemplatePath() に渡すのはファイルシステムのパスなので PLUGIN_LIB_DIR を使います。

use Acms\Services\Facades\Twig;

Twig::addTemplatePath(PLUGIN_LIB_DIR . 'MyPlugin/template', 'myplugin');

これで、テンプレートを @myplugin/admin/main.twig のように名前空間付きで参照できます。

3. 差し込みポイントに登録する

管理画面のメイン領域には admin-main という差し込みポイントがあります。ここに「HTML を返す callable」を登録すると、その戻り値が画面に差し込まれます。callable の中で Twig::renderTemplate() を呼んで、Twig の描画結果を返します。

use Acms\Services\Common\InjectTemplate;
use Acms\Services\Facades\Twig;

$inject = InjectTemplate::singleton();

if (defined('ADMIN') && ADMIN === 'app_' . $this->menu) {
    $inject->add('admin-main', static function (): string {
        return Twig::renderTemplate('@myplugin/admin/main.twig');
    });
}

ADMIN === 'app_' . $this->menu で「自分のプラグイン画面を開いているときだけ」差し込むようにしています。$this->menuServiceProvider$menu プロパティで指定したメニュー識別子です。

4. 完成形(ServiceProvider::init)

ここまでをまとめると、init() は次のようになります。

use Acms\Services\Common\InjectTemplate;
use Acms\Services\Facades\Twig;

public function init()
{
    // 1. テンプレートディレクトリの登録(init で一度だけでよい)
    Twig::addTemplatePath(PLUGIN_LIB_DIR . 'MyPlugin/template', 'myplugin');

    // 2. 自分の管理メニュー画面のときだけ、メイン領域に差し込む
    if (defined('ADMIN') && ADMIN === 'app_' . $this->menu) {
        $inject = InjectTemplate::singleton();
        $inject->add('admin-main', static function (): string {
            return Twig::renderTemplate('@myplugin/admin/main.twig');
        });
    }
}

5. 動作確認

プラグインを有効化し、管理画面のプラグインメニューから対象の画面を開きます。main.twig の内容が表示されれば成功です。

設定画面を Twig で書く

acms_config() と a-blog cms 標準のフォームを組み合わせると、コンフィグの保存・バリデーションを伴う設定画面を Twig だけで書くことができます。

値を参照するだけの基本 API は 組み込み Twig 関数 acms_config() を参照してください。ここでは、フォーム送信・保存・バリデーションの「編集レイヤー」を扱います。

保存の仕組み

設定画面のフォームは、標準の ACMS_POST_Config に送信します。

  • 送信ボタンの nameACMS_POST_Config にする

  • <input name="config[]" value="保存するキー"> で保存対象のキーを宣言する

  • バリデーションは hidden input で宣言する(後述)

ACMS_POST_Config が POST 値を保存し、バリデーション結果を保持したまま画面へ戻します。Twig 側は acms_config() から保存通知やエラーを取得して表示します。

<form action="" method="post" class="acms-admin-form">
  <button type="submit" name="ACMS_POST_Config" class="acms-admin-btn-admin acms-admin-btn-admin-primary">
    保存
  </button>

  <input type="text" name="site_title" value="{{ config.get('site_title') }}">
  <input type="hidden" name="config[]" value="site_title">
</form>

config[] で宣言したキーだけが保存対象になります。

バリデーションの宣言

バリデーションは a-blog cms 標準フォームと同じ作法で、hidden input として宣言します。ACMS_POST_Config がこれを解釈し、自動的に検証します。

<input type="hidden" name="キー:validator#メソッド" value="引数">
  • value 属性がバリデータへの引数になります(例: maxlength の文字数)。

  • :validator# は短縮形の :v# でも書けます。

<!-- 必須・最大 50 文字 -->
<input type="hidden" name="site_title:validator#required" value="true">
<input type="hidden" name="site_title:validator#maxlength" value="50">

使える主なバリデータ

メソッド

引数

内容

required

必須

email

メールアドレス形式

maxlength

文字数

最大文字数

digits

数字のみ

regex

正規表現

パターンに一致

in

候補(カンマ区切り等)

許可値のいずれか

min / max

数値

最小値 / 最大値

equalTo

比較対象

値が一致

詳しくは フォームオプション をご覧ください。

結果を取得するメソッド

acms_config() のアクセサから、保存通知とバリデーション結果を取得できます。

メソッド

戻り値

説明

saved()

bool

設定が保存されたかどうか

notice()

string

保存通知の文字列(saved / reset など)

posted()

bool

直近のリクエストがコンフィグ保存のPOSTりくえすとだったかどうか

valid()

bool

フォーム全体がバリデーションを通過したか

errors()

array

失敗したフィールドの一覧(フィールド名 → 失敗メソッド名のリスト

invalid(key, method = null, index = null)

bool

指定フィールド(とメソッド)が失敗したか

引数のないメソッドはプロパティ記法でも書けます(config.saved / config.valid / config.errors)。

保存通知とエラーサマリー

{% if config.saved %}
  <div role="alert" class="acms-admin-alert acms-admin-alert-info">保存しました</div>
{% endif %}

{% if config.posted and not config.valid %}
  <div role="alert" class="acms-admin-alert acms-admin-alert-danger">
    入力内容に誤りがあります
    {% if config.errors %}
      <ul>
        {% for field, methods in config.errors %}
          <li>{{ field }}: {{ methods|join(', ') }}</li>
        {% endfor %}
      </ul>
    {% endif %}
  </div>
{% endif %}

フィールドごとのエラー表示

invalid() で、特定フィールドの特定バリデータが失敗したかを判定できます。標準フォームと同じ data-validator-label のパターンに合わせると、既存の管理画面の見た目に揃います。

<input type="text" name="site_title" value="{{ config.get('site_title') }}">
<input type="hidden" name="config[]" value="site_title">
<input type="hidden" name="site_title:validator#required" value="true" id="validator-site_title-required">

<div role="alert" aria-live="assertive">
  <div data-validator-label="validator-site_title-required"
       class="validator-result-{{ config.invalid('site_title', 'required') ? '0' : '1' }}">
    <p class="error-text">サイトタイトルを入力してください</p>
  </div>
</div>

バリデーションエラーで再描画されたときも、acms_config() は POST 値を重ね合わせるため config.get() がユーザーの入力値を返します。入力をやり直させずに済みます。

完成例

テキスト(必須・最大文字数)、メール(必須・形式)、セレクト、チェックボックスを含む設定画面の例です。同梱の SamplePluginextension/plugins/SamplePlugin/template/admin/main.twig)が動作するサンプルです。

{% set config = acms_config(blogId: BID) %}

{% if config.saved %}
  <div role="alert" class="acms-admin-alert acms-admin-alert-info">設定を保存しました</div>
{% endif %}

{% if config.posted and not config.valid %}
  <div role="alert" class="acms-admin-alert acms-admin-alert-danger">入力内容に誤りがあります</div>
{% endif %}

<form action="" method="post" class="acms-admin-form">
  <button type="submit" name="ACMS_POST_Config" class="acms-admin-btn-admin acms-admin-btn-admin-primary">
    保存
  </button>

  {# テキスト:必須・最大 50 文字 #}
  <label for="sample_config_text">サンプルテキスト</label>
  <input id="sample_config_text" type="text" name="sample_config_text" value="{{ config.get('sample_config_text') }}">
  <input type="hidden" name="config[]" value="sample_config_text">
  <input type="hidden" name="sample_config_text:validator#required" value="true" id="validator-sample_config_text-required">
  <input type="hidden" name="sample_config_text:validator#maxlength" value="50" id="validator-sample_config_text-maxlength">
  <div role="alert" aria-live="assertive">
    <div data-validator-label="validator-sample_config_text-required"
         class="validator-result-{{ config.invalid('sample_config_text', 'required') ? '0' : '1' }}">
      <p class="error-text">サンプルテキストを入力してください</p>
    </div>
    <div data-validator-label="validator-sample_config_text-maxlength"
         class="validator-result-{{ config.invalid('sample_config_text', 'maxlength') ? '0' : '1' }}">
      <p class="error-text">50 文字以内で入力してください</p>
    </div>
  </div>

  {# メール:必須・メール形式 #}
  <label for="sample_config_email">メールアドレス</label>
  <input id="sample_config_email" type="text" name="sample_config_email" value="{{ config.get('sample_config_email') }}">
  <input type="hidden" name="config[]" value="sample_config_email">
  <input type="hidden" name="sample_config_email:validator#required" value="true" id="validator-sample_config_email-required">
  <input type="hidden" name="sample_config_email:validator#email" value="true" id="validator-sample_config_email-email">

  {# セレクト #}
  <label for="sample_config_status">公開ステータス</label>
  <select id="sample_config_status" name="sample_config_status">
    <option value="open" {{ config.selected('sample_config_status', 'open') }}>公開</option>
    <option value="close" {{ config.selected('sample_config_status', 'close') }}>非公開</option>
  </select>
  <input type="hidden" name="config[]" value="sample_config_status">

  {# チェックボックス:未チェック時に off を送るため hidden を前置 #}
  <label>メールマガジン</label>
  <input type="hidden" name="sample_config_mail" value="off">
  <input type="checkbox" name="sample_config_mail" value="on" {{ config.checked('sample_config_mail', 'on') }} id="sample_config_mail">
  <input type="hidden" name="config[]" value="sample_config_mail">
</form>

つまずきやすいポイント

  • テンプレートが見つからない: @myplugin/... の名前空間が addTemplatePath() の第 2 引数と一致しているか、ファイルシステムのパス(PLUGIN_LIB_DIR)を渡しているかを確認してください。

  • 画面に何も出ない: ADMIN の比較条件を確認してください。$menu を設定していない、または条件が現在の画面と一致していない可能性があります。

  • ページ全体を書いてしまう: 差し込みポイントには管理画面テンプレートの内側に断片が挿入されます。<html> や共通メニューは書かず、中身だけを出力してください。

  • @system を自分のテンプレートに使ってしまう: @system はシステムテーマ参照用の予約名前空間です。自前のテンプレートには別の名前空間(例では myplugin)を使ってください。

  • 保存されない: 保存したいキーを <input name="config[]" value="キー"> で宣言しているか確認してください。宣言のないキーは保存されません。

  • チェックボックスの OFF が保存されない: チェックボックスは未チェック時に値が送信されません。OFF を保存したい場合は、同名の hidden(value="off")をチェックボックスの前に置きます。