允许PHP应用程序插件的最佳方式

2022-08-30 06:06:09

我正在PHP中启动一个新的Web应用程序,这次我想创建一些人们可以使用插件接口进行扩展的东西。

如何将“钩子”写入其代码中,以便插件可以附加到特定事件?


答案 1

您可以使用观察点模式。实现此目的的简单功能方法:

<?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文档以获取更多信息。


答案 2

因此,假设您不想要 Observer 模式,因为它要求您更改类方法来处理侦听任务,并且需要一些通用的东西。假设您不想使用继承,因为您可能已经在类中从其他类继承了继承。有一种通用的方法来使任何类都可以毫不费力地插入,这不是很好吗?操作方法如下:extends

<?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 部分中,我们加载了一个类。请注意,我不必对该类执行任何特殊操作,这与 Observer 模式有很大不同。

在第 3 部分中,我们将类切换为“可插入”(即,支持允许我们重写类方法和属性的插件)。因此,例如,如果您有一个Web应用程序,则可能有一个插件注册表,您可以在此处激活插件。另请注意该函数。如果我在 return 语句之前设置,它将阻止狗吠叫,也会阻止Dog_bark_afterEvent,因为不会有任何事件。Dog_bark_beforeEvent()$mixed = 'BLOCK_EVENT'

在第 4 部分中,这是正常的操作代码,但请注意,您可能认为会运行的内容根本不会像这样运行。例如,狗不会宣布它的名字是“Fido”,而是“Coco”。狗不会说“喵”,而是“呜呜”。当你想事后看看狗的名字时,你会发现它是“不同”而不是“可可”。所有这些覆盖都在第 3 部分中提供。

那么这是如何工作的呢?好吧,让我们排除(每个人都说这是“邪恶的”),并排除它不是观察者模式。因此,它的工作方式是名为Plugable的偷偷摸摸的空类,它不包含Dog类使用的方法和属性。因此,既然发生这种情况,神奇的方法就会吸引我们。这就是为什么在第 3 部分和第 4 部分中,我们弄乱了从 Pluggable 类派生的对象,而不是 Dog 类本身。相反,我们让 Plugin 类为我们“触摸”Dog 对象。(如果这是某种我不知道的设计模式 - 请告诉我。eval()


推荐