Chuyển đến nội dung
Tài liệu

Hướng dẫn cho nhà phát triển

Xây dựng mô-đun

Mô-đun là đơn vị cơ bản để thêm tính năng trong Zittme. Bảng DB riêng, màn hình, cài đặt quản trị, cho đến trigger chen vào mô-đun khác đều được làm thành mô-đun.

Cấu trúc thư mục

Khuyến nghị dùng cấu trúc hiện đại dựa trên namespace.

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       ← 컴포저 의존성을 쓸 경우

Lớp dùng namespace khớp với cấu trúc thư mục, như Zittme\Modules\Mymodule\Controllers\Index.

  • Khung chuẩn là chia controller theo nhóm màn hình (Index/Read/Write...) thay vì dồn vào một. Dùng class="Controllers\Read" trong module.xml để chỉ định lớp phụ trách cho từng action.
  • Mô-đun dùng gói Composer cần khai báo kèm stub "require": { "rhymix/composer-stub": "dev-master" } trong composer.json. Nhờ đó mô-đun dùng được vendor riêng mà không xung đột với lõi.
Có công cụ tạo mô-đun xuất ra đúng cấu trúc này: Trình tạo mô-đun Rhymix/XE của poesis.dev. Khuyến nghị bắt đầu từ kết quả của trình tạo thay vì từ khung trống.

module.xml: khai báo action

Chỉ những action được khai báo trong module.xml mới được thực thi. Gọi action chưa khai báo sẽ gặp lỗi 403 hoặc 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>

Quy tắc cốt lõi:

  • disp chỉ dùng GET, proc chỉ dùng POST. Gọi proc bằng liên kết (GET) sẽ gặp lỗi 405. Thao tác cần đi vào từ màn hình thì làm thành disp, chỉ nhận việc gửi form bằng proc.
  • Action proc tự động kiểm tra token CSRF. Callback·API do máy chủ bên ngoài gọi phải thêm standalone="true" check-csrf="false". Tên thuộc tính dùng dấu gạch nối (check-csrf). Nếu viết bằng dấu gạch dưới, thuộc tính bị bỏ qua và việc kiểm tra vẫn bật.
  • index="true" là màn hình mặc định khi truy cập bằng mid.
  • permission chỉ định member (đã đăng nhập), manager (quyền quản lý), v.v.

Route

Khi thêm <route>, địa chỉ trở thành dạng ngắn mid/value.

  • Giới hạn biến bằng $name:type: int (số nguyên từ 0 trở lên) float alpha (chữ Latinh) alnum (chữ Latinh + số) hex word (chữ Latinh + số + gạch dưới) any (mọi ký tự trừ dấu gạch chéo) delete (xóa khỏi URL)
  • Nếu bỏ qua kiểu, biến kết thúc bằng _srl được xem là int, còn lại là any.
  • Route có số priority cao hơn được so khớp trước.
  • getUrl() tự chọn route khớp nhất với các biến được truyền để tạo địa chỉ ngắn, và gắn các biến còn lại không có trong route thành query string.
  • Khi đặt global-route="true", route được so khớp ngay tại gốc website mà không cần mid (/search, v.v.). Vì có nguy cơ xung đột nên chỉ dùng khi thật sự cần.
  • Action có error-handlers="404" sẽ trở thành trình xử lý 404 của mô-đun đó.

Sau khi sửa module.xml, phải chạy cập nhật mô-đun trong trang quản trị thì route·event handler mới được áp dụng.

Controller

<?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')));
	}
}
  • Lỗi được trả về bằng new \BaseObject(-1, 'lang-key').
  • Tệp ngôn ngữ là tệp PHP theo dạng $lang->msg_title_required = '...';.

Cài đặt và cập nhật: Install.php

Lõi tạo bảng dựa trên schemas/*.xml. Lớp Install phụ trách các việc khởi tạo khác.

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()
	{
	}
}

Quan trọng: lõi không tự động thêm cột mới trong XML schema vào bảng đã được tạo. Khi thêm cột trong lúc vận hành, ngoài việc ghi vào XML schema, bạn phải thêm cột bằng $oDB->isColumnExists() / addColumn() trong checkUpdate/moduleUpdate.

Trigger: chen vào mô-đun khác

Lõi và các mô-đun khác gọi trigger ở nhiều nơi. Bạn chỉ cần khai báo event handler trong module.xml.

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

Hãy viết handler một cách phòng thủ để ngoại lệ không làm sập lõi.

Những lỗi thường gặp khi phát triển

  • Action mới bị 403/404: chưa đăng ký trong module.xml hoặc chưa chạy cập nhật mô-đun
  • proc bị 405: đã gọi bằng GET. Đổi thành disp hoặc gọi bằng POST của form
  • Giá trị đưa vào kết quả truy vấn biến mất: cột không có trong <columns> của query XML sẽ bị bỏ đi mà không báo. Xem Query XML
  • Template lỗi 500: chú thích {{-- --}} nhiều dòng. Xem Cú pháp template v2