web-dev-qa-db-ja.com

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

私はプラグインを作成しました、そしてもちろん私であるので、私はNice OOアプローチで行きたかったです。今私がやっているのは、このクラスを作成し、そのすぐ下にこのクラスのインスタンスを作成することです。

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内から呼び出すことはできますが、2番目の例を使用してinit()内から呼び出すことはできません。多分誰かがこの点で私を啓発することができます。

編集: すべての答えのおかげで、クラス自体の中で初期化を処理する方法についてかなりの議論があることは明らかですが、add_action( 'plugins_loaded', ...);が実際にそれをキックするための最良の方法であるという一般的にかなり良いコンセンサスがあると思いますオフ...

編集: 問題を混乱させるために、これも使われているのを見たことがある(私はこのメソッドを自分では使わないだろうが、うまくOOクラスを関数に変えるのはそれを打破するように思われる):

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

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

私はよくします。

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メンバーによる this Gisttoscho で見ることができます。

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

これは、空のコンストラクタのアプローチを完全に例示した上記の要旨から得られた長所/短所の抜粋です。

  • 利点:

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

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

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

    • 無効にするのは簡単。

    • それでも本物のOOP:作業方法は静的です。

  • 不利益:

    • たぶん読むのは難しいですか?

私の意見での不利な点はそれが私の好意的なアプローチでなければならない理由であるが、私が使用する唯一のものではないという点でそれが弱い点です。実際、このトピックを取り巻くいくつかの良い意見があるため、このトピックについて他のいくつかの重点が疑問を投げかけます。


注意:私は toscho から、それぞれの長所と短所を調べてプラグイン内でクラスをインスタンス化する方法を3回または4回比較したGistの例を見つける必要があります。しかし、他の例はこのトピックとはかなり対照的です。うまくいけば 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!
    }
}
57
userabuser

元の質問が尋ねられてからちょうど2年後にここに到着すると、いくつかのことがあります指摘したいと思います。 (多くのことを指摘するように頼まないでください、これまで)。

適切なフック

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

"plugins_loaded"のような非常に早いフックを使用することは、管理、フロントエンド、およびAJAXリクエストに対して起動されるため、ほとんど意味がありませんが、非常に多くの場合、プラグインクラスを必要です。

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

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

神クラスなし

古い回答の例として使用されているすべてのクラスのほとんどは、"Prefix_Example_Plugin"または"My_Plugin"...などの名前のクラスを使用しています。これは、プラグインにmainクラスがあることを示しています。

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

オブジェクト指向プログラミングでは、コードは S.O.L.I.D。 になる傾向があります。ここで、 "S"は "単一責任原則" を表します。

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

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

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

ほぼ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に多くの機能を追加しました 匿名関数 および 名前空間überalles
  • PHPの新しいバージョンはたくさんありますfaster。 PHPは無料です。更新は無料です。高速で安全なバージョンを無料で使用できるのに、なぜ低速で安全でないバージョンを使用するのですか?

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

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

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

単一の責任原則をよりよく説明するために、私の例では3つのクラスを使用します。1つはフロントエンドでsomethingを、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 、これは適用する1つの方法です Dependency Inversion原則 、SOLID頭字語の「D」。

Toolsクラスは、インスタンス化されるときに他の2つのクラスに「注入」されるため、このようにして責任を分離することができます。

さらに、AdminStuffおよびFrontStuffクラスは、 type hinting を使用して、ToolsInterfaceを実装するクラスが必要であることを宣言します。

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

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

オートローダー

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

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

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

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

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

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

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

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

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

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

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

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

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

この答えは長すぎて理由をさらに説明することはできませんが、 "global variables evil" のSERPの最初の結果を読むことは良い出発点です。

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

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

ここでの古い回答のいくつかは、staticインスタンスアプローチを使用しています。

public static function instance() {

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

  return self::$instance;
}

簡単で結構ですが、アクセスするすべてのクラスにパターンを実装する必要があります。

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

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

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

ただし、プラグインはアクセス可能なsomeクラスを作成する必要があり、その場合、静的インスタンスのアプローチは圧倒的です。

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

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

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

上記の例は、レジストリをグローバルにアクセスできるようにする必要があることを明らかにしています。唯一のレジストリのグローバル変数はvery悪くありませんが、非グローバルな純粋主義者にとっては、レジストリの静的インスタンスアプローチを実装することは可能です。変数:

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コンテナオブジェクトでさえアクセスできる必要があります。そのためには、上記のメソッド、グローバル変数、静的インスタンス変数、フィルター経由でオブジェクトを返すなどのいずれかを使用できます。

Composer

DIコンテナを使用することは、多くの場合、サードパーティのコードを使用することを意味します。最近では、PHPで外部ライブラリを使用する必要がある場合(つまり、DIコンテナだけでなく、アプリケーションの一部ではないanyコード) 、単にダウンロードしてアプリケーションフォルダに配置するだけでは良い方法とは見なされません。たとえ他のコードの作成者であっても。

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

Composer は、PHP依存関係を管理するPHPコミュニティのde-facto標準です。 WPコミュニティでもmainstreamであるため、すべてのPHPおよびWordPress開発者が使用するツールです使用しない場合、少なくとも知っている必要があります。

この答えは、さらなる議論を可能にするためにすでに本サイズであり、ここでComposerについて議論することはおそらくトピックから外れています。完全を期すために言及しただけです。

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


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

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

77
gmazzap

私は以下の構造を使います。

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の多くのもの )そして、staticはそれらにはあまり好ましくないと聞きます。あなたがそれを単体テストする必要があるならば、これについてあなたの研究をしてください。

10
Rarst

それはすべて機能に依存します。

私はかつてコンストラクタが呼び出されたときにスクリプトを登録するプラグインを作成したので、wp_enqueue_scriptsフックでそれをフックしなければなりませんでした。

functions.phpファイルがロードされたときにそれを呼び出したい場合は、あなたがあなたが述べたようにあなた自身で$class_instance = new ClassName();インスタンスを作成するかもしれません。

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

3
Tim S.

私はこれが数年前だと知っていますが、その間に php 5.3は無名メソッドをサポートしています それで私はこれを思い付きました:

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

そしてどういうわけか私はそれが一番好きです。私は通常のコンストラクタを使うことができ、私のOOP構造をめちゃくちゃにする "init"や "on_load"メソッドを定義する必要はありません。

0
Basti