PHP で新しい Web アプリケーションを開始しています。今回は、プラグイン インターフェイスを使用して拡張できるものを作成したいと考えています。
プラグインが特定のイベントにアタッチできるように、コードに「フック」を書き込むにはどうすればよいでしょうか?
PHP で新しい Web アプリケーションを開始しています。今回は、プラグイン インターフェイスを使用して拡張できるものを作成したいと考えています。
プラグインが特定のイベントにアタッチできるように、コードに「フック」を書き込むにはどうすればよいでしょうか?
Observerパターンを使用できます。これを達成するための簡単な機能的な方法:
<?php
/** Plugin system **/
$listeners = array();
/* Create an entry point for plugins */
function hook() {
global $listeners;
$num_args = func_num_args();
$args = func_get_args();
if($num_args < 2)
trigger_error("Insufficient arguments", E_USER_ERROR);
// Hook name should always be first argument
$hook_name = array_shift($args);
if(!isset($listeners[$hook_name]))
return; // No plugins have registered this hook
foreach($listeners[$hook_name] as $func) {
$args = $func($args);
}
return $args;
}
/* Attach a function to a hook */
function add_listener($hook, $function_name) {
global $listeners;
$listeners[$hook][] = $function_name;
}
/////////////////////////
/** Sample Plugin **/
add_listener('a_b', 'my_plugin_func1');
add_listener('str', 'my_plugin_func2');
function my_plugin_func1($args) {
return array(4, 5);
}
function my_plugin_func2($args) {
return str_replace('sample', 'CRAZY', $args[0]);
}
/////////////////////////
/** Sample Application **/
$a = 1;
$b = 2;
list($a, $b) = hook('a_b', $a, $b);
$str = "This is my sample application\n";
$str .= "$a + $b = ".($a+$b)."\n";
$str .= "$a * $b = ".($a*$b)."\n";
$str = hook('str', $str);
echo $str;
?>
出力:
This is my CRAZY application
4 + 5 = 9
4 * 5 = 20
ノート:
このサンプルソースコードでは、拡張可能にする実際のソースコードの前に、すべてのプラグインを宣言する必要があります。プラグインに渡される単一または複数の値を処理する方法の例を含めました。これの最も難しい部分は、どの引数が各フックに渡されるかをリストする実際のドキュメントを書くことです。
これは、PHPでプラグインシステムを実現するための1つの方法にすぎません。より良い代替案があります。詳細については、WordPressのドキュメントを確認することをお勧めします。
では、オブザーバー パターンが必要ないとしましょう。なぜなら、リッスンのタスクを処理するためにクラス メソッドを変更する必要があり、汎用的なものが必要だからです。extends
そして、クラスで他のクラスからすでに継承している可能性があるため、継承を使用したくないとしましょう。手間をかけずに任意のクラスをプラグ可能にする汎用的な方法があれば素晴らしいと思いませんか? 方法は次のとおりです。
<?php
////////////////////
// PART 1
////////////////////
class Plugin {
private $_RefObject;
private $_Class = '';
public function __construct(&$RefObject) {
$this->_Class = get_class(&$RefObject);
$this->_RefObject = $RefObject;
}
public function __set($sProperty,$mixed) {
$sPlugin = $this->_Class . '_' . $sProperty . '_setEvent';
if (is_callable($sPlugin)) {
$mixed = call_user_func_array($sPlugin, $mixed);
}
$this->_RefObject->$sProperty = $mixed;
}
public function __get($sProperty) {
$asItems = (array) $this->_RefObject;
$mixed = $asItems[$sProperty];
$sPlugin = $this->_Class . '_' . $sProperty . '_getEvent';
if (is_callable($sPlugin)) {
$mixed = call_user_func_array($sPlugin, $mixed);
}
return $mixed;
}
public function __call($sMethod,$mixed) {
$sPlugin = $this->_Class . '_' . $sMethod . '_beforeEvent';
if (is_callable($sPlugin)) {
$mixed = call_user_func_array($sPlugin, $mixed);
}
if ($mixed != 'BLOCK_EVENT') {
call_user_func_array(array(&$this->_RefObject, $sMethod), $mixed);
$sPlugin = $this->_Class . '_' . $sMethod . '_afterEvent';
if (is_callable($sPlugin)) {
call_user_func_array($sPlugin, $mixed);
}
}
}
} //end class Plugin
class Pluggable extends Plugin {
} //end class Pluggable
////////////////////
// PART 2
////////////////////
class Dog {
public $Name = '';
public function bark(&$sHow) {
echo "$sHow<br />\n";
}
public function sayName() {
echo "<br />\nMy Name is: " . $this->Name . "<br />\n";
}
} //end class Dog
$Dog = new Dog();
////////////////////
// PART 3
////////////////////
$PDog = new Pluggable($Dog);
function Dog_bark_beforeEvent(&$mixed) {
$mixed = 'Woof'; // Override saying 'meow' with 'Woof'
//$mixed = 'BLOCK_EVENT'; // if you want to block the event
return $mixed;
}
function Dog_bark_afterEvent(&$mixed) {
echo $mixed; // show the override
}
function Dog_Name_setEvent(&$mixed) {
$mixed = 'Coco'; // override 'Fido' with 'Coco'
return $mixed;
}
function Dog_Name_getEvent(&$mixed) {
$mixed = 'Different'; // override 'Coco' with 'Different'
return $mixed;
}
////////////////////
// PART 4
////////////////////
$PDog->Name = 'Fido';
$PDog->Bark('meow');
$PDog->SayName();
echo 'My New Name is: ' . $PDog->Name;
パート 1 では、これをrequire_once()
PHP スクリプトの先頭に呼び出しで含めることができます。クラスをロードして、何かをプラグ可能にします。
パート 2 では、ここでクラスをロードします。クラスに対して特別なことをする必要はありませんでした。これは、Observer パターンとは大きく異なります。
パート 3 では、ここでクラスを「プラグ可能」に切り替えます (つまり、クラスのメソッドとプロパティをオーバーライドできるプラグインをサポートします)。たとえば、Web アプリがある場合、プラグイン レジストリがあり、ここでプラグインを有効化できます。機能にも注意してくださいDog_bark_beforeEvent()
。return ステートメントの前に設定$mixed = 'BLOCK_EVENT'
すると、犬が吠えるのをブロックし、イベントがないため Dog_bark_afterEvent もブロックします。
パート 4 では、これが通常の操作コードですが、実行されると思われるコードが実際にはまったく実行されないことに注意してください。たとえば、犬は自分の名前を 'Fido' ではなく 'Coco' と発表します。犬は「ニャー」ではなく「ウーフ」と言います。あとで犬の名前を見てみると、「Coco」ではなく「Different」であることがわかります。これらのオーバーライドはすべてパート 3 で提供されました。
では、これはどのように機能するのでしょうか。さて、eval()
(誰もが「悪」だと言う) を除外し、それが Observer パターンではないことを除外しましょう。そのため、Plugable と呼ばれる卑劣な空のクラスが機能します。このクラスには、Dog クラスで使用されるメソッドとプロパティが含まれていません。したがって、それが起こるので、魔法の方法が私たちのために従事します. そのため、第 3 部と第 4 部では、Dog クラス自体ではなく、Pluggable クラスから派生したオブジェクトをいじっています。代わりに、Plugin クラスに Dog オブジェクトへの「タッチ」を行わせます。(それが私が知らないデザインパターンである場合は、お知らせください。)
フックとリスナーの方法が最も一般的に使用されますが、他にもできることがあります。アプリのサイズと、誰にコードの表示を許可するか (これは FOSS スクリプトにするか、社内で行うか) に応じて、プラグインを許可する方法に大きく影響します。
kdeloach には良い例がありますが、彼の実装とフック関数は少し安全ではありません。あなたが書いたphpアプリの性質と、プラグインがどのように適合するかについて、より多くの情報を提供してください.
私からkdeloachに+1。
これは私が使用したアプローチです。これは、オブザーバー パターンの一種である Qt シグナル/スロット メカニズムからコピーしようとする試みです。オブジェクトはシグナルを発することができます。すべてのシグナルはシステム内で ID を持っています - それは送信者の ID + オブジェクト名で構成されています すべてのシグナルは受信者にバインドできます。これは単純に「呼び出し可能」です バスクラスを使用して、受信に関心のある人にシグナルを渡します信号を「送信」します。以下は実装例です
<?php
class SignalsHandler {
/**
* hash of senders/signals to slots
*
* @var array
*/
private static $connections = array();
/**
* current sender
*
* @var class|object
*/
private static $sender;
/**
* connects an object/signal with a slot
*
* @param class|object $sender
* @param string $signal
* @param callable $slot
*/
public static function connect($sender, $signal, $slot) {
if (is_object($sender)) {
self::$connections[spl_object_hash($sender)][$signal][] = $slot;
}
else {
self::$connections[md5($sender)][$signal][] = $slot;
}
}
/**
* sends a signal, so all connected slots are called
*
* @param class|object $sender
* @param string $signal
* @param array $params
*/
public static function signal($sender, $signal, $params = array()) {
self::$sender = $sender;
if (is_object($sender)) {
if ( ! isset(self::$connections[spl_object_hash($sender)][$signal])) {
return;
}
foreach (self::$connections[spl_object_hash($sender)][$signal] as $slot) {
call_user_func_array($slot, (array)$params);
}
}
else {
if ( ! isset(self::$connections[md5($sender)][$signal])) {
return;
}
foreach (self::$connections[md5($sender)][$signal] as $slot) {
call_user_func_array($slot, (array)$params);
}
}
self::$sender = null;
}
/**
* returns a current signal sender
*
* @return class|object
*/
public static function sender() {
return self::$sender;
}
}
class User {
public function login() {
/**
* try to login
*/
if ( ! $logged ) {
SignalsHandler::signal(this, 'loginFailed', 'login failed - username not valid' );
}
}
}
class App {
public static function onFailedLogin($message) {
print $message;
}
}
$user = new User();
SignalsHandler::connect($user, 'loginFailed', array($Log, 'writeLog'));
SignalsHandler::connect($user, 'loginFailed', array('App', 'onFailedLogin'));
$user->login();
?>
最も簡単な方法は、Jeff自身のアドバイスに従い、既存のコードを確認することだと思います。WordPress、Drupal、Joomla、およびその他の有名なPHPベースのCMSを調べて、APIフックのルックアンドフィールを確認してください。このようにして、これまで考えていなかったアイデアを取得して、物事をもう少し堅牢にすることもできます。
より直接的な答えは、必要なユーザビリティを提供するファイルに「include_once」する一般的なファイルを書き込むことです。これはカテゴリに分割され、1つのMASSIVE「hooks.php」ファイルでは提供されません。ただし、最終的に発生するのは、それらに含まれるファイルの依存関係と機能がますます向上することになるため、注意してください。APIの依存関係を低く保つようにしてください。IEに含めるファイルが少なくなります。
PHP でプラグインを処理するための作業の多くを処理する、Yahoo の Matt Zandstra によるSticklebackという優れたプロジェクトがあります。
プラグイン クラスのインターフェイスを強制し、コマンド ライン インターフェイスをサポートし、起動して実行するのはそれほど難しくありません。特に、PHP アーキテクト マガジンでカバー ストーリーを読んだ場合はなおさらです。
良いアドバイスは、他のプロジェクトがそれをどのように行ったかを調べることです。多くの場合、プラグインをインストールし、その「名前」をサービスに登録する必要があります(wordpressのように)。そのため、登録されたリスナーを識別して実行する関数を呼び出すコードに「ポイント」があります。標準のオブジェクト指向設計パターンはオブザーバーパターンです。これは、真にオブジェクト指向のPHPシステムに実装するのに適したオプションです。
Zend Frameworkは多くのフック方法を利用しており、非常にうまく設計されています。それは見るのに良いシステムでしょう。
ここでの回答のほとんどが、Web アプリケーションにローカルなプラグイン、つまりローカル Web サーバーで実行されるプラグインに関するものであるように思われることに驚いています。
プラグインを別のリモートサーバーで実行したい場合はどうですか? これを行う最善の方法は、アプリケーションで特定のイベントが発生したときに呼び出されるさまざまな URL を定義できるフォームを提供することです。
発生したばかりのイベントに基づいて、さまざまなイベントがさまざまな情報を送信します。
この方法では、アプリケーションに提供された URL (例: https 経由) に対して cURL 呼び出しを実行するだけで、アプリケーションから送信された情報に基づいてリモート サーバーがタスクを実行できます。
これには、次の 2 つの利点があります。