WPプラグインでクラスを開始する最良の方法は?


90

私はプラグインを作成しましたが、もちろん私であるため、素晴らしいオブジェクト指向アプローチを採用したかったのです。今私がやっていることは、このクラスを作成し、次にこのクラスのインスタンスを作成することです:

class ClassName {

    public function __construct(){

    }
}

$class_instance = new ClassName();  

このクラスを開始するより多くのWPの方法があると思います。そして、私は彼らinit()__construct()1 つよりも機能を持つことを好むと言っている人々に出会いました。同様に、次のフックを使用している数人のユーザーが見つかりました。

class ClassName {

    public function init(){

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

一般的に、ロード時にWPクラスインスタンスを作成し、これをグローバルにアクセス可能な変数として作成する最良の方法は何ですか?

注:おもしろい側面として、register_activation_hook()から呼び出す__constructことはできますがinit()、2番目の例を使用してから呼び出すことはできません。おそらく誰かがこの点で私を啓発することができます。

編集:すべての答えをありがとう、クラス自体の中で初期化を処理する方法についてはかなり議論がありますがadd_action( 'plugins_loaded', ...);、実際にはそれを実際に開始する最良の方法であるかなり良いコンセンサスがあると思います...

編集:問題を混乱させるために、これが使用されていることも見てきました(ただし、オブジェクト指向クラスを適切に関数に変換することはそのポイントを無効にしているように見えるので、私はこのメソッドを使用しません):

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

1
最後の編集に関して、それがクラスと同じプラグインファイルに含まれている場合、それはいくらか役に立たなくなります。私が説明する方法に従って、クラスをインスタンス化することもできます。たとえそれが別のファイルにあったとしても、それでもやや無意味です。私が見ることができるのは、プラグインファイルの外部、テーマなどでクラスをインスタンス化できるラッパー関数を作成する場合のみです。それでも、条件とフックを適切に使用すると、インスタンス化をきめ細かく制御できるため、代わりにプラグインの使用に集中できるため、その背後にあるロジックを尋ねる必要があります。
アダム

私はこれに少し同意しますが、いくつかのWPプラグインで見つけたので、入れる価値があると思いました。
kalpaitch

回答:


60

良い質問です。いくつかのアプローチがあり、それはあなたが何を達成したいかによって異なります。

私はよくやります。

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によるこの要点で見ることができます。

空のコンストラクターアプローチ。

空のコンストラクターアプローチを完全に例示する上記の要点から得られた利点/欠点の抜粋を以下に示します。

  • 利点:

    • 単体テストでは、フックを自動的にアクティブ化せずに新しいインスタンスを作成できます。シングルトンなし。

    • グローバル変数は必要ありません。

    • プラグインインスタンスを使用したい人は誰でもT5_Plugin_Class_Demo :: get_instance()を呼び出すことができます。

    • 簡単に無効化できます。

    • まだ本当のOOP:静的な作業メソッドはありません。

  • 不利益:

    • 読みにくいかもしれませんか?

私の意見での不利な点は、それが私の好むアプローチでなければならない理由ですが、私が使用している唯一のアプローチではありません。実際、他のいくつかの重い重みは、まもなくこの主題を表明するべきいくつかの良い意見があるので、この主題についてすぐに彼らの意見でチャイムを鳴らすでしょう。


注:それぞれの長所と短所を調べたプラグイン内でクラスをインスタンス化する方法の3つまたは4つの比較を実行したtoschoの要点例を見つける必要があります。他の例は、このトピックとの良好な対照を提供します。うまくいけば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

1
load-plugins.phpが理解していることから、それは機能しupdate.phpますが、コアファイルに関連付けられており、初期化中に発生するイベントのシーケンスに関して依存する通常のデフォルトアクションの一部ではありません。この場合、適用されるフックを使用しますplugins_loaded。これは、アクションリファレンスの実行時に起こることのクイックスナップショットとしてよく参照します。私の説明は完全ではありません。
アダム

4
私はこのシングルトンのようなアプローチが好きです。ただし、初期化アクションフックとしてplugins_loadedを使用することには疑問があります。このフックはすべてのプラグインがロードされたに実行されることを意図しています。それをフックすることで、あなたはそのフックをハイジャックし、他のプラグインまたはplugins_loadedにフックするテーマとの競合や起動シーケンスの問題を楽しむことができます。初期化メソッドを実行するためのアクションにはフックしません。プラグインアーキテクチャは、アクションではなくインラインで実行するように設計されています。
トム・オージェ

2
使用するregister_activation_hook()場合は、plugins_loadedアクションがトリガーされる前にその関数を呼び出す必要があることに注意してください。
-Geert

1
追加情報として、@ mikeschinkelからのこの投稿とコメントの説明を参照してください。hardcorewp.com/2012/…–
bueltge

78

最初の質問が出されてからちょうど2年後にここに到着したとき、私が指摘したいこといくつかあります。(これまでにたくさんのことを指摘してはいけません)。

適切なフック

プラグインクラスをインスタンス化するには、適切なフックを使用する必要があります。クラスの動作に依存するため、一般的なルールはありません。

このような"plugins_loaded"フックは管理、フロントエンド、AJAXリクエストに対して起動されるため、非常に早い段階のフックを使用することは意味がありませんが、必要な場合にのみプラグインクラスをインスタンス化できるため、後のフックの方がはるかに優れています。

たとえば、テンプレートの処理を行うクラスをインスタンス化できます"template_redirect"

一般的に、クラスを"wp_loaded"起動する前にインスタンス化する必要があることは非常にまれです。

神クラスなし

古い答えの例は以下のようにという名前のクラスを使用して使用されるすべてのクラスのほとんどは、"Prefix_Example_Plugin"または"My_Plugin"...これはおそらくがあることを示している主なプラグインのクラス。

単一のクラスでプラグインが作成されない限り(この場合、プラグイン名にちなんで命名するのは絶対に合理的です)、プラグイン全体を管理するクラスを作成します(たとえば、プラグインに必要なすべてのフックを追加するか、他のすべてのプラグインクラスをインスタンス化します) )は、神のオブジェクトの例として、悪い習慣と考えることができます

オブジェクト指向プログラミングでは、コードは「S」が「単一責任原則」を表す場合、SOLIDになる傾向があります。

つまり、すべてのクラスが1つのことを実行する必要があります。WordPressプラグイン開発では、開発者は単一のフックを使用してメインプラグインクラスをインスタンス化することを避ける必要がありますが、クラスの責任に応じて、異なるクラスをインスタンス化するために異なるフックを使用する必要があります。

コンストラクターでのフックを避ける

この議論は他の回答でも紹介されていますが、この概念に注目し、ユニットテストの範囲でかなり広く説明されている他の回答にリンクさせたいと思います。

ほぼ2015年:PHP 5.2はゾンビ向け

2014年8月14日以降、PHP 5.3は廃止されました。間違いなく死んでいます。PHP 5.4は2015年のすべてでサポートされる予定です。つまり、私が書いている時点でもう1年です。

ただし、WordPressは引き続きPHP 5.2をサポートしていますが、特にコードがOOPである場合、そのバージョンをサポートするコードを1行も記述しないでください。

さまざまな理由があります。

  • PHP 5.2はずっと前に死んでおり、セキュリティ修正はリリースされていません。つまり、安全ではありません。
  • PHP 5.3はPHP、に、多くの機能を追加した無名関数名前空間の ユーバーのALLES
  • PHPの新しいバージョンは非常に高速です。PHPは無料です。更新は無料です。高速で安全なバージョンを無料で使用できるのに、なぜ低速で安全でないバージョンを使用するのですか?

PHP 5.4+コードを使用したくない場合は、少なくとも5.3+を使用してください

この時点で、ここまで私が言ったことに基づいて、古い回答を確認します。

5.2を気にする必要がなくなったら、名前空間を使用できます。

単一の責任原則をよりよく説明するために、私の例では3つのクラスを使用します。1つはフロントエンドで何かを、1つはバックエンドで、もう1つは両方のケースで使用します。

管理クラス:

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();

}

そして、他の2つで使用されるToolsクラス:

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 );    
} );

依存関係の反転と依存関係の注入

上記の例では、名前空間と匿名関数を使用して、さまざまなフックでさまざまなクラスをインスタンス化し、上記で述べたことを実際に実行しました。

名前空間がプレフィックスなしの名前のクラスを作成する方法に注意してください。

上記で間接的に言及した別の概念を適用しました:Dependency InjectionはDependency Inversion Principle、SOLID頭字語の「D」を適用する1つの方法です。

Toolsそれらがインスタンス化されるときに、クラスは、この方法では、責任を分離することができるので、他の二つのクラスに「注入さ」されます。

さらに、AdminStuffおよびFrontStuffクラスは、タイプヒンティングを使用して、を実装するクラスが必要であることを宣言しますToolsInterface

このように、コードを使用する私たち自身またはユーザーは、同じインターフェースの異なる実装を使用し、コードを具体的なクラスではなく抽象化に結合することができます:それがまさに依存関係反転の原理です。

ただし、上記の例はさらに改善できます。方法を見てみましょう。

オートローダー

より読みやすいOOPコードを書く良い方法は、タイプ(インターフェース、クラス)定義を他のコードと混合せず、すべてのタイプを独自のファイルに入れることです。

この規則は、PSR-1コーディング標準の1つでもあります。

ただし、そうすることで、クラスを使用する前に、それを含むファイルを要求する必要があります。

これは圧倒的ですが、PHPは、名前に基づいてファイルをロードするコールバックを使用して、必要なときにクラスを自動ロードするユーティリティ関数を提供します

名前空間を使用すると、フォルダ構造と名前空間構造を一致させることができるため、非常に簡単になります。

それは可能であるだけでなく、別のPSR規格でもあります(または、より良い2:PSR-0は非推奨になり、PSR-4になります)。

この標準に従うと、カスタムオートローダーをコーディングすることなく、オートロードを処理するさまざまなツールを利用できます。

WordPressのコーディング標準には、ファイルの命名規則が異なると言わざるを得ません。

そのため、WordPressコア用のコードを作成する場合、開発者はWPルールに従う必要がありますが、カスタムコードを作成する場合は開発者の選択ですが、PSR標準を使用すると、すでに作成されたツールを使用する方が簡単です2

グローバルアクセス、レジストリ、およびサービスロケーターパターン。

WordPressでプラグインクラスをインスタンス化する際の最大の問題の1つは、コードのさまざまな部分からプラグインクラスにアクセスする方法です。

WordPress自体はグローバルアプローチを使用しています。変数はグローバルスコープに保存され、どこからでもアクセスできます。すべてのWP開発者globalは、キャリアの中で何千回も単語を入力します。

これも上記の例で使用したアプローチですが、です。

この答えはすでに長すぎてその理由をさらに説明することはできませんが、「グローバル変数の悪」に関するSERPの最初の結果を読むことは良い出発点です。

しかし、グローバル変数をどのように回避できますか?

さまざまな方法があります。

ここでの古い答えのいくつかは静的インスタンスアプローチを使用します

public static function instance() {

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

  return self::$instance;
}

それは簡単で非常にうまくいきますが、アクセスしたいすべてのクラスにパターンを実装することを強制します。

さらに、多くの場合、このアプローチは、開発者がこのメソッドを使用してメインクラスにアクセスできるようにし、それを使用して他のすべてのクラスにアクセスするため、神クラスの問題に陥ります。

神クラスがどれほど悪いかについてはすでに説明したので、静的インスタンスのアプローチは、プラグインがアクセス可能な1つまたは2つのクラスを作成するだけでよい場合に適した方法です。

これは、いくつかのクラスを持つプラグインにのみ使用できることを意味するものではありません。実際、依存性注入の原則が適切に使用されている場合、多数のグローバルにアクセス可能にすることなく、非常に複雑なアプリケーションを作成することが可能ですオブジェクトの。

ただし、プラグインは一部のクラスにアクセスできるようにする必要がある場合があり、その場合、静的インスタンスのアプローチは圧倒的です。

別の可能なアプローチは、レジストリパターンを使用することです。

これは非常に単純な実装です。

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;
}

関数が最初に呼び出されると、レジストリがインスタンス化され、以降の呼び出しでは関数が返されます。

クラスをグローバルにアクセス可能にする別のWordPress固有の方法は、フィルターからオブジェクトインスタンスを返すことです。このようなもの:

$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ライブラリが登場しました。

このライブラリは、依存関係を解決するクラスをインスタンス化し、オブジェクトを保存し、必要に応じて返すことができるため、レジストリオブジェクトと同様に動作するため、Dependency Injection Containersと呼ばれます。

通常、DIコンテナを使用する場合、開発者はアプリケーションのすべてのクラスの依存関係を設定する必要があり、その後、適切な依存関係でインスタンス化されたコードで初めてクラスが必要になり、後続のリクエストで同じインスタンスが何度も返されます。

一部のDIコンテナは、設定なしで依存関係を自動的に検出することもできますが、PHPリフレクションを使用します

よく知られているDIコンテナは次のとおりです。

その他多数。

少数のクラスのみを含む単純なプラグインと、多くの依存関係を持たないクラスについては、おそらくDIコンテナーを使用する価値はないことを指摘しておきます。静的インスタンスメソッドまたはグローバルアクセス可能なレジストリは良いソリューションですが、複雑なプラグインについてはDIコンテナーの利点が明らかになります。

もちろん、アプリケーションで使用するには、DIコンテナオブジェクトにもアクセスできる必要があります。そのために、上記のメソッド、グローバル変数、静的インスタンス変数、フィルター経由でオブジェクトを返すなどのいずれかを使用できます。

作曲家

DIコンテナを使用することは、多くの場合、サードパーティのコードを使用することを意味します。今日では、我々は外部のlib(そうDIコンテナだけでなく、使用する必要がPHPで任意のアプリケーションの一部ではないコード)を、単にそれをダウンロードして、当社のアプリケーションフォルダにそれを置くことは良い習慣とはみなされません。たとえ他のコードの作成者であっても。

アプリケーションコードを外部の依存関係から切り離すことは、コードの編成、信頼性、および健全性の向上の兆候です。

Composerは、PHP依存関係を管理するためのPHPコミュニティの事実上の標準です。WPコミュニティの主流となるには遠く離れており、PHPとWordPressのすべての開発者が使用しないとしても少なくとも知っておくべきツールです。

この回答は、さらなる議論を可能にするためにすでに本のサイズになっています。また、ここでComposerについて議論することは、おそらくトピックから外れています。

詳細については、Composerサイトをご覧ください。また、@ Rarstがキュレーションしたこのミニサイトを読む価値があります。


1 PSRは、PHP Framework Interop GroupによってリリースされたPHP標準ルールです

2特にComposer(この回答で言及されるライブラリ)には、オートローダーユーティリティも含まれています。


1
また、PHP 5.3もサポート終了です。責任のあるホストは、デフォルトではない場合、少なくとも5.4をオプションとして提供します
トムJノウェル

2
「2014年8月14日以降、PHP 5.3は寿命に達しました。間違いなく死んでいます。」「PHP 5.2はゾンビ向け」の下の最初の行です@TomJNowell
gmazzap

3
ただ不思議に思って、「たくさんの」ものをどのように指摘しますか?;-)
MikeSchinkel

グローバル変数を回避するための努力では、関数と静的変数を使用したレジストリパターンのアイデアが好きです。関数が最初に呼び出されると、レジストリがインスタンス化され、以降の呼び出しでは関数が返されます。
マイケルエクルンド

10

私は次の構造を使用します:

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(  ) {


    }
}

ノート:

  • すぐに実行する必要があるものの場所を定義しました
  • 調整の無効化/オーバーライドは簡単です(1つのinitメソッドをアンフックします)
  • プラグインクラスのオブジェクトを使用したり、必要としたりしたことはないと思います。これは本当にOOPではなく、目的ごとの偽の名前空間です(ほとんどの場合)

免責事項ユニットテストはまだ使用していません(myplateで非常に多くのものが使用されています)。単体テストが必要な場合は、これについて調査してください。


3
ユニットテストに大きな関心を寄せている人は、静的/シングルトンソリューションが本当に嫌いだと思います。静的を使用して達成しようとしていることを完全に理解し、少なくともそうすることの影響を認識している場合、そのようなメソッドを実装することは完全にうまくいくと思います。Stack Overflow
Adam

これは本当に考えさせられました。したがって、単純な接頭辞付き関数に戻るのではなく、なぜクラスを使用するのでしょう。きれいな関数/メソッド名にするためだけにこれを行いますか?私はそれらを「静的な」b4でネストすることを意味します。名前の競合が発生する可能性は、適切なプレフィックスを使用するか、何か不足している場合、単一のクラス名の場合とほぼ同じです。
ジェームズミッチ

1
@JamesMitchはい、すべて静的なメソッドは、ほとんどの場合、WPで使用される偽の名前空間を持つ関数にすぎません。ただし、この場合でも、クラスには純粋な関数よりも優れた利点があります(自動ロードや継承など)。最近、静的メソッドから、依存性注入コンテナによって編成された実際のインスタンス化されたオブジェクトに向かっています。
ラスト

3

すべて機能に依存しています。

かつて、コンストラクターが呼び出されたときにスクリプトを登録するプラグインを作成したため、wp_enqueue_scriptsフックでフックする必要がありました。

functions.phpファイルがロードされたときに呼び出す場合は、$class_instance = new ClassName();前述のように自分でインスタンスを作成することもできます。

速度とメモリ使用量を考慮することをお勧めします。私は何も知りませんが、場合によっては呼び出されていないフックがあると想像できます。そのフックでインスタンスを作成すると、サーバーリソースを節約できます。


おかげで、上記の質問にも2つのポイントがあると思います。もう1つは、__ constructが適切かどうか、またはinit()がクラスを初期化するより良い方法であるかどうかです。
kalpaitch

1
init()クラスインスタンスは、既存の変数を上書きできる別のスコープではなく、クラスのスコープで呼び出されるように、静的メソッドを使用します。
ティムS.

0

私はこれが数年前であることを知っていますが、PHP 5.3は匿名メソッドをサポートしているので、私はこれを思いつきました:

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

どういうわけか私はそれが一番好きです。通常のコンストラクターを使用でき、OOP構造を台無しにする「init」または「on_load」メソッドを定義する必要はありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.