最初の質問が出されてからちょうど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(この回答で言及されるライブラリ)には、オートローダーユーティリティも含まれています。