DedeCms深受用户欢迎,很大的原因是因为模板标签调用的灵活性和系统的强大可伸缩性,如果用传统的Smarty开发,是不可能达到那种效果的,当然我们可以对其进行一下改进,从而实现这样的功能。
一、设计目标:实现类似
{dede:arclist row='10'}
[field name=f1/] -- [field name=f2/]<br />
{/dede:arclist}
这样的风格的标签(注意:dede的标签是触发式的,因此我们不能简单的修改Smarty去实现,而是也要实现触发机制)
二、Smarty 的插件主要有下面几种:
1、block 块插件
用块插件参实现类似dede标签的效果,但在中间部份不能使用变量(那部份会被预先编译),只能使用简单的替换,因此块插件是并不适合我们要使用的方法,但是我们正是需要用与它类似的方法进行改进的。
2、compiler 插件
这种插件对属性里的变量不会预先编译,属于比较接近底层的一种插件,但它默认的情况是不能直接使用{tag}{/tag}这样的标签的,否则{/tag}这里会报错。
3、function 自由定义函数的插件
用{function att1=a att2=b ...}形式调用
4、modifier 输出内容修正插件
实际上这种插件和function是类似的,只是写法不同,它的写法即是
{$var|function}
其实和
{function att=$var}
是类似的,只是写法上更简单
三、使用Smarty要实现的风格
{mytag item='fields'}
{$fields.f1} -- {$fields.f2} <br />
{/mytag}
要实现上面标签,关键是两点:
1、把 mytag 输出的数组输化为 foreach ($arr as $fields) {
2、把 {/mytag} 替换为 ' } '
{$fields.f1} -- {$fields.f2} 这部份任由默认的smarty编译,它实际上得到的源码应该是
<?php echo $this->_tpl_vars['fields']['f1']; ?> -- <?php echo $this->_tpl_vars['fields']['f2']; ?>
这里是不需要我们去理会的,因此关键还是在1、2两点。
由于这种标签是肯定和block相冲突的,所以这里用前缀对自定义标签分开,这里就用dd吧,不过不能用 dd: 而是用 dd_。
即是:
{dd_mytag item='fields'}
{$fields.f1} -- {$fields.f2} <br />
{/dd_mytag}
首先我们打开 Smarty_Compiler.class.php
找到:function _compile_tag
关键部份是在
default:
if ($this->_compile_compiler_tag($tag_command, $tag_args, $output)) {
return $output;
} else if ($this->_compile_block_tag($tag_command, $tag_args, $tag_modifier, $output)) {
return $output;
} else if ($this->_compile_custom_tag($tag_command, $tag_args, $tag_modifier, $output)) {
return $output;
} else {
$this->_syntax_error("unrecognized tag '$tag_command'", E_USER_ERROR, __FILE__, __LINE__);
}
这里首先对 dd_ 前缀的标签进行拦截
default:
$first = substr($tag_command, 0, 4);
if( preg_match('/^(dd|\/dd)_/', $first) && $this->_compile_myblock_tag($tag_command, $tag_args, $output) ) {
return $output;
} else if ($this->_compile_compiler_tag($tag_command, $tag_args, $output)) {
return $output;
} else if ($this->_compile_block_tag($tag_command, $tag_args, $tag_modifier, $output)) {
return $output;
} else if ($this->_compile_custom_tag($tag_command, $tag_args, $tag_modifier, $output)) {
return $output;
} else {
$this->_syntax_error("unrecognized tag '$tag_command'", E_USER_ERROR, __FILE__, __LINE__);
}
然后 $this->_compile_myblock_tag($tag_command, $tag_args, $output) 就是我们要实现的关键代码了
_compile_myblock_tag函数具体如下:
/**
* 类似dede风格的块标签(块名称需要用 dd_ 开头)
*
* sets $output to the compiled custom compiler tag
* @param string $tag_command
* @param string $tag_args
* @param string $output
* @return boolean
*/
function _compile_myblock_tag($tag_command, $tag_args, &$output)
{
if( substr($tag_command, 0, 1) == '/' )
{
$output = '<?php } ?>';
return true;
}
$found = false;
$have_function = true;
if (isset($this->_plugins['myblock'][$tag_command]))
{
$found = true;
$plugin_func = $this->_plugins['myblock'][$tag_command][0];
if (!is_callable($plugin_func)) {
$message = "compiler function '$tag_command' is not implemented";
$have_function = false;
}
}
else if ($plugin_file = $this->_get_plugin_filepath('myblock', $tag_command))
{
$found = true;
include_once $plugin_file;
$plugin_func = 'smarty_myblock_' . $tag_command;
if (!is_callable($plugin_func)) {
$message = "plugin function $plugin_func() not found in $plugin_file\n";
$have_function = false;
} else {
$this->_plugins['myblock'][$tag_command] = array($plugin_func, null, null, null, true);
}
}
if ($found)
{
if ($have_function)
{
$this->_add_plugin('myblock', $tag_command);
$attrs = $this->_parse_attrs($tag_args);
$_cache_attrs = '';
$arg_list = $this->_compile_arg_list('myblock', $tag_command, $attrs, $_cache_attrs);
$output = '<?php '." \${$tag_command} = smarty_myblock_{$tag_command}(array(".implode(',', $arg_list)."), \$this);\n";
$fieldname = empty($attrs['item']) ? 'field' : $attrs['item'];
$fieldname = preg_replace('/["\']/', '', $fieldname);
$output .= "foreach( \${$tag_command} as \$this->_tpl_vars['key']=>\$this->_tpl_vars['".$fieldname."'] )\n{\n";
$output .= '?>';
} else {
$this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__);
}
return true;
} else {
return false;
}
}
四、新建一个插件进行测试
我们写插件的时候是用 myblock.dd_mytag.php 这样的名称的来命名,里面的函数命名为 smarty_myblock_dd_mytag
例如:
function smarty_myblock_dd_test($_params, &$compiler)
{
$f1 = empty($_params['att2']) ? '' : $_params['att2'];
$f2 = empty($_params['att1']) ? '' : $_params['att1'];
${$_params['item']}[0]['f1'] = $f1;
${$_params['item']}[0]['f2'] = $f1;
${$_params['item']}[1]['f1'] = 'aaa';
${$_params['item']}[1]['f2'] = 'bbb';
return ${$_params['item']};
}
这个函数返回的必须是一个二维数组,当然多更多维也是可以的,前提是你在里面懂得调用它。
把上面文件保存为:plugins/myblock.dd_test.php
OK
在模板写个标签测试一下:
{dd_test item="field" att1='111' att2='222' }
{$field.f1} -- {$field.f2}<br />
{/dd_test}
看到结果:
222--222
aaa--bbb
了吧,呵呵,这正是我们想要的效果。
当然,要在你的项目中实用任意标签调用,就直接写这种形式的插件即可,dedecms的模板引擎是和文档类有关连的,因此它可以在插件里直接调用文档类的一些环境变量,在我们的工程中,如果类写得比较合理的,可以给Smarty增加一个属性,如:var $doc; 把它和文档类关连起来,这样也能从插件里通过 $compiler->doc 来获取文档的相关特性了(如栏目id、文档id等),如果你的类不是那么规范,办法还是用的,可以用静态类来做一个工厂的方法,然后插件里用autoload的方式获得这个类也是可以的,不过你也可以直接用global从外部获取一些参数,但从MVC角度考虑,这样是不科学的,也不得于安全。 |