本文へスキップ
ドキュメント

開発者ガイド

モジュール制作

モジュールは、Zittme で機能を追加するための基本単位です。独自の DB テーブル、画面、管理者設定、他のモジュールに割り込むトリガーまで、すべてモジュールとして作ります。

フォルダ構成

名前空間ベースのモダンな構成を推奨します。

modules/mymodule/
├── conf/
│   ├── info.xml        ← 이름·설명·버전·제작자
│   └── module.xml      ← 액션·권한·라우트·이벤트 핸들러
├── controllers/
│   ├── Base.php        ← 공통 상수·헬퍼 (ModuleObject 상속)
│   ├── Index.php       ← 화면 그룹별 컨트롤러 (목록)
│   ├── Read.php        ← (읽기)
│   ├── Write.php       ← (쓰기 화면 + proc)
│   ├── Admin.php       ← 관리자 화면·처리
│   ├── EventHandlers.php ← 트리거 핸들러
│   └── Install.php     ← 설치·업데이트 훅
├── models/             ← 데이터 로직
├── queries/            ← 쿼리 XML
├── schemas/            ← 테이블 XML
├── skins/default/      ← 사용자 화면 템플릿
├── views/admin/        ← 관리자 템플릿 (*.blade.php)
├── lang/ko.php         ← 언어 파일
└── composer.json       ← 컴포저 의존성을 쓸 경우

クラスは Zittme\Modules\Mymodule\Controllers\Index のように、フォルダ構成と一致する名前空間を使います。

  • コントローラーは一つにまとめず、画面グループごとに分けること(Index/Read/Write...)が標準の骨組みです。module.xml の class="Controllers\Read" でアクションごとに担当クラスを指定します。
  • Composer パッケージを使うモジュールは、composer.json に "require": { "rhymix/composer-stub": "dev-master" } のスタブを一緒に宣言します。コアと衝突せずにモジュール単位の vendor を使えるようにしてくれます。
この構成をそのまま生成してくれるモジュールジェネレーターがあります:poesis.dev の Rhymix/XE モジュールジェネレーター。空の骨組みから始めるのではなく、ジェネレーターの生成物から始めることを推奨します。

module.xml:アクションの宣言

アクションは module.xml に宣言されたものだけが実行されます。宣言していないアクションを呼び出すと 403 または 404 になります。

<?xml version="1.0" encoding="UTF-8"?>
<module>
	<actions>
		<action name="dispMymoduleList" class="Controllers\Front" index="true">
			<route route="$page:int" priority="10" />
		</action>
		<action name="procMymoduleInsert" class="Controllers\Front" permission="member" />
		<action name="dispMymoduleAdminConfig" class="Controllers\Admin" permission="manager" admin-index="true" menu-name="mymodule" menu-index="true" />
		<action name="procMymoduleApiPing" class="Controllers\Api" standalone="true" check-csrf="false" />
	</actions>
	<menus>
		<menu name="mymodule">
			<title xml:lang="ko">내 모듈</title>
		</menu>
	</menus>
</module>

重要なルール:

  • disp は GET、proc は POST 専用です。proc をリンク(GET)で呼び出すと 405 になります。画面から入る必要がある動作は disp で作り、フォーム送信だけを proc で受け取ってください。
  • proc アクションは CSRF トークンを自動でチェックします。外部サーバーが呼び出すコールバック・API には standalone="true" check-csrf="false" を付ける必要があります。属性名はハイフン(check-csrf)です。アンダースコアで書くと無視され、チェックが有効なまま残ります。
  • index="true" は mid でアクセスしたときの基本画面です。
  • permission には member(ログイン)、manager(管理権限)などを指定します。

ルート

<route> を付けると mid/value の形の短縮 URL になります。

  • 変数は $name:type で制限します:int(0 以上の整数)float alpha(英字)alnum(英字+数字)hex word(英字+数字+アンダースコア)any(スラッシュ以外すべて)delete(URL から除去)
  • 型を省略すると、_srl で終わる変数は int、それ以外は any として扱われます。
  • priority の数値が大きいルートが先にマッチします。
  • getUrl() は渡された変数に最もよく合うルートを自動で選んで短縮 URL を作り、ルートにない残りの変数はクエリ文字列として付けます。
  • global-route="true" を指定すると、mid なしでサイトのルートから直接マッチします(/search など)。衝突の危険があるので、本当に必要なときだけ使ってください。
  • error-handlers="404" を指定したアクションは、そのモジュールの 404 ハンドラーになります。

module.xml を修正した後は、管理画面でモジュールのアップデートを実行しないとルート・イベントハンドラーが反映されません。

コントローラー

<?php

namespace Zittme\Modules\Mymodule\Controllers;

class Index extends Base
{
	/**
	 * init()은 이 컨트롤러의 액션이 실행되기 전에 자동 호출됩니다.
	 * 스킨 경로처럼 액션마다 반복되는 준비를 여기서 한 번에 합니다.
	 */
	public function init()
	{
		// 관리자가 고른 스킨을 존중하고, 없으면 default
		$this->setTemplatePath($this->module_path . 'skins/' . ($this->module_info->skin ?: 'default'));
	}

	public function dispMymoduleList()
	{
		$output = executeQueryArray('mymodule.getItems', new \stdClass);
		\Context::set('items', $output->data ?? []);

		$this->setTemplateFile('list');
	}

	public function procMymoduleInsert()
	{
		// Context::set이 같은 이름의 요청 변수를 덮어쓸 수 있으므로
		// proc에서는 $_POST를 직접 읽는 편이 안전합니다
		$title = trim((string)($_POST['title'] ?? ''));
		if ($title === '')
		{
			return new \BaseObject(-1, 'mymodule.msg_title_required');
		}

		$args = new \stdClass;
		$args->item_srl = getNextSequence();
		$args->title = $title;
		$args->regdate = date('YmdHis');

		$output = executeQuery('mymodule.insertItem', $args);
		if (!$output->toBool())
		{
			return $output;
		}

		$this->setMessage('success_registed');
		$this->setRedirectUrl(getNotEncodedUrl('', 'mid', \Context::get('mid')));
	}
}
  • エラーは new \BaseObject(-1, 'lang_key') で返します。
  • 言語ファイルは $lang->msg_title_required = '...'; 形式の PHP ファイルです。

インストールとアップデート:Install.php

テーブルは schemas/*.xml を見てコアが作成します。Install クラスはそれ以外の初期化を担当します。

class Install extends Base
{
	public function moduleInstall()
	{
		return new \BaseObject();
	}

	// true를 돌려주면 관리자 화면에서 업데이트가 실행됩니다
	public function checkUpdate()
	{
		return false;
	}

	public function moduleUpdate()
	{
		return new \BaseObject();
	}

	public function recompileCache()
	{
	}
}

重要:コアは、すでに作成済みのテーブルにスキーマ XML の新しいカラムを自動で追加しません。運用中にカラムを追加するときは、スキーマ XML に記述するとともに、checkUpdate/moduleUpdate で $oDB->isColumnExists() / addColumn() を使って追加する必要があります。

トリガー:他のモジュールに割り込む

コアや他のモジュールがあちこちでトリガーを呼び出します。module.xml にイベントハンドラーを宣言すれば使えます。

<eventHandlers>
	<eventHandler after="document.insertDocument" class="Controllers\Trigger" method="afterInsertDocument" />
</eventHandlers>

ハンドラーは、例外でコアを停止させないよう防御的に書いてください。

開発中によくあること

  • 新しいアクションが 403/404:module.xml に未登録か、モジュールのアップデートを実行していない場合
  • proc が 405:GET で呼び出した場合。disp に変えるか、フォームの POST で呼び出す
  • クエリの結果に入れた値が消える:クエリ XML の <columns> にないカラムは黙って捨てられます。クエリ XML を参照
  • テンプレートが 500:複数行の {{-- --}} コメント。テンプレート構文 v2 を参照