将函数作为插件动态添加到PHP类中


Dynamically add functions to PHP class as plugins

我正在使用一个用于导出模板的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();