Answers:
您可以使用观察者模式。实现此目的的简单功能方法:
<?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文档以获取更多信息。
Mediator Pattern
。真正的观察者纯粹是通知,没有消息传递或有条件的通知(也没有中央管理器来控制通知)。它不会使答案错误,但应注意阻止人们用错误的名字称呼事物……
假设您不希望使用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部分中,您可能会require_once()
在PHP脚本的顶部包含一个调用。它加载类以使其可插入。
在第2部分中,这是我们加载类的地方。注意,我不必为该类做任何特殊的事情,这与Observer模式有很大不同。
在第3部分中,我们将类转换为“可插入”的(即,支持让我们覆盖类方法和属性的插件)。因此,例如,如果您有一个Web应用程序,则可能有一个插件注册表,您可以在此处激活插件。还要注意该Dog_bark_beforeEvent()
功能。如果我$mixed = 'BLOCK_EVENT'
在return语句之前设置,它将阻止狗吠叫,也将阻止Dog_bark_afterEvent,因为不会发生任何事件。
在第4部分中,这是正常的操作代码,但是请注意,您可能认为将要运行的代码根本不会那样运行。例如,狗没有宣布其名字为“ Fido”,而是宣布为“ Coco”。狗不是说“喵”,而是说“哇”。然后,当您想查看狗的名字时,会发现它是“不同的”而不是“可可”。第3部分中提供了所有这些替代。
那么这是如何工作的呢?好吧,让我们排除掉eval()
(每个人都说这是“邪恶”)并排除它不是观察者模式。因此,它的工作方式是一个名为Pluggable的狡猾空类,其中不包含Dog类使用的方法和属性。因此,既然发生了,魔术方法将为我们服务。这就是为什么在第3部分和第4部分中,我们会处理从Pluggable类派生的对象,而不是Dog类本身。相反,我们让Plugin类为我们对Dog对象进行“触摸”。(如果这是我不知道的某种设计模式,请告诉我。)
这是我使用的一种方法,它是尝试从Qt信号/插槽机制复制的一种观察者模式。对象可以发出信号。每个信号在系统中都有一个ID-由发送者的ID +对象名组成。每个信号都可以绑定到接收者,这仅仅是“可调用的”。您可以使用总线类将信号传递给有兴趣接收信号的任何人。发生时,您“发送”信号。下面是示例实现
<?php
class SignalsHandler {
/**
* hash of senders/signals to slots
*
* @var array
*/
private static $connections = array();
/**
* current sender
*
* @var class|object
*/
private static $sender;
/**
* connects an object/signal with a slot
*
* @param class|object $sender
* @param string $signal
* @param callable $slot
*/
public static function connect($sender, $signal, $slot) {
if (is_object($sender)) {
self::$connections[spl_object_hash($sender)][$signal][] = $slot;
}
else {
self::$connections[md5($sender)][$signal][] = $slot;
}
}
/**
* sends a signal, so all connected slots are called
*
* @param class|object $sender
* @param string $signal
* @param array $params
*/
public static function signal($sender, $signal, $params = array()) {
self::$sender = $sender;
if (is_object($sender)) {
if ( ! isset(self::$connections[spl_object_hash($sender)][$signal])) {
return;
}
foreach (self::$connections[spl_object_hash($sender)][$signal] as $slot) {
call_user_func_array($slot, (array)$params);
}
}
else {
if ( ! isset(self::$connections[md5($sender)][$signal])) {
return;
}
foreach (self::$connections[md5($sender)][$signal] as $slot) {
call_user_func_array($slot, (array)$params);
}
}
self::$sender = null;
}
/**
* returns a current signal sender
*
* @return class|object
*/
public static function sender() {
return self::$sender;
}
}
class User {
public function login() {
/**
* try to login
*/
if ( ! $logged ) {
SignalsHandler::signal(this, 'loginFailed', 'login failed - username not valid' );
}
}
}
class App {
public static function onFailedLogin($message) {
print $message;
}
}
$user = new User();
SignalsHandler::connect($user, 'loginFailed', array($Log, 'writeLog'));
SignalsHandler::connect($user, 'loginFailed', array('App', 'onFailedLogin'));
$user->login();
?>
我相信最简单的方法是遵循Jeff的建议并浏览现有代码。尝试查看Wordpress,Drupal,Joomla和其他基于PHP的知名CMS,以了解其API挂钩的外观。这样,您甚至可以获得以前可能没有想到的想法,使事情变得更加混乱。
一个更直接的答案是将它们会“ include_once”的通用文件写入其文件中,从而提供所需的可用性。这将被分为几类,并且不会在一个MASSIVE“ hooks.php”文件中提供。但是要小心,因为最终发生的是它们所包含的文件最终具有越来越多的依赖性,并且功能得到了改善。尽量保持较低的API依赖性。IE包含的文件更少。
有一个叫整洁的项目棘鱼马特Zandstra在雅虎那么多在PHP处理插件工作的手柄。
它实现了插件类的接口,支持命令行接口,并且启动和运行起来并不难-尤其是如果您在PHP Architect杂志上阅读了有关它的封面故事。
好的建议是看看其他项目是如何做到的。许多人要求安装插件并为服务注册它们的“名称”(就像wordpress一样),因此您在代码中有“点”,您可以在其中调用标识已注册的侦听器并执行它们的函数。一个标准的OO设计模式是Observer Pattern,这是在真正的面向对象的PHP系统中实现的一个不错的选择。
在Zend框架使用了诸多挂钩的方法,并且非常漂亮架构。那将是一个很好的系统。
令我惊讶的是,这里的大多数答案似乎都针对Web应用程序本地的插件,即在本地Web服务器上运行的插件。
如果您希望插件在其他远程服务器上运行怎么办?最好的方法是提供一种表格,该表格允许您定义在应用程序中发生特定事件时将调用的不同URL。
不同的事件将基于刚刚发生的事件发送不同的信息。
这样,您只需要对提供给应用程序的URL(例如通过https)执行cURL调用,远程服务器就可以根据应用程序发送的信息执行任务。
这提供了两个好处: