Ir al contenido
Docs

Guía para desarrolladores

Crear módulos

Los módulos son la unidad básica para agregar funciones en Zittme. Tablas propias en la base de datos, pantallas, ajustes de administración e incluso triggers que se integran en otros módulos: todo se construye como módulo.

Estructura de carpetas

Te recomendamos la estructura moderna basada en espacios de nombres.

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

Las clases usan un espacio de nombres que coincide con la estructura de carpetas, como Zittme\Modules\Mymodule\Controllers\Index.

  • La estructura estándar es dividir los controladores por grupo de pantallas (Index/Read/Write...) en lugar de juntarlo todo en uno. En module.xml asignas la clase responsable de cada acción con class="Controllers\Read".
  • Los módulos que usan paquetes de Composer declaran también el stub "require": { "rhymix/composer-stub": "dev-master" } en composer.json. Así cada módulo puede usar su propio vendor sin chocar con el núcleo.
Existe un generador de módulos que crea exactamente esta estructura: el generador de módulos Rhymix/XE de poesis.dev. Te recomendamos partir del resultado del generador en lugar de un esqueleto vacío.

module.xml: declaración de acciones

Solo se ejecutan las acciones declaradas en module.xml. Si llamas a una acción no declarada, obtendrás un 403 o un 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>

Reglas clave:

  • disp es solo para GET y proc solo para POST. Si llamas a un proc desde un enlace (GET), obtendrás un 405. Las acciones a las que se entra desde una pantalla hazlas como disp, y recibe con proc solo el envío de formularios.
  • Las acciones proc verifican el token CSRF automáticamente. Los callbacks y APIs que llama un servidor externo deben llevar standalone="true" check-csrf="false". El nombre del atributo lleva guion (check-csrf). Si lo escribes con guion bajo, se ignora y la verificación sigue activa.
  • index="true" es la pantalla predeterminada al entrar por el mid.
  • permission indica member (sesión iniciada), manager (permiso de administración), etc.

Rutas

Si agregas <route>, obtienes una dirección corta de la forma mid/value.

  • Las variables se restringen con $name:type: int (entero de 0 o más) float alpha (letras) alnum (letras + números) hex word (letras + números + guion bajo) any (todo excepto la barra) delete (se elimina de la URL)
  • Si omites el tipo, las variables que terminan en _srl se tratan como int y el resto como any.
  • Las rutas con un número de priority más alto se evalúan primero.
  • getUrl() elige automáticamente la ruta que mejor coincide con las variables que le pasas para generar la dirección corta, y agrega como query string las variables que no están en la ruta.
  • Con global-route="true" la ruta coincide directamente desde la raíz del sitio, sin mid (/search, etc.). Hay riesgo de conflictos, así que úsalo solo cuando sea necesario.
  • La acción con error-handlers="404" se convierte en el manejador de 404 de ese módulo.

Después de modificar module.xml, debes ejecutar la actualización del módulo en la administración para que se apliquen las rutas y los manejadores de eventos.

Controladores

<?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')));
	}
}
  • Los errores se devuelven con new \BaseObject(-1, 'lang_key').
  • Los archivos de idioma son archivos PHP con el formato $lang->msg_title_required = '...';.

Instalación y actualización: Install.php

El núcleo crea las tablas a partir de schemas/*.xml. La clase Install se encarga del resto de la inicialización.

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

Importante: el núcleo no agrega automáticamente las columnas nuevas del XML de esquema a tablas que ya existen. Para agregar una columna en producción, además de escribirla en el XML de esquema, debes agregarla en checkUpdate/moduleUpdate con $oDB->isColumnExists() / addColumn().

Triggers: integrarse en otros módulos

El núcleo y otros módulos llaman a triggers en muchos puntos. Basta con declarar un manejador de eventos en module.xml.

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

Escribe los manejadores de forma defensiva para que una excepción no tumbe el núcleo.

Problemas frecuentes durante el desarrollo

  • Una acción nueva da 403/404: no está registrada en module.xml o no ejecutaste la actualización del módulo
  • Un proc da 405: lo llamaste con GET. Cámbialo a disp o llámalo con un POST de formulario
  • Desaparecen valores que pusiste en el resultado de la consulta: las columnas que no están en <columns> del XML de consulta se descartan en silencio. Consulta Query XML
  • Error 500 en la plantilla: comentarios {{-- --}} de varias líneas. Consulta Sintaxis de plantillas v2