我开始一个新的 web 应用程序在 PHP 中,此次想要创建用户可以使用插件接口扩展的某些内容。

请问有关引入其代码编写挂钩,以使插件可以将附加到特定的事件?

2008-08-01 12:50:18
问题评论:

回答:

您可以使用观察者模式。一个简单功能的方法来实现此目的︰

<?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 ";
$str .= "$a + $b = ".($a+$b)." ";
$str .= "$a * $b = ".($a*$b)." ";

$str = hook('str', $str);

echo $str;

?>

输出︰

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

注释︰

此示例的源代码,您必须声明所有您插件之前想要扩展的实际源代码。我提供了如何处理单个或多个值传递给插件的示例。最难的部分编写实际的文档,其中列出了哪些参数传递到每个挂接。

这是完成在 PHP 中的插件系统只是一种方法。有更好的替代方案,我建议您签出 WordPress 文档以了解更多信息。

很抱歉,它显示下划线字符都用 HTML 实体通过减价吗?可以重新发布此代码获取修复此 bug。

编辑︰ Nevermind,它才会显示这种方式编辑时

请注意,对于 PHP > = 5.0 您可以实现此使用观察者 / 使用者在 SPL 中定义的接口︰ php.net/manual/en/class.splobserver.php

Pedantic 注意︰ 这不是一种观察者模式。它是一种Mediator Pattern真正的观察者都仅仅是通知、 没有条件的通知或消息传递 (也没有一个中央管理器用于控制通知)。它没有任何答案错误,但应该注意停止调用操作人员由错误的名称...

因此,让我们说不想观察者模式,因为它需要您更改您的类方法来处理此任务的监听,并需要更通用。并假设您不想使用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 />
";
    }

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


} //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;

在第一部分,这是什么您可能包括通过require_once()调用您的 PHP 脚本顶部。它加载的类使可插拔。

在第二部分,这是我们在其中加载类。请注意,不必执行任何特殊的类,观察者模式显著不同。

在第 3 部分,这是我们在其中切换我们围绕类成为"可插入"(也就是说,让我们重写类的方法和属性的支持插件)。因此,例如,如果您有一个 web 应用程序,可能有插件注册表中,并可以激活以下插件。此外请注意Dog_bark_beforeEvent()函数。如果设置$mixed = 'BLOCK_EVENT'之前返回语句,它将阻止那只狗从 barking 和因为不会有任何事件,也将阻止 Dog_bark_afterEvent。

在第 4,这是正常操作代码,但请注意,您可能认为什么会运行程序不运行类似的根本。例如,狗不宣布它的名称为 Fido,但 Coco。那只狗的不是 meow,但 Woof。当您想要以后看狗的名称,您发现它是不同而不是 Coco。第 3 部分中提供了所有这些重写。

这是如何工作的?好吧,让我们排除eval() (这大家都说是"有害的"),并排除它不是观察者模式。因此,它的工作的方式是狡猾空的类称为 Pluggable,它不包含方法和使用狗类的属性。因此,出现这种情况,因为魔术方法将与我们接洽。这就是为什么在 3 和 4 我们乱动可插拔类,不是狗类自身派生的对象的部分。相反,我们让我们不要"相互接触"狗对象上的插件类。(如果是某种类型的设计模式不了解--请让我知道。)

这不是修饰器吗?

仔细读一下关于此的维基百科,嗯,您说对了 !:)

挂钩侦听器方法是最常用的但您可以执行其他操作。这取决于您的应用程序,和他们的大小允许您将会看到代码 (为此将由一个 FOSS 的脚本,或者家中) 会影响极大地要允许插件的方式。

kdeloach 有一个很好的示例,但其实施和挂钩函数是有点不安全。我会问的您为您书写的 php 应用程序的性质的详细信息并查看插件中管接头方式。

在我为 kdeloach + 1。

下面是我使用过的方法,而是试图复制从 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 挂钩的外观和感觉的方式。通过这种方式甚至可以获得想法您可能意想不到的以前为使更多的 rubust 的事情。

更直接的答案是,编写的一般文件,他们将"include_once"为他们提供他们需要的可用性的文件。这将分成类别并没有提供一个大规模"hooks.php"文件中。要小心,因为什么最终发生时,它们包含的文件会有越来越多的依赖项和功能改进了。试着保持 API 相关性低。他们包括文件即较少。

将 DokuWiki 添加到列表中的系统可能会去看看。它具有丰富的插件体系允许好的事件系统。

请输入您的翻译

Best way to allow plugins for a PHP application

确认取消