我在学习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)
包含文件需要更改应用程序行为.因为它有任何意义.
我也不知道这叫什么,所以如果有的话,请指出正确的命名.
您滥用术语"插件".插件通常是一个扩展或改变系统基本功能的代码包 - 用于创建实际的 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)
如果您唯一担心的是不包括plugin1会产生错误,那么您可以使用自动加载来自动加载plugin2加载插件1:
// 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汽车或马匹.