プラグイン(拡張アプリ)

目次

名前空間

プラグイン(拡張アプリ)での拡張方法を見て行く前に、名前空間について説明します。

名前空間

ファイルのパスと命名規則はPSR4に従います。(ただし下位互換性を保つため一部したがっていない場合があります)

extension/plugins の名前空間は Acms\Plugins になります。

パス extension/plugins/SamplePlugin/ServiceProvider.php

クラス名 Acms\Plugins\SamplePlugin\ServiceProvider
記述例 <?php

namespace Acms\Plugins\SamplePlugin;

class ServiceProvider extends \ACMS_App
{
}

プラグイン(プラグイン(拡張アプリ))開発の基本

プラグイン(拡張アプリ)として拡張機能を作ることにより、様々な拡張を1パッケージにして、ブログ単位でインストール出来るようになります。

拡張できる機能

  • 校正オプションの拡張
  • フック処理の拡張
  • Validatorの拡張
  • 独自GETモジュールの追加
  • 独自POSTモジュールの追加
  • テンプレートの挿入(テンプレートの拡張)

最低限必要なファイル

プラグイン(拡張アプリ)を作るためには、以下のファイルが最低限必要になります。

  • extension/プラグイン(拡張アプリ)のディレクトリ/ServiceProvider.php

このファイルがあれば、最小限のプラグイン(拡張アプリ)として成立し、管理画面からインストールもできるようになります。このファイルは、アプリのバージョンや説明やインストール・アップデート時の処理や起動の初期処理を記述できるファイルになっています。詳しく見ていきましょう。

ServiceProvider.php

下のコードは、 ServiceProvider.php の 基本的なコードになります。

<?php

namespace Acms\Plugins\SamplePlugin;

use ACMS_App;

class ServiceProvider extends ACMS_App
{
    /**
     * @var string
     */
    public $version = '1.0.0';

    /**
     * @var string
     */
    public $name = 'SamplePlugin';

    /**
     * @var string
     */
    public $author = 'com.appleple';

    /**
     * @var bool
     */
    public $module = false;

    /**
     * @var bool|string
     */
    public $menu = false;

    /**
     * @var string
     */
    public $desc = 'サンプルのプラグインです。';

    /**
     * サービスの初期処理
     */
    public function init()
    {

    }

    /**
     * インストールする前の環境チェック処理
     *
     * @return bool
     */
    public function checkRequirements()
    {
        return true;
    }

    /**
     * インストールするときの処理
     * データベーステーブルの初期化など
     *
     * @return void
     */
    public function install()
    {

    }

    /**
     * アンインストールするときの処理
     * データベーステーブルの始末など
     *
     * @return void
     */
    public function uninstall()
    {

    }

    /**
     * アップデートするときの処理
     *
     * @return bool
     */
    public function update()
    {
        return true;
    }

    /**
     * 有効化するときの処理
     *
     * @return bool
     */
    public function activate()
    {
        return true;
    }

    /**
     * 無効化するときの処理
     *
     * @return bool
     */
    public function deactivate()
    {
        return true;
    }
}

プロパティ


プロパティ名 説明
$version バージョンを指定します。バージョン表記に決まりはありませんが、セマンティックバージョニングで付けるといいと思います。
$name プラグイン(拡張アプリ)の名前を指定します。ここの名前が管理画面に表示されます。特に命名規則はありません
$author 作者を指定します。
$module ここにString(英数字)を指定すると、その名前でモジュールID作成の時、選択肢として表示されます。(非推奨)
$menu ここにString(英数字)を指定すると、そのURLでプラグイン(拡張アプリ)の管理画面が作成されます。
例: 'sample_index'
URL: /bid/1/admin/app_sample_index/
パス: themes/system/admin/app/sample/index.html
$desc プラグイン(拡張アプリ)の詳細を記述します。管理画面で表示されます。

メソッド


メソッド名 説明
init インストールされた場合に動作し初期処理を記述できます。
例えば、Hook処理のバインドやテンプレートの挿入処理、autoloadの設定などに利用します。
checkRequirements インストール前のチェックに利用します。「false」を返すとインストールが出来ません。
install ルートブログのアプリ管理画面からアプリのインストール時に実行されます。データベースの初期化処理などに利用ください。
uninstall ルートブログのアプリ管理画面からアプリのアンインストール時に実行されます。データベースの削除処理などに利用ください。
update プラグイン(拡張アプリ)のアップデート時に利用します。データ構造の変更などある場合に利用します。
activate プラグイン(拡張アプリ)の有効時(ブログ単位)に動作します。インストール時にもここが実行されます。
deactivate プラグイン(拡張アプリ)を無効化時(ブログ単位)に動作します。アンインストール時にもここが実行されます。

ポイント

  • extension/plugins/ の名前空間は 「Acms\Plugins」 です。
  • \ACMS_App を継承します。

これでプラグイン(拡張アプリ)開発の基本は終了です。次から実際の拡張方法について見ていきます。

モジュールの拡張

プラグイン(拡張アプリ)でのモジュールの拡張方法を見ていきます。基本的には、通常の拡張方法と同様ですが名前空間・クラス名に気をつけましょう。

GET・POST モジュールは、extension/plugins/独自ディレクトリ/(GET|POST) ディレクトリ以下に設置します。

例: extension/plugins/SamplePlugin/GET/Sample.php

<?php

namespace Acms\Plugins\SamplePlugin\GET;

use ACMS_GET;
use Template;
use ACMS_Corrector;

/**
 * テンプレート上では、標準のGETモジュールと同様に、
 * '<!-- BEGIN_MODULE Sample --><!--END_MODULE Sample -->' で呼び出されます。
 */
class Sample extends ACMS_GET
{
    function get()
    {
        $Tpl = new Template($this->tpl, new ACMS_Corrector());
        $data = array();

        return $Tpl->render($obj);
    }
}

気をつける点としては、「GET」 は 全て大文字 なので注意してください。あとは、モジュールの開発 を参照ください。

校正オプションの拡張

プラグイン(拡張アプリ)での校正オプションの拡張の仕方を見ていきます。名前空間・クラス名に気をつけましょう。

ファイル名は任意ですが、わかりやすくする為、Corrector.php とします。

例: extension/plugins/SamplePlugin/Corrector.php

<?php

namespace Acms\Plugins\SamplePlugin;

class Corrector
{
    /**
     * sample
     * 校正オプションのサンプルメソッド
     *
     * @param  string $txt  - 校正オプションが適用されている文字列
     * @param  array  $args - 校正オプションの引数 {var}[sample('ここの値')]
     * @return string       - 校正後の文字列
     */
    public function sample($txt, $args = array())
    {
        // 例 {var}[sample('hoge','fuga')]
        // {var}の中は,'a-blogcms' とする

        $hoge = isset($args[0]) ? $args[0] : null; // 'hoge'
        $fuga = isset($args[1]) ? $args[1] : null; // 'fuga'

        return $hoge.$fuga.'+'.$txt; // 'hogefuga+a-blog cms'
    }
}

ServiceProviderの設定

これだけでは、通常の拡張方法と違い動作しません。動作させるためには、このクラスのオブジェクトをa-blog cmsに登録する必要があります。

ServiceProviderで校正オプションの実装クラスを登録する

拡張アプリの基本で作成した ServiceProviderに少し手を加えます。

<?php

namespace Acms\Plugins\SamplePlugin;

use ACMS_App;
use Acms\Services\Common\CorrectorFactory;

class ServiceProvider extends ACMS_App
{
    /* 省略... */

    /**
     * サービスの初期処理
     */
    public function init()
    {
        $corrector = CorrectorFactory::singleton();
        $corrector->attach('SampleCorrector', new Corrector);
    }

    /* 省略... */
}

ポイント

Acms\Services\Common\CorrectorFactory のオブジェクトを

CorrectorFactory::singleton();

で取得して 先ほど作成した Acms\Plugins\SamplePlugin\Correctorattach します。

  • 第一引数: 被らないような任意の文字列
  • 第2引数: 実装クラスのオブジェクト

これでプラグイン(拡張アプリ)をインストールすれば、Corrector.php で実装した校正オプションが利用できるようになります。

フックの拡張

拡張アプリでのフックの拡張の仕方を見ていきます。名前空間・クラス名に気をつけましょう。

ファイル名は任意ですが、わかりやすくする為、Hook.php とします。

例: extension/plugins/SamplePlugin/Hook.php

<?php

namespace Acms\Plugins\SamplePlugin;

class Hook
{
    /**
     * 例: グローバル変数の拡張
     *
     * @param array &$globalVars
     */
    public function extendsGlobalVars(&$globalVars)
    {
         $globalVars->set('SAMPLE', 'サンプルのグローバル変数です');
    }
}

ServiceProviderの設定

これだけでは、通常の拡張方法と違い動作しません。動作させるためには、このクラスのオブジェクトをa-blog cmsに登録する必要があります。

ServiceProviderでHook実装クラスを登録する

拡張アプリの基本で作成した ServiceProviderに少し手を加えます。

<?php

namespace Acms\Plugins\SamplePlugin;

use ACMS_App;
use Acms\Services\Common\HookFactory;

class ServiceProvider extends ACMS_App
{
    /* 省略... */

    /**
     * サービスの初期処理
     */
    public function init()
    {
        $hook = HookFactory::singleton();
        $hook->attach('SampleHook', new Hook);
    }

    /* 省略... */
}

ポイント

Acms\Services\Common\HookFactory のオブジェクトを

HookFactory::singleton();

で取得して 先ほど作成した Acms\Plugins\SamplePlugin\Hookattach します。

  • 第一引数: 被らないような任意の文字列
  • 第2引数: 実装クラスのオブジェクト

これで拡張アプリをインストールすれば、Hook.php で実装したHookが利用できるようになります。

Validatorの拡張

拡張アプリでのValidatorの拡張の仕方を見ていきます。名前空間・クラス名に気をつけましょう。

ファイル名は任意ですが、わかりやすくする為、Validator.php とします。

例: extension/plugins/SamplePlugin/Validator.php

<?php

namespace Acms\Plugins\SamplePlugin;

class Validator
{
    /**
     * sample
     * バリデーターのサンプルメソッド
     *
     * @param  string $val - その変数の値
     * @param  string $arg - <input type="hidden" name="var:v#sample" value="ここの値">
     * @return boolean     - 入力が正しい場合は "true" そうでない場合は "false" を返す
     */
    function sample($val, $arg)
    {
        /**
         * 例:
         * <input type="text" name="var" value="{var}">
         * <input type="hidden" name="field[]" value="var">
         * <input type="hidden" name="var:v#sample" value="cms">
         *
         * <!-- BEGIN var:validator#sample -->
         *   <p class="acms-admin-text-error">cmsという文字列を含めてください。</p>
         * <!-- END var:validator#sample -->
         *
         * {var}の中は,'a-blogcms' とする
         */

        // name="var:v#sample" value="cms" で指定した
        // 文字列が含まれていなかったらエラーを出す
        return (strpos($val, $arg) !== false);
    }
}

ServiceProviderの設定

これだけでは、通常の拡張方法と違い動作しません。動作させるためには、このクラスのオブジェクトをa-blog cmsに登録する必要があります。

ServiceProviderでValidator実装クラスを登録する

拡張アプリの基本で作成した ServiceProviderに少し手を加えます。

<?php

namespace Acms\Plugins\SamplePlugin;

use ACMS_App;
use Acms\Services\Common\ValidatorFactory;

class ServiceProvider extends ACMS_App
{
    /* 省略... */

    /**
     * サービスの初期処理
     */
    public function init()
    {
        $validator = ValidatorFactory::singleton();
        $validator->attach('SampleValidator', new Validator);
    }

    /* 省略... */
}

ポイント

Acms\Services\Common\ValidatorFactory のオブジェクトを

ValidatorFactory::singleton();

で取得して 先ほど作成した Acms\Plugins\SamplePlugin\Validatorattach します。

  • 第一引数: 被らないような任意の文字列
  • 第2引数: 実装クラスのオブジェクト

これで拡張アプリをインストールすれば、Validator.php で実装したValidatorが利用できるようになります。

テンプレートの挿入


拡張アプリで様々な拡張をする場合、プログラムだけではなく管理ページを拡張したい場合があると思います。例えば、モジュールID化したいため、モジュール選択肢のテンプレートを拡張したい、拡張アプリ用の管理画面ページ(設定ページ)を作りたいなどです。


対応バージョン: Ver. 3.2 .24 で、差し込みエントリーに callable(HTML を返す関数)を渡せるようになりました。


仕組み

差し込みポイントは、システムテーマのテンプレートに置かれた Admin_InjectTemplate モジュールです。id 属性が差し込みポイントの種別になります。

<!-- BEGIN_MODULE Admin_InjectTemplate id="admin-main" --><!-- END_MODULE Admin_InjectTemplate -->

プラグインは InjectTemplate::add() で「この $type のときはこの内容を出す」と登録します。管理画面の描画時に、登録された内容がこの位置へ差し込まれます。

API

Acms\Services\Common\InjectTemplate はシングルトンです。singleton() で取得して使います。

add(string $type, string|callable $entry): void

差し込みポイント $type に内容を登録します。同じ $type に複数回登録でき、登録順に出力されます。

$entry は次のどちらかを渡せます。

種類

渡すもの

処理

文字列パス

標準テンプレートの相対パス(PLUGIN_DIR 起点)

<!--#include file="..." --> として展開される

callable

HTML 文字列を返す関数

戻り値の HTML をそのまま差し込む(空文字なら出力しない)

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

$inject = InjectTemplate::singleton();

// (1) 標準テンプレートのパスを渡す(従来の方法)
$inject->add('admin-module-select', PLUGIN_DIR . 'MyPlugin/template/module-select.html');

// (2) callable を渡して Twig の描画結果を差し込む
$inject->add('admin-main', static function (): string {
    return Twig::renderTemplate('@myplugin/admin/main.twig');
});

get(string $type): array

$type に登録されたエントリーの配列を返します。通常はフレームワーク側が描画時に呼ぶため、拡張側で直接使う場面は多くありません。

利用できる差し込みポイント

標準で用意されている主な $type です。配置場所はシステムテーマのテンプレートです。

$type

差し込まれる場所

admin-main

管理画面メイン領域(プラグイン専用画面の本体)

admin-topicpath

パンくずリスト

admin-action

アクションボックス

admin-module-select

モジュール選択画面

admin-module-config-{モジュール名}

各モジュールの設定画面

admin-form / admin-form-option-extend

フォーム編集画面

admin-entry-editor-top

エントリー編集画面の上部

admin-entry-field / admin-entry-field-foot

エントリー編集画面のフィールド領域

admin-category-field

カテゴリー編集画面のフィールド領域

admin-blog-field

ブログ編集画面のフィールド領域

admin-user-field

ユーザー編集画面のフィールド領域

admin-module-config-{モジュール名}{モジュール名} には、対象モジュールの名前が入ります(例: admin-module-config-Sample)。

差し込みポイントは利用テーマ側のテンプレートにも置けます。独自の差し込みポイントを増やしたい場合は、テンプレートに Admin_InjectTemplate モジュールを id 付きで配置し、同じ id を $type として add() してください。

callable と文字列パスの使い分け

  • Twig テンプレートで画面を組むなら callable を渡し、その中で Twig::renderTemplate() を呼びます。

  • 既存の 標準テンプレートを差し込むなら文字列パスを渡します。従来どおりの書き方です。

  • callable が空文字を返した場合、その分は出力されません。条件によって出し分けたいときに利用できます。

注意点

  • callable は管理画面の描画時に実行されます。重い処理を避け、表示に必要な HTML を返すことに徹してください。

  • 文字列パスと callable は同じ $type に混在できます。登録順に連結されます。

関連ドキュメント

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")をチェックボックスの前に置きます。

Twig 拡張 API(テンプレート・Filter・Function・Extension の登録)


プラグイン(拡張アプリ)やサイト拡張から、Twig を拡張するための API をまとめたリファレンスです。Twig ファサード(Acms\Services\Facades\Twig)と extendsTwig フックを通じて、テンプレートディレクトリの登録・部分描画・独自フィルターや関数の追加を行います。

全体像と位置づけは プラグイン(拡張アプリ)で Twig 管理画面を作る を参照してください。


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


登録経路は 2 つに分かれる

拡張は「登録するもの」によって書く場所が変わります。これは内部の都合(後述)によるもので、最初に押さえておくと迷いません。

登録するもの

書く場所

API

テンプレートディレクトリ

ServiceProvider::init()(一度だけ)

Twig::addTemplatePath()

Filter / Function / Extension

extendsTwig フック

Twig::registerFilter() ほか

テンプレートディレクトリの登録

Twig::addTemplatePath(string $dir, ?string $namespace = null): void

自前のテンプレートディレクトリを Twig のローダーに登録します。$namespace を指定すると、@namespace/... の形でテンプレートを参照できます。省略するとメイン名前空間に登録されます。

use Acms\Services\Facades\Twig;

// init() の中で一度だけ呼べばよい
Twig::addTemplatePath(PLUGIN_LIB_DIR . 'MyPlugin/template', 'myplugin');
{# 登録後はこう参照できる #}
{{ include('@myplugin/parts/header.twig') }}
  • $dir にはファイルシステムのパスを渡します。プラグインでは PLUGIN_LIB_DIR を使います。

  • ローダーの状態は Twig サービス内に保持され、Twig 環境が作り直されても引き継がれます。そのため初期化時に一度呼べば十分です。

  • @system はシステムテーマ参照用に予約された名前空間です。プラグインは独自の名前空間を切ってください。

Twig テンプレートを解決する

Twig::renderTemplate(string $name, array $context = []): string

指定したテンプレートを描画して、結果の HTML 文字列を返します。$context で変数を渡せます。差し込みポイントに登録する callable の中などで使います。

use Acms\Services\Facades\Twig;

$html = Twig::renderTemplate('@myplugin/admin/main.twig', [
    'title' => 'MyPlugin 設定',
    'items' => $items,
]);
<h1>{{ title }}</h1>
<ul>
  {% for item in items %}
    <li>{{ item }}</li>
  {% endfor %}
</ul>

描画結果をどこに出すかは InjectTemplate を参照してください。

Filter / Function / Extension の登録

独自のフィルター・関数・拡張クラスは、extendsTwig フックの中で登録します。フックは Twig 環境が組み立てられるたびに呼ばれます。

extendsTwig フック

extension/acms/Hook.php やプラグイン(拡張アプリ)のフックに extendsTwig メソッドを実装します。引数には Twig サービスが渡されます。

/**
 * Twig Environment 構築時に呼ばれる拡張ポイント
 */
public function extendsTwig(\Acms\Services\Template\Twig $twig): void
{
    $twig->registerFilter('site_format', fn(string $s) => '⚡' . $s);
    $twig->registerFunction('site_now', fn() => date('Y-m-d H:i:s'));
    $twig->registerExtension(new \Acms\Custom\Twig\SiteExtension());
}
{{ 'hello'|site_format }}   {# ⚡hello #}
{{ site_now() }}            {# 2026-06-17 10:00:00 #}

各メソッド

メソッド

説明

registerFilter(string $name, callable $callable, array $options = []): void

フィルターを登録する

registerFunction(string $name, callable $callable, array $options = []): void

関数を登録する

registerExtension(\Twig\Extension\AbstractExtension $extension): void

Twig Extension クラスを登録する

$options には Twig 標準の TwigFilter / TwigFunction のオプション(is_safe など)を渡せます。

同名のフィルター・関数を複数回登録した場合は、最後の登録が採用されます。同じ型の Extension も最後の登録にまとまります。

なぜ登録場所が分かれるのか

テンプレートディレクトリの登録と、Filter / Function / Extension の登録で書く場所が違うのには理由があります。

  • テンプレートディレクトリは Twig の「ローダー」に追加します。ローダーの状態はサービス側に保持され、環境が作り直されても保たれるため、初期化で一度登録すれば以後ずっと有効です。

  • Filter / Function / Extension は Twig の「環境(Environment)」インスタンスに結び付きます。環境は状況によって作り直されることがあり、そのたびに登録し直す必要があります。extendsTwig フックは環境の構築時に毎回呼ばれるので、ここに書いておけば作り直されても確実に再登録されます。

init()registerFilter() を呼んでも、内部で保留して環境構築時に反映する作りにはなっていますが、意図が明確になる extendsTwig フックに書くことを推奨します。