我正在使用一个用于导出模板的PHP类,并且有一个用于调用函数的标记,这些函数应该从插件脚本中动态加载。脚本存储在一个目录中,并以标记中函数的名称命名。然后,动态加载的函数的名称被存储在一个数组中,因此它们只加载一次。这很好,所以我的问题不是如何解决这个问题,而是我想询问一下这是否是一个稳定和安全的解决方案,是否有更好的方法来解决这个问题?
无论是观察者还是装饰者图案都不适用于这种设计,我希望它保持极简主义;在这种特殊情况下,我不会使用类,只使用函数。此外,在你看来,如果这是一个有效的解决方案,或者你提出了更好的建议;我可能也会在其他类似的类中实现它——因此我希望从一开始就把它做好。所以,请严格批评。
这个类看起来像这样:
<?php
// TemplateEngine.php
class TemplateEngine {
private static $functions = array();
private $vars = array();
...
public function &getVars() { return $this->vars; }
...
private function call(&$tag) {
$name = strtr(strtolower($tag->id),'-','_');
$value = $tag->value;
$func = false;
if(preg_match('/^['w'_]+$/',$name)) {
if(isset(self::$functions[$name])) {
$func = &self::$functions[$name];
} else {
$script = __DIR__.'/functions/'.$name.'.php';
if(file_exists($script)) $func = require_once $script;
self::$functions[$key] = &$func;
}
}
$ret = '';
if(is_callable($func)) $ret = $func($this,$value);
return $ret;
}
...
}
然后,迭代数组并用其值解析字符串的函数脚本如下所示:
<?php
// functions/iterate.php
return function(&$templ,$param) {
$vars = &$templ->getVars();
list($key,$value) = $param;
if(!isset($vars[$key])) return '';
$var = $vars[$key];
if(!is_array($var)) $var = array($key=>$var);
$value = stripcslashes($value);
$ret = '';
foreach($var as $k=>$v) {
$vars['key'] = $k;
$vars['value'] = $v;
$ret .= $templ->parse($value);
}
return $ret;
}
在模板中,标签可能看起来像这样:
{call iterate|js-files|<script src="{value}">'n}
这样的标签将调用函数"iterate",并在模板变量数组(从配置文件中读取)中查找索引"js files",并解析字符串"<script…>"。
一种方法(就像我在PHP模板引擎Contemplate中所做的那样)是:
在PHP模板类中有一个数组,用于存储插件函数(及其名称),然后使用PHP __call
魔术方法调用用户定义的插件(使用call_user_func),如果它是插件表中的名称函数,则调用名为的方法(因为它将是本机方法)
注意:这有点慢(使用__call
作为委托),但这是PHP(5.1+)中动态方法的最佳解决方案
用户可以在模板引擎中添加一个插件,如下所示(例如):
public function addPlugin($name, $handler)
{
$this->plugins[$name] = $handler;
}
示例用法:
MyTemplateEngine->addPlugin('foo', function(){ return 'foo'; });
调用插件:
MyTemplateEngine->foo();