我已经创建了一个插件,当然我是我自己,我想采用一种不错的面向对象方法。现在我要做的是创建该类,然后在下面创建该类的实例:

class ClassName {

    public function __construct(){

    }
}

$class_instance = new ClassName();  


我假设还有一种更多的WP方式开始这一课,然后我碰到人们说他们比init()更喜欢具有__construct()函数。同样,我发现一些人在使用以下钩子:

class ClassName {

    public function init(){

    }
}
add_action( 'load-plugins.php', array( 'ClassName', 'init' ) );


通常被认为是在加载时创建WP类实例并将其作为全局对象的最佳方法是什么?注意:作为一个有趣的观点,我注意到,虽然可以从register_activation_hook()内调用__construct,但不能使用第二个示例从init()内调用它。

编辑:感谢所有的回答,关于如何在类本身中处理初始化问题,显然存在很多争论,但我认为通常存在一个相当好的共识,认为add_action( 'plugins_loaded', ...);是实际启动它的最佳方法...

编辑:只是为了混淆问题,我也看到过这种用法(尽管我自己不会使用此方法,因为将漂亮的OO类转换为函数似乎无法解决问题):

// Start up this plugin
add_action( 'init', 'ClassName' );
function ClassName() {
    global $class_name;
    $class_name = new ClassName();
}


评论

关于上一次编辑,如果该编辑与该类包含在同一个插件文件中,它将变得毫无用处。您也可以按照我描述的方法实例化该类。即使将其放在单独的文件中,它也仍然毫无意义。我可以看到的唯一用例是,是否要创建包装函数,使您可以在插件文件之外,主题内等实例化类。即便如此,我还是要问其背后的逻辑是什么,因为正确使用条件和钩子应该可以对实例进行精细控制,从而使您可以专注于使用插件。

我对此表示同意,但是我认为值得一提,因为我在几个WP插件中找到了它。

#1 楼

好的问题,有很多方法,这取决于您要实现的目标。

我经常这样做;

add_action( 'plugins_loaded', array( 'someClassy', 'init' ));

class someClassy {

    public static function init() {
        $class = __CLASS__;
        new $class;
    }

    public function __construct() {
           //construct what you see fit here...
    }

    //etc...
}


更多WPSE成员toscho在这个要点中可以看到一个完整的深入示例,该示例是由于最近在聊天室中对该主题进行的一些讨论而产生的。

空的构造方法。

以下摘录自上述要点,总结了空构造器方法的全部优点/缺点。




优点:


单元测试可以创建新实例,而无需自动激活任何挂钩
。无需Singleton。
不需要全局变量。
想要使用插件实例的人都可以调用
T5_Plugin_Class_Demo :: get_instance()。
易于停用。
仍然是真实的面向对象操作:没有任何工作方法是静态的。






也许读起来更难?






我认为缺点是缺点,这就是为什么它必须成为我的首选方法,而不是我唯一使用的方法。实际上,其他几个重量级人物无疑很快就会在这个主题上引起关注,因为围绕这个主题有一些好的意见值得提出。


注意:我需要从toscho中找到要点示例,该示例经过3或4次比较,以了解如何实例化插件中的类的方法,从而考察了每种方法的优缺点,上面的链接是完成此操作的首选方法,但其他示例与该主题形成了很好的对比。希望toscho仍然有该文件。

注:WPSE对此主题的答案以及相关示例和比较。也是例如WordPress中的类的最佳解决方案。

add_shortcode( 'baztag', array( My_Plugin::get_instance(), 'foo' ) );
class My_Plugin {

    private $var = 'foo';

    protected static $instance = NULL;

    public static function get_instance() {

        // create an object
        NULL === self::$instance and self::$instance = new self;

        return self::$instance; // return the object
    }

    public function foo() {

        return $this->var; // never echo or print in a shortcode!
    }
}


评论


add_action('plugins_loaded',...)之间有什么区别?和add_action('load-plugins.php',...);我举的例子使用了后者

– Kalpaitch
2012-10-22 9:46

据我了解,虽然load-plugins.php可以工作,但它与核心update.php文件相关联,并且不属于通常的默认操作的一部分,当涉及初始化期间触发的事件序列时,应该依赖该默认默认操作原因是我更喜欢使用确实适用的钩子,在这种情况下为plugins_loaded。我经常将其称为“动作参考”发生时的快速快照。我的解释还不完整。

–亚当
2012年10月22日10:00

我喜欢这种单例方法。但是,我质疑使用plugins_loaded作为您的初始化动作挂钩。该挂钩应在所有插件加载后运行。通过钩住它,您就可以劫持该钩子,并且可能会陷入与其他插入到plugins_loaded的插件或主题的冲突或启动顺序问题。我不会执行任何操作来运行您的初始化方法。插件体系结构设计为内联运行,而不是在操作上运行。

–汤姆·俄格(Tom Auger)
2012年10月23日在21:25



请注意,如果使用register_activation_hook(),则需要在触发plugins_loaded操作之前调用该函数。

– Geert
2012年10月31日在9:52

作为其他信息,请参阅来自@mikeschinkel的帖子和评论中的dicus。 hardcorewp.com/2012/…

– Bueltge
13年1月3日,11:24

#2 楼

在提出原始问题后正好两年后到达这里,我想指出几点。 (永远不要问我指出很多事情)。

正确的钩子

要实例化插件类,应使用适当的钩子。并没有通用的规则,因为它取决于类的作用。

使用像"plugins_loaded"这样的非常早的钩子通常没有任何意义,因为这样的钩子是为管理员触发的,前端和AJAX请求,但通常情况下,使用以后的钩子要好得多,因为它只允许在需要时实例化插件类。可以在"template_redirect"上实例化一个为模板做事的类。

通常来说,在解雇"wp_loaded"之前需要实例化一个类的情况很少见。

没有上帝类

在较早的答案中用作示例的所有类中的大多数类都使用名为"Prefix_Example_Plugin""My_Plugin"之类的类...这表明该插件可能存在一个主类。

好吧,除非一个插件是由一个类创建的(在这种情况下,以插件名称命名是绝对合理的),否则要创建一个管理整个插件的类(例如,添加插件所需的所有钩子或实例化所有其他钩子)插件类)可以视为不当行为,例如以上帝对象为例。

在面向对象的编程中,代码应倾向于SOLID “ S”代表“单一责任原则”。

这意味着每个班级都应该做一件事情。在WordPress插件开发中,这意味着开发人员应避免使用单个钩子来实例化主插件类,但根据类职责,应使用不同的钩子来实例化不同的类。

避免使用钩子构造函数

此参数已在此处的其他答案中引入,但是我想对此概念进行说明,并将此其他答案链接到在单元测试的范围内已得到相当广泛解释的地方。

几乎2015年:PHP 5.2是适用于僵尸

自2014年8月14日起,PHP 5.3寿终正寝。它肯定已经死了。
PHP 5.4将在2015年全年得到支持,这意味着我正在写作的这一年又是一年。

但是WordPress仍然支持PHP 5.2,但没有应该编写一行支持该版本的代码,尤其是当代码是OOP时。

有不同的原因:


PHP 5.2死了很长时间以前,还没有针对它的安全修复程序发布,这意味着它不安全
PHP 5.3为PHP添加了很多功能,匿名函数和名称空间überalles

较新版本的PHP是快很多。 PHP是免费的。免费更新。如果可以免费使用速度更快,更安全的版本,为什么还要使用速度较慢,不安全的版本?

如果您不想使用PHP 5.4+代码,请至少使用5.3+

示例

现在是时候根据我在这里所说的内容来回顾较旧的答案。

一旦我们不再关心5.2, ,我们可以并且应该使用名称空间。

为了更好地解释单一职责原则,我的示例将使用3个类,一个在前端做某事,一个在后端做事,第三个使用在这两种情况下。

管理类:

namespace GM\WPSE\Example;

class AdminStuff {

   private $tools;

   function __construct( ToolsInterface $tools ) {
     $this->tools = $tools;
   }

   function setup() {
      // setup class, maybe add hooks
   }

}


前端类:

namespace GM\WPSE\Example;

class FrontStuff {

   private $tools;

   function __construct( ToolsInterface $tools ) {
     $this->tools = $tools;
   }

   function setup() {
      // setup class, maybe add hooks
   }

}


工具界面:

namespace GM\WPSE\Example;

interface ToolsInterface {

   function doSomething();

}


还有一个工具类,供其他两个使用:

namespace GM\WPSE\Example;

class Tools implements ToolsInterface {

   function doSomething() {
      return 'done';
   }

}


有了此类,我可以使用适当的钩子实例化它们。像这样的东西:

require_once plugin_dir_path( __FILE__ ) . 'src/ToolsInterface.php';
require_once plugin_dir_path( __FILE__ ) . 'src/Tools.php';

add_action( 'admin_init', function() {

   require_once plugin_dir_path( __FILE__ ) . 'src/AdminStuff.php';
   $tools = new GM\WPSE\Example\Tools;
   global $admin_stuff; // this is not ideal, reason is explained below
   $admin_stuff = new GM\WPSE\Example\AdminStuff( $tools ); 
} );

add_action( 'template_redirect', function() {

   require_once plugin_dir_path( __FILE__ ) . 'src/FrontStuff.php';
   $tools = new GM\WPSE\Example\Tools;
   global $front_stuff; // this is not ideal, reason is explained below
   $front_stuff = new GM\WPSE\Example\FrontStuff( $tools );    
} );


依赖倒置和依赖注入

在上面的示例中,我使用了名称空间和匿名函数在不同的钩子上实例化不同的类,将上面所说的内容付诸实践。
我应用了上面间接提到的另一个概念:依赖注入,这是一种应用依赖倒置原理的方法,即SOLID的首字母缩写“ D”。

Tools类是“注入”的在其他两个类中实例化它们时,可以通过这种方式将职责分开。此外,AdminStuffFrontStuff类使用类型提示来声明它们需要实现ToolsInterface的类。 br />
通过这种方式,我们自己或使用我们代码的用户可以使用同一接口的不同实现,从而使我们的代码不耦合到具体的类,而是耦合到抽象:这正是依赖关系反转原理的本质。 br />
但是,上面的例子可以进一步改善。让我们看看如何。

Autoloader

编写更好的可读性OOP代码的一个好方法是不要将类型(接口,类)定义与其他代码混合,并放置每种类型

此规则也是PSR-1编码标准之一。

但是,这样做之前,在能够使用一个类之前,需要要求包含该类的文件。

这可能会让人不知所措,但是PHP提供了自动使用实用程序功能的功能。

使用名称空间变得非常容易,因为现在可以将文件夹结构与名称空间结构进行匹配。

这不仅是可能的,而且是另一个PSR标准(或者更好的是2:现在已弃用PSR-0,而PSR-4)。

遵循该标准可以使用处理自动加载的不同工具,而不必编写自定义自动加载器。

我不得不说WordPress编码标准对文件的命名有不同的规则。

因此,当为WordPress核心编写代码时,开发人员必须遵循WP规则,但是在编写自定义代码时,这是开发人员的选择,但是使用PSR标准更易于使用已编写的工具2。

全局访问,注册表和服务定位器模式。

在WordPress中实例化插件类时最大的问题之一是如何从代码的各个部分访问它们。

WordPress本身使用全局方法:变量保存在全局范围内,使它们可在任何地方访问。每个WP开发人员在职业生涯中都会键入global一词。

这也是我在上面的示例中使用的方法,但这是邪恶的。已经太久了,无法让我进一步解释原因,但是阅读SERP中“全局变量邪恶”的第一个结果是一个很好的起点。

但是如何避免全局变量呢?

有不同的方法。

这里的一些较旧的答案使用静态实例方法。

public static function instance() {

  if ( is_null( self::$instance ) ) {
    self::$instance = new self;
  }

  return self::$instance;
}


这很简单,也很不错,但是它会强制为我们要访问的每个类实现该模式。

此外,这种方法很多时候都落在了神类问题上,因为开发人员使用此方法使主类可访问,然后使用它来访问所有其他类。

我已经解释了God类有多糟糕,因此静态实例方法是一个好方法一个插件只需要m可以访问一两个类。

这并不意味着它只能用于只有几个类的插件,实际上,当正确使用依赖项注入原理时,可能创建非常复杂的应用程序而无需全局访问大量对象。

但是,有时插件需要使某些类可访问,在这种情况下,静态实例方法不堪重负。

另一种可能的方法是使用注册表模式。

这是它的一个非常简单的实现:

namespace GM\WPSE\Example;

class Registry {

   private $storage = array();

   function add( $id, $class ) {
     $this->storage[$id] = $class;
   }

   function get( $id ) {
      return array_key_exists( $id, $this->storage ) ? $this->storage[$id] : NULL;
   }

}


使用此类,可以通过id将对象存储在注册表对象中,因此可以访问注册表有权访问所有对象。当然,当第一次创建对象时,需要将其添加到注册表中。

示例:

global $registry;

if ( is_null( $registry->get( 'tools' ) ) ) {
  $tools = new GM\WPSE\Example\Tools;
  $registry->add( 'tools', $tools );
}

if ( is_null( $registry->get( 'front' ) ) ) {
  $front_stuff = new GM\WPSE\Example\FrontStuff( $registry->get( 'tools' ) );    
  $registry->add( 'front', front_stuff );
}

add_action( 'wp', array( $registry->get( 'front' ), 'wp' ) );


上面的示例明确表明,要使注册表有用,需要全局访问。唯一注册表的全局变量不是很坏,但是对于非全局纯文本的注册表,可以为注册表实现静态实例方法,或者可以为带有静态变量的函数实现:

function gm_wpse_example_registry() {
  static $registry = NULL;
  if ( is_null( $registry ) ) {
    $registry = new GM\WPSE\Example\Registry;
  }
  return $registry;
}


第一次调用该函数时,它将实例化注册表,在后续调用中,它将仅返回它。过滤器中的对象实例。这样的事情:

$registry = new GM\WPSE\Example\Registry;

add_filter( 'gm_wpse_example_registry', function() use( $registry ) {
  return $registry;
} );


之后,到处都需要注册表:

$registry = apply_filters( 'gm_wpse_example_registry', NULL );


另一个可以使用的是服务定位器模式。它类似于注册表模式,但是服务定位器使用依赖注入传递给各种类。

这种模式的主要问题是它隐藏了类依赖关系,使得代码难以维护和读取。

DI容器

无论用于使注册表或服务定位器可全局访问的方法是什么,都必须将对象存储在其中,并且在存储之前必须实例化它们。
在复杂的应用程序中,有很多类并且其中许多类具有多个依赖项,实例化类需要大量代码,因此存在错误的可能性会增加:不存在的代码不会包含错误。

近年来,出现了一些PHP库,可帮助PHP开发人员轻松实例化和存储对象实例,自动解决其依赖关系。

该库被称为依赖注入容器,因为它们具有强大的功能实例化类以解决依赖关系,并存储对象并在需要时返回它们,其作用类似于注册表对象。

通常,当使用DI容器时,开发人员必须为应用程序的每个类设置依赖关系,然后第一次在代码中需要一个类时,将使用适当的依赖关系对其进行实例化,并在后续请求中一次又一次返回同一实例。

一些DI容器也能够自动可以在没有配置的情况下使用PHP反射来发现依赖关系。

一些著名的DI容器是:
PHP DI

还有很多其他。

我想指出的是,对于简单的插件,只涉及很少的类,并且类没有太多的依赖关系,可能不值得使用DI容器:静态实例方法或全局可访问注册表是很好的解决方案,但对于复杂的插件,DI容器的好处显而易见。

当然,甚至DI容器对象必须可访问才能在应用程序中使用,并且为此目的,可以使用上述方法之一,全局变量,静态实例变量,通过过滤器返回对象等。

Composer

使用DI容器通常意味着使用第三方代码。如今,在PHP中,当我们需要使用一个外部库(不仅是DI容器,而且是不属于应用程序的任何代码)时,简单地下载它并将其放在我们的应用程序文件夹中并不是一种好习惯。即使我们是另一段代码的作者。

将应用程序代码与外部依赖项分离是更好的组织,更好的可靠性和更好的代码完整性的标志。

Composer是PHP社区中事实上的标准,用于管理PHP依赖项。它也不是WP社区中的主流,它是每个PHP和WordPress开发人员至少应该知道的工具(如果不使用的话)。在此处还讨论Composer可能不在主题之列,仅为了完整性起见才提及该主题。 />

1 PSR是由PHP Framework Interop Group发布的PHP标准规则。

2 Composer(此答案中将提及的库)还包含一个自动装带器实用程序。

评论


另外,PHP 5.3也将终止。如果不是默认主机,负责的主机将至少提供5.4作为选择

– Tom J Nowell♦
14-10-27在10:55

“自2014年8月14日起,PHP 5.3寿终正寝。它肯定已经死了。”是“ PHP 5.2适用于僵尸”下的第一行@TomJNowell

– gmazzap♦
14-10-27在11:50

只是想知道,您会指出“很多东西”是什么样子? ;-)

– MikeSchinkel
2015年12月25日在9:15

为了避免全局变量,我喜欢带有功能和静态变量的注册表模式。第一次调用该函数将实例化注册表,在随后的调用中它将仅返回它。

–迈克尔·埃克伦德(Michael Ecklund)
17年8月11日在16:00

#3 楼

我使用以下结构:

Prefix_Example_Plugin::on_load();

/**
 * Example of initial class-based plugin load.
 */
class Prefix_Example_Plugin {

    /**
     * Hooks init (nothing else) and calls things that need to run right away.
     */
    static function on_load() {

        // if needed kill switch goes here (if disable constant defined then return)

        add_action( 'init', array( __CLASS__, 'init' ) );
    }

    /**
     * Further hooks setup, loading files, etc.
     *
     * Note that for hooked methods name equals hook (when possible).
     */
    static function init(  ) {


    }
}


注意:


已为需要立即运行的事物定义了位置
禁用/进行调整很容易(解开一个init方法)
我不认为我曾经使用过/不需要插件类的对象-需要跟踪它,等等;这实际上是有目的的虚假命名空间,而不是OOP(大多数情况下)。他们。如果需要进行单元测试,请对此进行研究。

评论


我知道从事单元测试的人们真的不喜欢静态/单例解决方案。我认为,如果您完全了解要使用静态方法实现的目标,并且至少知道这样做的后果,那么实现此类方法就非常合适。在Stack Overflow上解决这个问题的好话题

–亚当
2012年10月22日上午10:21

这让我真正思考。那么,为什么不使用类,而不仅仅是返回简单的前缀函数呢?我们这样做仅仅是为了获得更简洁的功能/方法名称吗?我的意思是让它们与“静态” b4嵌套在一起是否更具可读性?如果您使用适当的前缀,或者我遗漏了某些东西,则名称冲突的可能性与单个类名的可能性差不多。

–詹姆斯·米奇(James Mitch)
13年5月28日在5:52

@JamesMitch是的,全静态方法通常只是WP中使用带有假名称空间的函数。但是,即使在这种情况下,类也比纯函数具有一些优势,例如自动加载和继承。最近,我已经从静态方法转向由依赖项注入容器组织的实际实例化对象。

–稀有
13年5月28日在10:35

#4 楼

一切都取决于功能。

我曾经做过一个插件,该插件在调用构造函数时会注册脚本,因此我必须将其钩在wp_enqueue_scripts钩上。

如果要在加载functions.php文件时调用它,您也可以像前面提到的那样自己创建一个实例$class_instance = new ClassName();

您可能要考虑速度和内存使用情况。我什么都不知道,但是我可以想象在某些情况下有一些未使用的钩子。通过在该钩子上创建实例,您可以节省一些服务器资源。

评论


非常感谢,我想上述问题也有两点。另一个是__construct是否合适,或者init()是否是初始化类的更好方法。

– Kalpaitch
2012年10月22日9:40



好吧,我会去找一个静态的init()方法,以便在类的作用域而不是可能覆盖现有变量的另一个作用域中调用类实例。

– Tim S.
2012-10-22 9:54

#5 楼

我知道这已经有两年了,但是php 5.3支持匿名方法,所以我想到了:

add_action( 'plugins_loaded', function() { new My_Plugin(); } );


,我最喜欢它。我可以使用常规的构造函数,而无需定义任何会弄乱我的OOP结构的“ init”或“ on_load”方法。