PHP中的类插件?

Tim*_*nen 3 php plugins class

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

所以插件将能够改变核心组件的行为.

例如这样的工作:

include 'core.class.php';
include 'plugin1.class.php';
include 'plugin2.class.php';
new plugin2;
Run Code Online (Sandbox Code Playgroud)

其中core.class.php包含

class core {
  public function coremethod1(){
    echo 'coremethod1';
  }
  public function coremethod2(){
    echo 'coremethod2';
  }
}
Run Code Online (Sandbox Code Playgroud)

plugin1.class.php包含

class plugin1 extends core {
  public function coremethod1(){
    echo 'plugin1method1';
  }
}
Run Code Online (Sandbox Code Playgroud)

plugin2.class.php包含

class plugin2 extends plugin1 {
  public function coremethod2(){
    echo 'plugin2method2';
  }
}
Run Code Online (Sandbox Code Playgroud)

如果不是因为现在插件彼此可靠并且删除其中一个插件的问题,这将是理想的:

include 'core.class.php';
//include 'plugin1.class.php';
include 'plugin2.class.php';
new plugin2;
Run Code Online (Sandbox Code Playgroud)

打破了整个事情......

有没有适当的方法来做到这一点?如果没有,我可以考虑转移到另一个支持这个的语言...

谢谢你的帮助.

编辑: 显然我的理解是缺乏的,所以这里是一个澄清的尝试.

core.class.php包含任何内容......

plugin1.class.php包含任何内容......

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

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

然而:

include 'core.class.php';
include 'plugin2.class.php';
$core = new core;
$core->coremethod1();//outputs plugin1method1
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

包含文件需要更改应用程序行为.因为它有任何意义.

我也不知道这叫什么,所以如果有的话,请指出正确的命名.

Pet*_*ley 6

您滥用术语"插件".插件通常是一个扩展或改变系统基本功能的代码包 - 用于创建实际的 PHP插件(在PHP世界中称为扩展),您将编写C或C++.

你在这里描述的只是将类或类树包含在当前执行中以供使用.还有就是一个办法把他们带入当前执行上下文"自动",并通过这就是自动装载系统.

如果在您阅读了自动加载文档后,您仍然不确定如何继续前进,请在此处发表评论,我会帮助您.

编辑

好的,我知道你在追求什么.你无法完全按照自己的意愿去做.当您执行new core;该类的实例时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()
;
Run Code Online (Sandbox Code Playgroud)


Gor*_*don 5

如果您唯一担心的是不包括plugin1会产生错误,那么您可以使用自动加载来自动加载plugin2加载插件1:

来自spl_autoload上PHP手册中的注释

// 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();
Run Code Online (Sandbox Code Playgroud)

但是,如果您正在寻找类似特征/ mixin的功能,那么答案就是否定.PHP目前不支持此功能.至少在没有修补核心或使用 两个 API的情况下,您不希望在生产代码中使用它们.

更改对象在运行时的行为方式的正确方法是使用装饰器:

$class = new BasicCache( new BasicValidators ( new Basic ) );
Run Code Online (Sandbox Code Playgroud)

策略模式:

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

有关最常见的模式,请参见http://sourcemaking.com/design_patterns.


编辑以下是如何使用装饰器创建插件的示例.假设,我们有一种类型的游戏,其中一些非玩家角色在虚拟空间中四处走动并不时地迎接主角.这就是他们现在所做的一切.我们想要他们如何问候一些变化,这就是我们在这种情况下需要我们的插件/装饰器的原因.

首先,我们创建一个接口,定义任何能够问候的对象应该具有的方法.我们不关心在特定对象上调用这些方法时它的作用.我们只是想确保方法可用,并且使用明确定义的输入调用它们:

interface GreetInterface
{
    public function greet($name);
    public function setGreeting($greeting);
}
Run Code Online (Sandbox Code Playgroud)

接口基本上是任何实现对象必须满足的合同.在我们的案例中,合同说,如果你是一个可以迎接的对象,你必须有两种方法.以您喜欢的方式实现它们,但有这些方法.

现在让我们构建我们的非玩家角色类,实现这个界面

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我猜这是非常直截了当的.Dude类只是从接口定义了两个方法.当调用greet()时,它将获取存储在greeting中的字符串,并将前传到传递给greet方法的param.setGreeting方法允许我们在运行时更改问候语.注意:你也可以添加一个getter(我只是懒惰)

现在来看插件.我们将创建一个抽象的GreetPlugin类来包含一些共享的样板代码,因为我们不想在实际的插件中复制代码.抽象插件类将实现GreetInterface,因此我们可以确保所有子类也实现接口.

由于Dude已经实现了接口,我们可以让插件扩展Dude,但这在概念上是错误的,因为扩展创建了一个is-a关系,但插件不是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);
    }
}
Run Code Online (Sandbox Code Playgroud)

初始化时,插件类接受一个参数:任何实现GreetInterface的类.该TypeHint确保,类履行合同.这是必需的,因为正如你在代码中看到的那样,我们的插件需要在通过构造函数传递的类的接口中调用方法.如果我们从Dude延伸出来,我们现在可以将家伙包装成男人,这有点奇怪.不这样做的另一个原因.

现在转到第一个插件.我们希望我们的一些家伙能说一种华丽的法国口音,这意味着他们会在整个地方使用,但是不能说出合适的h.免责声明:是的,我知道这是陈词滥调.请忍受我的例子

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

由于插件扩展了抽象插件,我们现在可以专注于修改常规家伙如何进行问候的实际代码.当调用greet()时,我们在包装元素上调用greet(),然后删除所有h字符并将所有es转换为és.其他一切都是未经修改的抽象行为.

在另一个插件中,我们想要改变问候语的措辞,所以我们有一些人说Heya,而不仅仅是Hello.只是添加一些变化.

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

这样我们只重写构造函数,因为greet方法应该只返回它将会是什么.所以我们在传递给这个插件的对象上调用setGreeting方法.因为对象必须实现GreetInterface,所以我们可以确定这是有效的.

请注意,我将setGreeting的返回值指定为内部对象.这是可能的,因为每当调用setMethod时我都返回$ this.这不能通过界面强制执行,因此您不能依赖此表单的界面.我刚刚添加它来展示另一种技术:方法链接.

完成两个插件后,我们觉得我们有足够的变化.现在我们只需要一种方便的方法来创建Dudes.为此我们创建一个这样的小类:

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;
     }
}
Run Code Online (Sandbox Code Playgroud)

注意:我总是混淆Builder和AbstractFactory,所以如果上面是Factory,那么它就是一个工厂.看看我之前给出的设计模式链接;)

所有这些构建器都是,创建一个普通的家伙,然后将它包装/装饰到/我们告诉它使用的任何插件,而不是返回它.因为构建器不封装自己的状态,所以我们使构建方法保持静态.

对于这个例子,我假设您使用了我在右上方给出的自动加载代码.如果没有,您可以在foreach循环中包含插件文件.只有在需要时才加载它们会使加载时间缩短几微秒,而不是将它们全部放在最上面.希望这也解释了我在各种评论中的含义,当时我认为行为不应该由文件包含控制.文件包含只是必需品.您不能使用PHP不了解的类.但实际使用该类,仅由我们的代码控制,通过将插件名称传递给构建方法.

我们现在就这样做

$regularDude         = DudeBuilder::build();
$frenchDude          = DudeBuilder::build('French');
$easygoingDude       = DudeBuilder::build('EasyGoing');
$frenchEasyGoingDude = DudeBuilder::build('French', 'EasyGoing');
Run Code Online (Sandbox Code Playgroud)

这实际上与:

$regularDude         = new Dude;
$frenchDude          = new FrenchPlugin(new Dude);
$easygoingDude       = new EasyGoingPlugin(new Dude);
$frenchEasyGoingDude = new FrenchPlugin(new EasyGoingPlugin(new Dude));
Run Code Online (Sandbox Code Playgroud)

只需两个插件,我们现在可以创建三种类型的Dudes.让他们问候你:

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
Run Code Online (Sandbox Code Playgroud)

我们现在可以创建额外的插件来装饰我们的基本类.如果出于某种原因,你决定你的游戏也应该有说话的马或汽车,你也可以创建一个类Car或Horse并让它实现greet接口并为它们添加一个Builder.然后,您可以重复使用插件来创建法国EasyGoing汽车或马匹.