【问题标题】:Make a script pluginable使脚本可插件化
【发布时间】:2012-01-28 01:16:23
【问题描述】:

我正在编写一个脚本,我需要让它可插入。现在我提供的并且应该对我有用的语法是让它使用类。例如,为了创建一个在达到某个点(钩子)时运行的新插件,您将声明一个新类。我不确定如何在该语法中指定钩子,所以我正在寻找建议。

语法示例:

<?php
class ScriptPlugin
{
    function runPlugin() {} // would be run when the time has to come to execute this plugin
}
?>

另外,如果那个语法不起作用,如果你们能给我一个很好的语法示例,那就太好了。

【问题讨论】:

标签: php


【解决方案1】:

我想到了观察者模式。插件会自行注册并在调用钩子时收到通知。

想到的另一件事是 PHP 中的回调。我想到了similar question alreadyan answer。它显示基于回调的钩子。

观察者模式 运行起来有点短,因为使用钩子时,您经常希望提供参数和返回值之类的东西。使用回调的链接答案也没有这个,所以我写了一个小Hooks 示例类,为注册的回调提供命名的钩子/事件,以及注册你自己的类的方法,例如一个插件。

这个想法很基本:

  • 挂钩有一个名称和零个或多个附加回调。
  • 所有挂钩都在 Hooks 类中进行管理。
  • 主代码通过调用Hooks 对象上的函数来调用挂钩。
  • 插件(和其他类)可以注册自己的回调,这是在Registerable 接口的帮助下完成的。

一个插件和两个钩子的一些示例代码:

<?php
Namespace Addon;

class Hooks
{
    private $hooks = array();
    private $arguments;
    private $name;
    private $return;
    public function __call($name, array $arguments)
    {
        $name = (string) $name;
        $this->name = $name;
        $this->arguments = $arguments;
        $this->return  = NULL;
        foreach($this->getHooks($name) as $hook)
        {
            $this->return = call_user_func($hook, $this);
        }
        return $this->return;
    }
    public function getHooks($name)
    {
        return isset($this->hooks[$name]) ? $this->hooks[$name] : array();
    }
    public function getArguments()
    {
        return $this->arguments;
    }
    public function getName()
    {
        return $this->name;
    }
    public function getReturn()
    {
        return $this->return;
    }
    public function setReturn($return)
    {
        $this->return = $return;
    }
    public function attach($name, $callback)
    {
        $this->hooks[(string) $name][] = $callback;
    }
    public function register(Registerable $plugin)
    {
        $plugin->register($this);
    }
}

interface Registerable
{
    public function register(Hooks $hooks);
}

class MyPlugin implements Registerable
{
    public function register(Hooks $hooks)
    {
        $hooks->attach('postPublished', array($this, 'postPublished'));
        $hooks->attach('postDisplayFilter', array($this, 'filterToUpper'));
    }
    public function postPublished()
    {
        echo "MyPlugin: postPublished.\n";
    }
    public function filterToUpper(Hooks $context)
    {
        list($post) = $context->getArguments();
        return strtoupper($post);
    }
}

$hooks = new Hooks();

$plugin = new MyPlugin();
$hooks->register($plugin);  

$hooks->postPublished();

echo $hooks->postDisplayFilter("Some post text\n");

我这样做是为了防止每个插件必须只有一个具体的基类,因为它想使用钩子。此外,一切都可以注册钩子,唯一需要的是回调。例如一个匿名函数:

$hooks->attach('hookName', function() {echo "Hook was called\n";});

您可以自己创建一个插件基类,例如实现register 函数并自动注册具有特定文档块标签或函数名称的函数

class MyNewPlugin extends PluginSuper
{
    /**
     * @hook postPublished
     */
    public function justAnotherFunction() {}

    public hookPostPublished() {}
}

超类可以利用Reflection 在运行时添加挂钩。然而,反射会减慢速度,并可能使调试变得更加困难。

【讨论】:

  • 这正是我想要的工作方式。我试图实现一个会自动注册一个函数的类,它应该在插件启动时运行,但没有成功。不幸的是,我对 OOP 了解不多 :(
  • 插件需要在钩子上注册它的功能。您可能想在我的答案末尾添加您的代码,以便我查看。
【解决方案2】:

假设一个插件是这样的:

class NewsPlugin extends Plugin
{
  function onCreate($title)
  {
    # Do some stuff
  }
}

然后,当您创建新闻时,您可以在所有注册的插件上调用 onCreate。

【讨论】:

  • 这就是我想要实现的语法。但是,我在查找所有插件时遇到问题。我怎么能这样做?
  • 我通常将它们全部放在一个名为 plugins 的目录中,并使用 glob 将它们全部放在我的标题中。它们都扩展了同一个基类。
  • 如果我可以在 Plugin 类中使用 __construct() 方法,你怎么看,所以当创建一个新实例并基于该类时,它应该以某种方式将 $this 变量传递给插件数组?
  • 当然可以,但是 $plugins 应该是一个全局变量,插件仍然需要构建。
  • 我明白了。另外,即使我在类 Plugin 中添加了 __construct(),我仍然无法运行全局插件函数,可以说“runPlugin()”左右,对吗?
【解决方案3】:

我会为所有可能被调用的钩子创建一个包含函数的基本抽象类。

abstract class Plugin {
    abstract function yourHook();
}

所有插件类都应该继承这个基类,并且会用自己的覆盖这些基函数。

class SomePlugin extends Plugin {
    function yourHook() {
        echo 'yourHook() Called!';
    }
}

现在,当您的程序运行时,您需要找到所有要包含的插件文件,并以某种方式将它们放入一个数组中,例如$plugins。见这篇文章:https://stackoverflow.com/a/599694/362536

foreach (glob("classes/*.php") as $filename)
{
    include $filename;
}

(来自卡斯滕)

定义一个可以从任何东西访问的函数,例如registerPlugin()

function registerPlugin($classname) {
    $plugins[] = new $classname();
}

使每个插件文件的顶行像这样(在类之前):

registerPlugin('SomePlugin');

如果您这样做,您将在 $plugins 中拥有一个数组,其中包含每个插件的实例。在适当的时候,你可以这样做:

foreach ($plugins as $plugin) {
    $plugin->yourHook();
}

作为替代方案,在您的情况下使用interfaces 可能更合适。您应该决定哪种方法最适合您的应用程序。

【讨论】:

  • 一切都很好。这正是我想要实现的。但是,我失败的是,我无法识别插件文件中的类的名称是什么?我不认为要求插件文件与类具有相同的名称,以便知道该类实际具有什么名称,对吗?
  • 我在想一个类似的,但不是同一件事。无论如何这已经足够好了,但我会首先尝试实现我想要做的事情。再打一枪不会花我任何钱,如果我不满意,我会使用这种方法。我要谢谢你!
  • @user726049,看__autoload()php.net/manual/en/language.oop5.autoload.php
猜你喜欢
  • 1970-01-01
  • 2017-04-27
  • 1970-01-01
  • 2018-11-06
  • 2020-08-23
  • 1970-01-01
  • 1970-01-01
  • 2014-05-13
  • 2022-11-29
相关资源
最近更新 更多