跳到主要內容
文件

開發者指南

模組製作

模組是在 Zittme 中新增功能的基本單位。專屬的資料庫資料表、畫面、管理員設定,乃至介入其他模組的觸發器,全都以模組製作。

資料夾結構

建議採用以命名空間為基礎的現代化結構。

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" } stub。這樣就能以模組為單位使用 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。以連結(GET)呼叫 proc 會出現 405。需要從畫面進入的動作請做成 disp,只有表單送出才以 proc 接收。
  • proc 動作會自動檢查 CSRF 權杖。由外部伺服器呼叫的回呼・API 必須加上 standalone="true" check-csrf="false"。屬性名稱使用連字號(check-csrf)。若寫成底線會被忽略,檢查仍會維持開啟。
  • index="true" 是以 mid 連線時的預設畫面。
  • permission 可指定 member(登入)、manager(管理權限)等。

路由

加上 <route> 後會成為 mid/value 形式的短網址。

  • 變數以 $name:type 限制:int(0 以上的整數) float alpha(英文字母) alnum(英文字母+數字) hex word(英文字母+數字+底線) any(斜線以外的全部) delete(從 URL 中移除)
  • 省略型別時,以 _srl 結尾的變數視為 int,其餘視為 any。
  • priority 數字較高的路由會優先比對。
  • getUrl() 會自動挑選與傳入變數最相符的路由來產生短網址,路由中沒有的其餘變數則附加為查詢字串。
  • 設定 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