我开始一个新的web应用程序在PHP和这一次,我想创建一些东西,人们可以通过使用插件接口扩展。
如何在他们的代码中编写“钩子”,以便插件可以附加到特定的事件?
我开始一个新的web应用程序在PHP和这一次,我想创建一些东西,人们可以通过使用插件接口扩展。
如何在他们的代码中编写“钩子”,以便插件可以附加到特定的事件?
当前回答
好的建议是看看其他项目是如何做到这一点的。许多人要求安装插件,并为服务注册插件的“名称”(就像wordpress那样),这样在代码中就有了“点”,可以调用一个函数来标识已注册的侦听器并执行它们。一个标准的OO设计模式是观察者模式,在真正面向对象的PHP系统中实现它是一个很好的选择。
Zend Framework使用了许多钩子方法,并且架构非常好。这是一个很好的系统。
其他回答
因此,假设您不想要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.)
这是我使用的一种方法,它试图复制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();
?>
好的建议是看看其他项目是如何做到这一点的。许多人要求安装插件,并为服务注册插件的“名称”(就像wordpress那样),这样在代码中就有了“点”,可以调用一个函数来标识已注册的侦听器并执行它们。一个标准的OO设计模式是观察者模式,在真正面向对象的PHP系统中实现它是一个很好的选择。
Zend Framework使用了许多钩子方法,并且架构非常好。这是一个很好的系统。
我很惊讶,这里的大多数答案似乎都是关于web应用程序的本地插件,即运行在本地web服务器上的插件。
如果你想让插件在另一个远程服务器上运行呢?最好的方法是提供一个表单,允许您定义不同的url,当应用程序中发生特定事件时将调用这些url。
不同的事件会根据刚刚发生的事件发送不同的信息。
这样,您只需对提供给应用程序的URL(例如通过https)执行cURL调用,远程服务器可以根据应用程序发送的信息执行任务。
这提供了两个好处:
您不必在本地服务器上托管任何代码(安全性) 代码可以在远程服务器上(可扩展性),使用PHP以外的不同语言(可移植性)
雅虎的Matt Zandstra有一个叫做Stickleback的项目,它处理了很多PHP插件的处理工作。
它加强了插件类的接口,支持命令行接口,并且安装和运行起来并不太难——特别是如果您阅读了PHP架构师杂志上关于它的封面故事。