代码之家  ›  专栏  ›  技术社区  ›  geocoin

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

  •  258
  • geocoin  · 技术社区  · 17 年前

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

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

    8 回复  |  直到 8 年前
        1
  •  155
  •   community wiki 2 revs, 2 users 86% Kevin    8 年前

    您可以使用观察者模式。实现这一点的简单实用方法:

    <?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文档了解更多信息。

    抱歉,下划线字符被HTML实体替换为标记?当这个bug被修复时,我可以重新发布这个代码。

    编辑:不必担心,只有在编辑时才会出现这种情况。

        2
  •  54
  •   Andrey Rudenko    9 年前

    因此,假设您不希望使用观察者模式,因为它要求您更改类方法来处理监听任务,并且需要一些通用的东西。假设你不想使用 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部分中,这就是我们加载类的地方。注意,我不必对这个类做任何特殊的事情,这与观察者模式有很大的不同。

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

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

    那么这是如何工作的呢?好吧,让我们排除 eval() (每个人都说这是“邪恶的”)并排除它不是观察者模式。因此,它的工作方式是名为Pluggable的鬼鬼祟祟的空类,它不包含dog类使用的方法和属性。因此,既然发生了这种情况,魔法方法将为我们服务。这就是为什么在第3部分和第4部分中,我们把源于可插拔类的对象搞得一团糟,而不是dog类本身。相反,我们让插件类为我们在dog对象上做“触摸”。(如果这是某种我不知道的设计模式——请告诉我。)

        3
  •  34
  •   w-ll    17 年前

    这个 听众 方法是最常用的方法,但还有其他一些事情可以做。取决于你的应用程序的大小,以及你允许谁查看代码(这将是一个FOSS脚本,还是内部的东西)将极大地影响你允许插件的方式。

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

    +从我这里离开。

        4
  •  21
  •   mauris    15 年前

    这是我使用的一种方法,它尝试从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();
    
    ?>
    
        5
  •  17
  •   helloandre    17 年前

    我认为最简单的方法是遵循Jeff自己的建议并查看现有代码。试试看WordPress、Drupal、Joomla和其他著名的基于PHP的CMS,看看它们的API钩子的外观和感觉如何。这样你甚至可以得到你以前没有想到过的想法,让事情变得更加浮夸。

    一个更直接的答案是将他们“包含一次”的常规文件写入他们的文件中,从而提供他们需要的可用性。这将分为多个类别,而不是在一个大的“hooks.php”文件中提供。但是要小心,因为最终会发生的是,它们所包含的文件最终会有越来越多的依赖性和功能性得到改善。尽量降低API依赖性。也就是说,要包含的文件更少。

        6
  •  14
  •   Peter Mortensen icecrime    13 年前

    有一个叫 Stickleback 雅虎的Matt Zandstra负责处理PHP插件的大部分工作。

    它强制执行插件类的接口,支持命令行接口,并且不太难启动和运行-特别是如果您在 PHP architect magazine .

        7
  •  10
  •   THEMike    17 年前

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

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

        8
  •  7
  •   Tim Groeneveld    12 年前

    我很惊讶,这里的大多数答案似乎都是针对Web应用程序本地的插件,即运行在本地Web服务器上的插件。

    如果你想让插件在不同的远程服务器上运行呢?最好的方法是提供一个表单,允许您定义在应用程序中发生特定事件时调用的不同URL。

    不同的事件将根据刚刚发生的事件发送不同的信息。

    通过这种方式,您只需对已提供给应用程序的URL执行curl调用(例如通过https),远程服务器可以根据应用程序发送的信息执行任务。

    这提供了两个好处:

    1. 您不必在本地服务器上托管任何代码(安全性)
    2. 代码可以在远程服务器上(可扩展性),使用不同的语言,而不是PHP(可移植性)。
    推荐文章