代码之家  ›  专栏  ›  技术社区  ›  Timo Huovinen

PHP中的类插件?

  •  3
  • Timo Huovinen  · 技术社区  · 15 年前

    我在学习PHP的时候还遇到了一些问题,PHP是否实现了任何内置的插件系统?

    例如,类似的工作原理:

    include 'core.class.php';
    include 'plugin1.class.php';
    include 'plugin2.class.php';
    new plugin2;
    

    哪里

    class core {
      public function coremethod1(){
        echo 'coremethod1';
      }
      public function coremethod2(){
        echo 'coremethod2';
      }
    }
    

    plugin1.class.php包含

    class plugin1 extends core {
      public function coremethod1(){
        echo 'plugin1method1';
      }
    }
    

    plugin2.class.php包含

    class plugin2 extends plugin1 {
      public function coremethod2(){
        echo 'plugin2method2';
      }
    }
    

    include 'core.class.php';
    //include 'plugin1.class.php';
    include 'plugin2.class.php';
    new plugin2;
    

    打破了一切。。。

    有没有合适的方法来做这件事? 如果没有,我可能会考虑搬到一个不同的语言来支持这个…

    编辑: 显然是我的理解不足,所以这里有一个

    plugin2.class.php包含任何内容。。。

    include 'core.class.php';
    include 'plugin1.class.php';
    include 'plugin2.class.php';
    $core = new core;
    $core->coremethod1();//outputs plugin2method1
    

    include 'core.class.php';
    include 'plugin2.class.php';
    $core = new core;
    $core->coremethod1();//outputs plugin1method1
    

    我对任何实现都感兴趣,即使是不涉及类的实现 例如

    include 'core.php';
    //does core stuff
    
    include 'core.php';
    include 'plugin1';
    //does extended core stuff
    
    include 'core.php';
    include 'plugin2';
    //does extended core stuff
    
    
    include 'core.php';
    include 'plugin2';
    include 'plugin1';
    //does very extended core stuff
    

    包含文件需要更改应用程序行为。

    我也不知道这叫什么,所以如果有合适的名字就给我指一下。

    4 回复  |  直到 15 年前
        1
  •  5
  •   Community CDub    8 年前

    如果您唯一担心的是不包括plugin1会产生错误,那么您可以求助于自动加载,让plugin2自动加载plugin1:

    从评论中 PHP Manual on spl_autoload

    // Your custom class dir
    define('CLASS_DIR', 'class/')
    
    // Add your class dir to include path
    set_include_path(get_include_path().PATH_SEPARATOR.CLASS_DIR);
    
    // You can use this trick to make autoloader look 
    // for commonly used "My.class.php" type filenames
    spl_autoload_extensions('.class.php');
    
    // Use default autoload implementation
    spl_autoload_register();
    

    但是,如果您正在寻找类似traits/mixin的特性,那么答案是否定的。至少 not without patching the core these two 不希望在生产代码中使用的API。

    改变对象在运行时的行为的正确方法是使用 Decorators

    $class = new BasicCache( new BasicValidators ( new Basic ) );
    

    Strategy 模式:

    $class = new Basic;
    $class->setStrategy(function() { return 'foo'} );
    echo $class->callStrategy(); // foo
    $class->setStrategy(function() { return 'bar'} );
    echo $class->callStrategy(); // bar
    

    看到了吗 http://sourcemaking.com/design_patterns 最常见的模式。


    下面是一个如何使用decorator创建插件的示例。假设,我们有一个游戏,其中一些非玩家角色在虚拟空间中走动,时不时地和主人公打招呼。他们现在只做这些。不过,我们希望它们的问候方式有所不同,这就是为什么在这个场景中我们需要插件/装饰器的原因。

    首先我们 create an interface 它定义了任何能够问候的对象应该具有的一些方法。我们不关心在特定对象上调用这些方法时它会做什么。我们只想确保这些方法可用,并使用明确定义的输入调用它们:

    interface GreetInterface
    {
        public function greet($name);
        public function setGreeting($greeting);
    }
    

    现在让我们构建非玩家角色类,实现这个接口

    class Dude implements GreetInterface
    {
        protected $greeting = 'hello';
        public function greet($name)
        {
            return sprintf('%s %s', $this->greeting, $name);
        }
        public function setGreeting($greeting)
        {
            $this->greeting = $greeting;
            return $this;
        }
    }
    

    我想那是相当困难的。Dude类只是从接口定义了两个方法。调用greet()时,它将获取存储在greeting中的字符串,并将其前置到传递给greet方法的参数。setGreeting方法允许我们在运行时更改问候语。

    现在来看看插件。我们将创建一个抽象的GreetPlugin类来包含 shared boilerplate code ,只是因为我们不想在实际的插件中复制代码。抽象插件类将实现GreetInterface,因此我们可以确保所有子类也实现该接口。

    既然Dude已经实现了这个接口,我们可以让插件扩展Dude,但这在概念上是错误的,因为扩展会创建一个 关系,但插件不是花花公子。

    abstract class GreetPluginAbstract implements GreetInterface
    {
        protected $inner;
        public function __construct(GreetInterface $inner)
        {
             $this->inner = $inner;
        }
        public function setGreeting($greeting)
        {
            $this->inner->setGreeting($greeting);
            return $this;
        }
        public function greet($name)
        {
            return $this->inner->greet($name);
        }
    }
    

    TypeHint 确保班级履行合同。这是必需的,因为正如您在代码中看到的,我们的插件将需要调用通过构造函数传递的类的接口中的方法。如果我们从“花花公子”扩展到“花花公子”,我们现在就可以把花花公子包装成花花公子了,这有点奇怪。不这么做的另一个原因。

    现在开始第一个插件。我们想让我们的一些人说一个花哨的法国口音,这意味着他们使用ccnt各地,但不能发出一个正确的h。 免责声明:是的,我知道那是陈词滥调。请接受我的例子

    class FrenchPlugin extends GreetPluginAbstract
    {
        public function greet($name) {
           return str_replace(array('h', 'e'), array('', 'é'),
                              $this->inner->greet($name));
        }
    }
    

    class EasyGoingPlugin extends GreetPluginAbstract
    {
        protected $inner;
        public function __construct(GreetInterface $inner) {
             $this->inner = $inner->setGreeting('heya');
             parent::__construct($inner);
        }
    }
    

    这样我们只重写构造函数,因为greet方法应该只返回它将返回的任何内容。所以我们对传递给这个插件的对象调用setGreeting方法。因为对象必须实现GreetInterface,所以我们可以确定这是可行的。

    method chaining

    有了两个插件,我们觉得我们有足够的变化。现在我们只需要一个方便的方法来创造男人。为此,我们创建了这样一个小类:

    class DudeBuilder
    {
         public static function build()
         {
             $dude = new Dude();
             $decorators = func_get_args();
             foreach($decorators as $decorator) {
                 $decorator .= "Plugin";
                 // require_once $decorator;
                 $dude = new $decorator($dude);
             }
             return $dude;
         }
    }
    

    注:我总是混淆建设者和抽象工厂,所以如果上面是一个工厂,那么它是一个工厂。查看我前面给出的设计模式链接;)

    这个生成器所做的一切,就是创建一个普通的dude,然后用我们告诉它使用的插件包装/装饰它,然后返回它。因为构建器不封装自己的状态,所以我们使构建方法是静态的。

    对于本例,我假设您使用了我在顶部给出的自动加载代码。如果没有,可以在foreach循环中包含插件文件。只在需要的时候懒洋洋地加载它们会使加载时间缩短几微秒,把它们都放在上面。希望这也能解释我在各种评论中的意思,当时我认为行为不应该由文件包含控制。文件包含只是必要的。不能使用对PHP不了解的类。但是这个类的实际使用是由我们的代码单独控制的,通过将插件名传递给build方法。

    $regularDude         = DudeBuilder::build();
    $frenchDude          = DudeBuilder::build('French');
    $easygoingDude       = DudeBuilder::build('EasyGoing');
    $frenchEasyGoingDude = DudeBuilder::build('French', 'EasyGoing');
    

    这实际上与执行以下操作相同:

    $regularDude         = new Dude;
    $frenchDude          = new FrenchPlugin(new Dude);
    $easygoingDude       = new EasyGoingPlugin(new Dude);
    $frenchEasyGoingDude = new FrenchPlugin(new EasyGoingPlugin(new Dude));
    

    只需两个插件,我们就可以创建三种类型的花花公子。让他们问候你:

    echo $regularDude->greet('Yuri'), PHP_EOL,
         $frenchDude->greet('Yuri'), PHP_EOL,
         $easygoingDude->greet('Yuri'), PHP_EOL,
         $frenchEasyGoingDude->greet('Yuri'), PHP_EOL;
    
    // gives
    
    hello Yuri
    éllo Yuri
    heya Yuri
    éya Yuri
    

    我们现在可以创建额外的插件来装饰我们的基本类。如果出于某种原因,你决定你的游戏也应该有会说话的马或车,你也可以创建一个类车或马,让它实现问候接口,并为它们添加一个生成器。然后可以重用这些插件来创建法国随和的汽车或马。

        2
  •  6
  •   Peter Bailey    15 年前

    你误用了“插件”这个词。插件通常是一个扩展或改变系统基本功能的代码包 实际的 PHP插件(在PHP世界中称为 你会写C或C++。

    这里所描述的只是将类或类树包含到当前执行中以供使用。在那里 一种“自动”将它们引入当前执行上下文的方法,这是通过 autoload system .

    如果在您阅读了有关自动加载的文档之后,仍然不确定如何继续,请在此处发表评论,我将帮助您。

    编辑

    确切地 你在追求什么。当你执行 new core; 类的实例 core 将被返回-你不能修改它。

    核心 ,然后我想我有一些东西可以为你工作,它可能看起来像这样。

    class core {
      public function coremethod1(){
        echo 'coremethod1';
      }
      public function coremethod2(){
        echo 'coremethod2';
      }
    
      /**
       * @return core
       */
      final public static function create()
      {
        // listed in order of preference
        $plugins = array( 'plugin2', 'plugin1' );
    
        foreach ( $plugins as $plugin )
        {
            if ( class_exists( $plugin ) )
            {
              return new $plugin();
            }
        }
    
        return new self;
      }
    }
    
    class plugin1 extends core {
      public function coremethod1(){
        echo 'plugin1method1';
      }
    }
    
    class plugin2 extends plugin1 {
      public function coremethod2(){
        echo 'plugin2method2';
      }
    }
    
    $core = core::create();
    
    // test what we have
    echo get_class( $core ), '<br>'
       , $core->coremethod1(), '<br>'
       , $core->coremethod2()
    ;
    
        3
  •  2
  •   Adam Hopkinson    15 年前

    核心功能可以通过 override_function

    call_user_func .

    如果你能解释一下你的计划,我们能提供一个更好的答案吗?

        4
  •  0
  •   Jage    15 年前

    您的代码正在中断,因为plugin2扩展了plugin1,而您没有包含plugin1类。为什么不让类plugin2扩展核心呢?这似乎就是你想要的。

    推荐文章