我开始一个新的web应用程序在PHP和这一次,我想创建一些东西,人们可以通过使用插件接口扩展。

如何在他们的代码中编写“钩子”,以便插件可以附加到特定的事件?


当前回答

雅虎的Matt Zandstra有一个叫做Stickleback的项目,它处理了很多PHP插件的处理工作。

它加强了插件类的接口,支持命令行接口,并且安装和运行起来并不太难——特别是如果您阅读了PHP架构师杂志上关于它的封面故事。

其他回答

钩子和监听器方法是最常用的方法,但您还可以做其他事情。取决于你的应用程序的大小,以及你允许谁看到代码(这将是一个自由/开源软件脚本,还是内部的东西)将极大地影响你想要如何允许插件。

Kdeloach有一个很好的例子,但是他的实现和钩子函数有点不安全。我想请你给更多的信息,php应用程序的性质,你写的,以及你如何看待插件适合。

+1给我。

好的建议是看看其他项目是如何做到这一点的。许多人要求安装插件,并为服务注册插件的“名称”(就像wordpress那样),这样在代码中就有了“点”,可以调用一个函数来标识已注册的侦听器并执行它们。一个标准的OO设计模式是观察者模式,在真正面向对象的PHP系统中实现它是一个很好的选择。

Zend Framework使用了许多钩子方法,并且架构非常好。这是一个很好的系统。

雅虎的Matt Zandstra有一个叫做Stickleback的项目,它处理了很多PHP插件的处理工作。

它加强了插件类的接口,支持命令行接口,并且安装和运行起来并不太难——特别是如果您阅读了PHP架构师杂志上关于它的封面故事。

因此,假设您不想要Observer模式,因为它要求您更改类方法来处理侦听任务,并且希望使用一些通用的模式。假设你不想使用扩展继承因为你可能已经在你的类中从其他类继承了。如果有一种通用的方法使任何类都可以不费多大力气就可插入,这不是很棒吗?方法如下:

<?php

////////////////////
// PART 1
////////////////////

class Plugin {

    private $_RefObject;
    private $_Class = '';

    public function __construct(&$RefObject) {
        $this->_Class = get_class(&$RefObject);
        $this->_RefObject = $RefObject;
    }

    public function __set($sProperty,$mixed) {
        $sPlugin = $this->_Class . '_' . $sProperty . '_setEvent';
        if (is_callable($sPlugin)) {
            $mixed = call_user_func_array($sPlugin, $mixed);
        }   
        $this->_RefObject->$sProperty = $mixed;
    }

    public function __get($sProperty) {
        $asItems = (array) $this->_RefObject;
        $mixed = $asItems[$sProperty];
        $sPlugin = $this->_Class . '_' . $sProperty . '_getEvent';
        if (is_callable($sPlugin)) {
            $mixed = call_user_func_array($sPlugin, $mixed);
        }   
        return $mixed;
    }

    public function __call($sMethod,$mixed) {
        $sPlugin = $this->_Class . '_' .  $sMethod . '_beforeEvent';
        if (is_callable($sPlugin)) {
            $mixed = call_user_func_array($sPlugin, $mixed);
        }
        if ($mixed != 'BLOCK_EVENT') {
            call_user_func_array(array(&$this->_RefObject, $sMethod), $mixed);
            $sPlugin = $this->_Class . '_' . $sMethod . '_afterEvent';
            if (is_callable($sPlugin)) {
                call_user_func_array($sPlugin, $mixed);
            }       
        } 
    }

} //end class Plugin

class Pluggable extends Plugin {
} //end class Pluggable

////////////////////
// PART 2
////////////////////

class Dog {

    public $Name = '';

    public function bark(&$sHow) {
        echo "$sHow<br />\n";
    }

    public function sayName() {
        echo "<br />\nMy Name is: " . $this->Name . "<br />\n";
    }


} //end class Dog

$Dog = new Dog();

////////////////////
// PART 3
////////////////////

$PDog = new Pluggable($Dog);

function Dog_bark_beforeEvent(&$mixed) {
    $mixed = 'Woof'; // Override saying 'meow' with 'Woof'
    //$mixed = 'BLOCK_EVENT'; // if you want to block the event
    return $mixed;
}

function Dog_bark_afterEvent(&$mixed) {
    echo $mixed; // show the override
}

function Dog_Name_setEvent(&$mixed) {
    $mixed = 'Coco'; // override 'Fido' with 'Coco'
    return $mixed;
}

function Dog_Name_getEvent(&$mixed) {
    $mixed = 'Different'; // override 'Coco' with 'Different'
    return $mixed;
}

////////////////////
// PART 4
////////////////////

$PDog->Name = 'Fido';
$PDog->Bark('meow');
$PDog->SayName();
echo 'My New Name is: ' . $PDog->Name;

在第1部分中,这就是在PHP脚本顶部的require_once()调用中可能包含的内容。它加载类以使某些东西可插入。

在第2部分中,我们将加载一个类。注意,我不需要对类做任何特殊的操作,这与观察者模式有很大不同。

在第3部分中,我们将类转换为“可插入的”(也就是说,支持让我们重写类方法和属性的插件)。例如,如果你有一个web应用,你可能有一个插件注册表,你可以在这里激活插件。还要注意Dog_bark_beforeEvent()函数。如果我在return语句之前设置$mixed = 'BLOCK_EVENT',它会阻止狗叫,也会阻止Dog_bark_afterEvent,因为不会有任何事件。

在第4部分中,这是正常的操作代码,但请注意,您可能认为会运行的代码根本不是那样运行的。例如,这只狗不会说它的名字是“菲多”,而是“可可”。这只狗不说“喵”,只说“汪”。当你事后想看狗狗的名字时,你会发现它是“Different”而不是“Coco”。第3部分提供了所有这些覆盖。

So how does this work? Well, let's rule out eval() (which everyone says is "evil") and rule out that it's not an Observer pattern. So, the way it works is the sneaky empty class called Pluggable, which does not contain the methods and properties used by the Dog class. Thus, since that occurs, the magic methods will engage for us. That's why in parts 3 and 4 we mess with the object derived from the Pluggable class, not the Dog class itself. Instead, we let the Plugin class do the "touching" on the Dog object for us. (If that's some kind of design pattern I don't know about -- please let me know.)

您可以使用观察者模式。一个简单的功能方法来完成这个:

<?php

/** Plugin system **/

$listeners = array();

/* Create an entry point for plugins */
function hook() {
    global $listeners;

    $num_args = func_num_args();
    $args = func_get_args();

    if($num_args < 2)
        trigger_error("Insufficient arguments", E_USER_ERROR);

    // Hook name should always be first argument
    $hook_name = array_shift($args);

    if(!isset($listeners[$hook_name]))
        return; // No plugins have registered this hook

    foreach($listeners[$hook_name] as $func) {
        $args = $func($args); 
    }
    return $args;
}

/* Attach a function to a hook */
function add_listener($hook, $function_name) {
    global $listeners;
    $listeners[$hook][] = $function_name;
}

/////////////////////////

/** Sample Plugin **/
add_listener('a_b', 'my_plugin_func1');
add_listener('str', 'my_plugin_func2');

function my_plugin_func1($args) {
    return array(4, 5);
}

function my_plugin_func2($args) {
    return str_replace('sample', 'CRAZY', $args[0]);
}

/////////////////////////

/** Sample Application **/

$a = 1;
$b = 2;

list($a, $b) = hook('a_b', $a, $b);

$str  = "This is my sample application\n";
$str .= "$a + $b = ".($a+$b)."\n";
$str .= "$a * $b = ".($a*$b)."\n";

$str = hook('str', $str);
echo $str;
?>

输出:

This is my CRAZY application
4 + 5 = 9
4 * 5 = 20

注:

对于这个示例源代码,您必须在您想要扩展的实际源代码之前声明所有的插件。我已经包含了一个如何处理传递给插件的单个或多个值的示例。其中最困难的部分是编写实际的文档,列出传递给每个钩子的参数。

这只是在PHP中实现插件系统的一种方法。有更好的选择,我建议你查看WordPress文档以获得更多信息。