registerTag自定义标签
Tag标签 和插件 的注册方式大体是一样的,但是 Tag 标签在这里指需要闭合的标签,如果 {list}{/list} 标签,在开发中我们可能会使用成对的标签把中间部分包括起来,使用循环等操作。
编写Tag插件
插件的定义和命名规则:
Tag插件在 Sdopx 模板引擎中是一个类,应该实现 sdopx\interfaces\Tag ,但是并不要强制要求以代码接口实现的形式书写代码。
下面使用一个例子来介绍如何自定义一个 Tag插件:
list 插件
define('ROOT_DIR', __DIR__);
date_default_timezone_set('PRC');
require(ROOT_DIR . '/vendor/autoload.php');
use sdopx\lib\Outer;
use sdopx\Sdopx;
Sdopx::$defaultTemplateDirs = './view';
Sdopx::$defaultCompileDir = './runtime';
class ListTag
{
/**
* 用于回调的参数描述变量指定
* @return array
*/
public function callbackParameter(): array
{
// 1 键名 item 是指在标签中如 {list item=rs} 中 item 的属性名称,
// 会从这里获取的值 item 获取的rs 将作为下面 reader 中 $callback($arg) 中的参数名称,在$callback 内部可以使用 $rs 变量来读取 $arg 的值。
// 我们可以这么理解 使用item 属性指定一个变量 $rs,使得 callback 中的 $rs=$arg; 那么 在 包含中的模板内容 可以使用 $rs 来读取数据。
// 2 must => true 表示这个item 属性必须存在,如果不指定默认的 defualt='rs' 那么 如果不设置这个参数会报错。
// 3 可以指定多个回调参数,但是 callback 调用时 需要对于指定的参数传值。
return [
'item' => ['must' => true, 'default' => 'rs'],
];
}
/**
* 渲染
* @param array $params 这个参数 是来自模板插件传递过来的属性参数值
* @param $callback 中间部分内容的回调函数
* @param \sdopx\lib\Outer $outer 用于输出内容或者抛出错误的输出对象
*/
public function render(array $params, $callback, Outer $outer)
{
$data = isset($params['data']) ? $params['data'] : [];
if (!is_array($data)) {
$outer->throw('data 参数必须是数组形式');
return;
}
$outer->html('<ul>');
foreach ($data as $item) {
//因为 callbackParameter 中指定了 一个参数,那么如果不传属性,默认可以在 中间模板中使用 $rs 读取,如果指定可以使用对应的变量读取
call_user_func($callback, $item);
}
$outer->html('</ul>');
}
}
//注意 提供的是 实例
Sdopx::registerTag('list', new ListTag());
$sdopx = new Sdopx();
$sdopx->assign('name', 'wj008');
$sdopx->display('index.tpl');
Outer 说明:
//说明:sdopx\lib\Outer 提供了一些方法,以及可以获得模板对象。
$outer->sdopx //php 代码中的 $sdopx 对象
//如需要设置插件缓存值等
$outer->sdopx->_cache['name']='xxx';
$outer->text($code) // 会转成html实体输出,防止xss 漏洞攻击,建议使用。
$outer->html($code) // 原样输出 html 代码。
$outer->throw($error) // 抛出异常,使用该方法抛出异常在调试模式下会自动附上模板错误位置,建议使用。
注意:我们在模板插件开发中,所有的输出均使用 sdopx\lib\Outer 进行输出,不可使用 系统自带的 echo print 等方法输出。
callbackParameter 返回数据描述:
Sdopx 中 Tag 标记中包含的代码是以匿名回调方式提供给开发者的,因为编译器并不知道开发者您的回调到底需要多少个参数,所以 需要使用 callbackParameter 指定你的回调参数属性
{list} 包含的代码 {/list}
参数的数量可能为因为 使用标签者设置或者不设置参数从而导致 编译器生成的参数数量是不确定的。
如下面这种情况:
class ListTag
{
/**
* 用于回调的参数描述变量指定
* @return array
*/
public function callbackParameter(): array
{
return [
'item' => ['must' => true, 'default' => 'rs'],
//这个key属性使用者可能设置或者不设置 must=false 是允许不设置
'key' => ['must' => false],
];
}
/**
* 渲染
* @param array $params 这个参数 是来自模板插件传递过来的属性参数值
* @param $callback 中间部分内容的回调函数
* @param \sdopx\lib\Outer $outer 用于输出内容或者抛出错误的输出对象
*/
public function render(array $params, $callback, Outer $outer)
{
$data = isset($params['data']) ? $params['data'] : [];
if (!is_array($data)) {
$outer->throw('data 参数必须是数组形式');
return;
}
$outer->html('<ul>');
foreach ($data as $key => $item) {
//因为 callbackParameter 中指定了 一个参数,那么如果不传属性,默认可以在 中间模板中使用 $rs 读取,如果指定可以使用对应的变量读取
if (isset($params['key'])) {
call_user_func($callback, $item, $key);
} else {
call_user_func($callback, $item);
}
}
$outer->html('</ul>');
}
}
key 的属性 {list data=$data key=i} 它可能被设置,但是也可能不被设置,那么我们调用 callback 的时候 就需要传递不同的参数数量。
注意:回调函数 callback 的参数顺序必须和 callbackParameter 返回的数组参数位置一致。
测试模板:
{*不指定 item 使用 callbackParameter 设置的默认值 rs*}
{list data=[['id'=>1,'name'=>'wj007'],['id'=>2,'name'=>'wj008']]}
<li>id:{$rs.id},名字:{$rs.name}</li>
{/list}
{*指定 item 为 vo*}
{list data=[['id'=>1,'name'=>'wj007'],['id'=>2,'name'=>'wj008']] item='vo'}
<li>id:{$vo.id},名字:{$vo.name}</li>
{/list}
输出:
<ul>
<li>id:1,名字:wj007</li>
<li>id:2,名字:wj008</li>
</ul>
<ul>
<li>id:1,名字:wj007</li>
<li>id:2,名字:wj008</li>
</ul>
自动装载插件
Tag 插件的自动载入和 Plugin 基本一致,其不同之处在于 命名后缀要求是 Tag
1 需要被自动载入的插件命名要求。
需要自动载入的插件命名必须为大驼峰形式的类名 其类名后缀必须是 Tag 结尾,文件名与类名必须保持一致。
如:
class List //无效,没有以Tag 结尾,找不到。
class listTag //无效,类名不是大驼峰,找不到。
class My_listTag //无效,类名不可出现下划线。
class MyListTag //有效,但是 插件使用时是 {my_list} 而不是 {myList} 也不是 {mylist}
class ListTag // 有效 插件使用是 {list}
文件名 如 ListTag.php
2 需要被自动载入的插件 命名空间必须是 namespace sdopx\plugin;
Sdopx 是有命名空间要求的,所以需要被自动载入插件的类,必须是在 sdopx\plugin 下的。
如:
ListTag.php
namespace sdopx\plugin;
use sdopx\lib\Outer;
class ListTag
{
/**
* 用于回调的参数描述变量指定
* @return array
*/
public function callbackParameter(): array
{
return [
'item' => ['must' => true, 'default' => 'rs'],
'key' => ['must' => false],
];
}
/**
* 渲染
* @param array $params 这个参数 是来自模板插件传递过来的属性参数值
* @param $callback 中间部分内容的回调函数
* @param \sdopx\lib\Outer $outer 用于输出内容或者抛出错误的输出对象
*/
public function render(array $params, $callback, Outer $outer)
{
$data = isset($params['data']) ? $params['data'] : [];
if (!is_array($data)) {
$outer->throw('data 参数必须是数组形式');
return;
}
$outer->html('<ul>');
foreach ($data as $key => $item) {
if (isset($params['key'])) {
call_user_func($callback, $item, $key);
} else {
call_user_func($callback, $item);
}
}
$outer->html('</ul>');
}
}
3 需要被自动载入的插件 存放位置要求。
如果 你使用的是 composer 自动加载路径,那么 你只要在你的 composer.json 文件中指定 sdopx/plugin 命名空间的对应目录即可。
如:
composer.json
{
.... 你的其他配置信息
"autoload": {
"psr-4": {
"sdopx\\plugin\\": "myplugin",
}
},
.... 你的其他配置信息
}
这个时候 你可以把你的插件放入跟目录下的 myplugin 文件夹中,并且运行 composer update
如果上例子 存放路径为 /myplugin/ListTag.php
如果你不使用 composer 那么 你需要用 Sdopx::setPluginDir(); 来设置你插件的存放路径
使用代码如下:
define('ROOT_DIR', __DIR__);
date_default_timezone_set('PRC');
require(ROOT_DIR . '/sdopx/Sdopx.php');
use sdopx\Sdopx;
Sdopx::$defaultTemplateDirs = './view';
Sdopx::$defaultCompileDir = './runtime';
//设置自定义插件存放路径
Sdopx::setPluginDir('./myplugin');
$sdopx = new Sdopx();
$sdopx->assign('name', 'wj008');
$sdopx->display('index.tpl');
在这里 我们依然建议您使用 composer 的方式自动载入命名空间。
使用继承的方式创建 Tag
例如,我们系统内置提供了 volist 这个 Tag标签,那么我们可能并不想自己去实现太多功能,我们可以继承它来实现一些我们想要的东西。
这里 我演示实现一个 文章列表插件
class ArticleTag extends \sdopx\plugin\VolistTag
{
public function render(array $params, $callback, Outer $outer)
{
//我需要从上下文中获取数据库操作类 db 需要创建的时候 使用 new Sdopx($context) 来实例化,并且假设 $context->db 为数据库操作类
$context = $outer->sdopx->context;
if ($context == null) {
$outer->throw('没有设置上下文 context');
}
/**
* @var \mysqli
*/
$db = $context->db; //可以从 $context 拿到 db 也可以PHP使用 $sdopx->_cache['db']=$db; 存入并在插件中获取
$offset = isset($params['offset']) ? intval($params['offset']) : 0;
$length = isset($params['length']) ? intval($params['length']) : 20;
$mod = isset($params['mod']) ? intval($params['mod']) : 1;
$limit = $length * $mod;//保证足够的数据可以被 mod 逃过
$from = $db->query('select * from Article limit ' . $offset . ',' . $limit)->fetch_all(MYSQLI_ASSOC);
//因为已经使用了sql 跳过,所以volist 中不需要跳过
$params['offset'] = 0;
$params['from'] = $from;//这里将读取的数据交给 volist
$outer->html('<ul>');
parent::render($params, $callback, $outer);
$outer->html('</ul>');
}
}
对于$ db 的传入,如果上下文支持 db 可以使用上下文的方式,如果不支持 可以使用 $sdopx->_cache 的方式存入db操作对象, $sdopx->_cache 键名需要避免重复覆盖。
使用 cache
class ArticleTag extends \sdopx\plugin\VolistTag
{
public function render(array $params, $callback, Outer $outer)
{
$db = $outer->sdopx->_cache['sdopx_db'];
if ($db == null) {
$outer->throw('没有设置 sdopx_db 的缓存');
}
$offset = isset($params['offset']) ? intval($params['offset']) : 0;
$length = isset($params['length']) ? intval($params['length']) : 20;
$mod = isset($params['mod']) ? intval($params['mod']) : 1;
$limit = $length * $mod;//保证足够的数据可以被 mod 逃过
$from = $db->query('select * from Article limit ' . $offset . ',' . $limit)->fetch_all(MYSQLI_ASSOC);
//因为已经使用了sql 跳过,所以volist 中不需要跳过
$params['offset'] = 0;
$params['from'] = $from;//这里将读取的数据交给 volist
$outer->html('<ul>');
parent::render($params, $callback, $outer);
$outer->html('</ul>');
}
}
使用的时候需要传入db
$sdopx = new Sdopx();
$sdopx->_cache['sdopx_db']=$db;
$sdopx->assign('name', 'wj008');
$sdopx->display('index.tpl');