MySQL Native Driver プラグインAPI
The MySQL Native Driver プラグインAPI は MySQL Native Driver、略して mysqlnd が持つ機能の一つです。mysqlnd プラグインは、PHPアプリケーションと MySQL サーバーの間にあるレイヤで動きます。これは MySQL Proxy と比較できます。MySQL Proxy はあらゆる MySQL クライアントアプリケーション、たとえば PHPアプリケーション と MySQL サーバーの間のレイヤで動きます。mysqlnd プラグインは典型的な MySQL Proxy のタスク、たとえばロードバランシングやモニタリング、パフォーマンスの最適化をこなせます。アーキテクチャや置かれる場所が異なるため、mysqlnd プラグインは MySQL Proxy が持ついくつかの欠点がありません。たとえば、プラグインを使えば、単一障害点が存在しませんし、専用のプロキシサーバーをデプロイする必要もありませんし、新しいプログラミング言語(Lua) を学ぶ必要もありません。
mysqlndプラグインは mysqlndの拡張と考えることができます。プラグインは 多くの mysqlnd 関数の制御を奪い取ることができます。mysqlnd 関数は ext/mysql や ext/mysqli、PDO_MYSQL のような PHP の MySQL拡張モジュールによって呼び出されます。その結果として、mysqlndプラグイン はクライアントアプリケーションからこれらの拡張モジュールへの呼び出しの制御をすべて奪うことができるのです。
内部的な mysqlnd 関数の呼び出しも制御を奪ったり、処理を置き換えたりすることができます。mysqlnd の内部的な関数テーブルを管理することに関しても全く制限がありません。ある mysqlnd 関数が mysqlnd を使う拡張モジュールによって呼び出される場合に、適切な mysqlndプラグインの適切な関数に処理を転送するようにセットアップすることが可能です。このように、mysqlnd の内部関数テーブルを管理できることで、プラグインの柔軟性が最大限に発揮できるのです。
mysqlndプラグインは、実際は mysqlnd のプラグインAPI (これは MySQL Native Driver, 略して mysqlnd に組み込まれています) を使い、C言語 で書かれた PHP拡張モジュール です。 プラグインは PHPアプリケーションに対して 100% 透過的です。つまり、プラグインがPHPアプリケーションとは異なるレイヤで動作するため、アプリケーションを変更する必要がないのです。mysqlndプラグイン は、mysqlnd のひとつ下のレイヤで動くと考えることができます。
mysqlndプラグインで実現可能なアプリケーションのリストをいくつか以下に示します
ロードバランシング
読み取り/書き込み の分割。例として、PECL/mysqlnd_ms (Master Slave) が挙げられます。この 拡張モジュール は 読み取り/書き込み のクエリをレプリケーションのセットアップ向けに分割します。
フェイルオーバー
ラウンドロビン, 負荷が一番低いサーバーへの転送
サーバーの監視
クエリのロギング
クエリの分析
クエリの監査。例として、PECL/mysqlnd_sip (SQL Injection Protection) が挙げられます。この 拡張モジュール はクエリを調べ、ルールセットに従って許可されたクエリのみを実行します。
パフォーマンスの向上
キャッシュ。例として、PECL/mysqlnd_qc (Query Cache) が挙げられます。
トラフィックの調整
シャーディング。例として、PECL/mysqlnd_mc (Multi Connect) が挙げられます。この拡張モジュール は、SELECT ステートメントを SELECT ... LIMIT part1, SELECT LIMIT part_n という形でn個に分割します。そしてクエリを別々の MySQLサーバーに送り、結果をクライアント側でマージします。
利用可能な MySQL Native Driverプラグイン
既にたくさんの mysqlnd プラグインが利用可能になっています。以下が含まれます。
PECL/mysqlnd_mc - 複数接続ができるプラグイン
PECL/mysqlnd_ms - マスタースレーブ構成用のプラグイン
PECL/mysqlnd_qc - クエリキャッシュプラグイン
PECL/mysqlnd_pscache - プリペアドステートメントハンドルをキャッシュするプラグイン
PECL/mysqlnd_sip - SQLインジェクションから防御するためのプラグイン
PECL/mysqlnd_uh - ユーザーハンドラープラグイン
mysqlndプラグイン と MySQL Proxyの比較
mysqlndプラグインと MySQL Proxyは、異なるアプローチを用いた異なる技術です。どちらもロードバランシングや監視、パフォーマンスの向上のような共通の様々な課題を解決するのに適したツールです。重要な違いは、MySQL Proxyがあらゆる MySQLクライアント と協調して動作するのに対して、mysqlndプラグインは PHPアプリケーション との協調動作に特化しているということです。
PHP拡張モジュールとして、mysqlndプラグイン はPHPの残りの部分とともに、PHPのアプリケーションサーバーにインストールされます。MySQL Proxy はPHPアプリケーションサーバー上でも動作しますし、複数のPHPアプリケーションサーバーを扱う専用マシンにもインストールできます。
MySQL Proxyをアプリケーションサーバーにデプロイすることにはふたつの利点があります:
単一障害点がありません
スケールアウトしやすい (水平方向へのスケールアウト、クライアントによるスケーリング)
MySQL Proxy (と、mysqlndプラグイン) は、他のやり方だと既存のアプリケーションを変更しなければならない問題にも容易に対処することができます。
しかし、MySQL Proxyにはいくつか欠点があります:
MySQL Proxy のコンポーネントと技術について新たにマスターし、デプロイしなければなりません。
MySQL Proxy は Lua スクリプト言語の知識が必要です。
MySQL Proxy は C言語と プログラミング言語 Lua によってカスタマイズできます。Lua は MySQL Proxy において好ましいスクリプト言語です。ほとんどのPHPエキスパートにとって、Lua は新しく学ばなければならない言語です。mysqlndプラグインは C言語で書くことができます。PECL/mysqlnd_uh を使って、プラグインを PHP で書くこともできます。
MySQL Proxy はデーモン - バックグラウンドのプロセスとして動作します。MySQL Proxy は初期の決定を取り消すこともできますが、すべての状態を保持することできます。しかしながら mysqlndプラグインは、PHPのリクエスト単位のライフサイクルに結びついています。MySQL Proxy は一度計算された結果を複数のアプリケーションサーバーで共有できます。mysqlndプラグインでこれを行うには、永続的なストレージにデータを保存する必要があります。この目的のためには Memcache のような別のデーモンが必要です。この場合は、MySQL Proxy に有利です。
MySQL Proxy は wire protocol (訳注:ネットワークを通じてデータを転送するプロトコル。WikipediaでのWire Protocolの説明, StackOverflow でのWire Protocolの説明) の上で動作します。MySQL Proxy を使うと、MySQLクライアントサーバープロトコルを解析し、リバースエンジニアリングしなければなりません。MySQL Proxy でできることは、通信プロトコルを管理することで達成できることに限られます。wire protocol が変更(滅多にありませんが)されると、MySQL Proxy のスクリプトも変更する必要があります。
mysqlndプラグインは C言語のAPI上で動作します。このAPIは libmysqlclientクライアント の動きをそのままコピーしています。この C言語のAPI は、 基本的に MySQLクライアントサーバープロトコル、時に wire protocol と呼ばれるもののラッパーです。開発者は C言語のAPI呼び出しの制御を奪うことができます。それゆえに、wire protocol レベルのプログラムに一切変更を加えることなく、すべてのPHP呼び出しをフックできるのです。
mysqlnd は wire protocol を実装しています。そのため、プラグインは通信プロトコルを解析し、リバースエンジニアリングし、管理できるばかりか、通信プロトコルを置き換えることだってできます。ただ、こんなことをする必要は通常ありません。
プラグインが 2つのレベル(C言語のAPI と wire protocol) を使って実装できるので、MySQL Proxy よりも大きな柔軟性を得られます。 mysqlndプラグインが C言語のAPI を使って実装されれば、wire protocol に対していかなる変更が行われても、プラグインへの変更は必要ありません。
mysqlnd plugin APIを取得する
mysqlndプラグインAPI は、MySQL Native Driver PHP拡張モジュールである ext/mysqlnd の一部です。2009年12月に mysqlndプラグインAPI の開発がスタートしました。これは PHP のソースリポジトリの一部として開発が進められ、git経由で公開されたり、ソースコードのスナップショットがダウンロード可能になっています。
プラグインの開発者は mysqlnd のバージョンを MYSQLND_VERSION にアクセスすることで決めることができます。この値は、mysqlnd 8.3.17
という文字列フォーマットです。MYSQLND_VERSION_ID という値にもアクセスできます。これはたとえば、50007 のような数値です。 開発者は次のようにしてバージョン番号を計算することができます:
MYSQLND_VERSION_ID 計算テーブル
Version (一部)
例
Major*10000
5*10000 = 50000
Minor*100
0*100 = 0
Patch
7 = 7
MYSQLND_VERSION_ID
50007
開発している間は、開発者は mysqlnd のバージョン番号を互換性と対応バージョンのテストとして参照すべきです。PHP本体のひとつのバージョンにおける開発のライフサイクルの間に、mysqlnd の開発イテレーションは複数行われる場合があるためです。
MySQL Native Driverプラグイン のアーキテクチャ
このセクションでは、mysqlndプラグイン のアーキテクチャについての概要を示します。
MySQL Native Driver の概要
mysqlndプラグイン を開発する前に、mysqlnd そのものがどのように成り立っているのかを少し知っておくと役に立ちます。mysqlnd は次に示すモジュールからなります:
mysqlnd のモジュール毎のソースコードの組み合わせを示した表
モジュールの統計情報
mysqlnd_statistics.c
データベース接続
mysqlnd.c
結果セット
mysqlnd_result.c
結果セットのメタデータ
mysqlnd_result_meta.c
プリペアドステートメント
mysqlnd_ps.c
ネットワーク
mysqlnd_net.c
Wire protocol
mysqlnd_wireprotocol.c
C言語のオブジェクト指向パラダイム
ソースコードレベルで、mysqlnd は オブジェクト指向を実装するためのパターンを採用しています。
C言語では、オブジェクトを表現するために struct(構造体) を使います。struct のメンバがオブジェクトのプロパティを表現します。関数を指している struct のメンバがメソッドを表現します。
C++ や Java のような言語と異なり、C言語におけるオブジェクト指向のパラダイムでは決まった継承のルールがありませんが、従う必要があるルールがいくつかあります。このルールについては後に述べます。
PHP のライフサイクル
PHP のライフサイクルを考えるとき、ふたつの基本的なサイクルが存在します。
PHPエンジンの起動と終了までのサイクル
リクエスト毎のライフサイクル
PHPエンジンが起動するとき、PHP はモジュールを初期化する (MINIT) 関数を登録された拡張モジュールごとに呼び出します。これによって、各々のモジュールが PHPエンジンが処理を行うライフサイクルの間存在するリソースを割り当てたり、変数を定義することができます。PHPエンジンが終了するときには、 エンジンが終了(MSHUTDOWN)関数を拡張モジュール毎に呼び出します。
PHPエンジンが起動している間、エンジンはたくさんのリクエストを受けとります。それぞれのリクエストは別のライフサイクルを構成します。リクエスト毎にPHPエンジンはリクエストの初期化関数を拡張モジュール毎に呼び出します。拡張モジュール側では、リクエストの処理に必要な変数の定義やリソースの割り当てを行うことができます。リクエストのサイクルが終了するときは、PHPエンジンがリクエストの終了(RSHUTDOWN)関数を拡張モジュール毎に呼び出します。これによって、拡張モジュールは必要となるあらゆるクリーンアップ処理を行うことができます。
プラグインはどうやって動くか
mysqlndプラグイン は mysqlnd を使う拡張モジュールが mysqlnd を呼び出すときの制御を奪うことによって動作します。これは mysqlnd の関数テーブルを取得し、バックアップし、カスタムの関数テーブルと置き換えることによって実現されます。この関数テーブルが、プラグインが必要とする関数を呼び出すのです。
次のコードは、mysqlnd の関数テーブルを置き換える方法を示しています:
query = MYSQLND_METHOD(my_conn_class, query);
}
]]>
接続関数テーブルの管理は、モジュールを初期化(MINIT)している間に行わなければなりません。関数テーブルはグローバルに共有されるリソースです。マルチスレッド環境で、TSRMを有効にしてPHPをビルドした環境では、グローバルに共有されたリソースをリクエストを処理している間に操作すると、ほぼ確実に衝突が起こります。
mysqlnd の関数テーブルを管理するときに、固定サイズを割り当てるロジックは絶対に使わないでください。新しいメソッドが関数テーブルの最後に追加される可能性があるからです。関数テーブルは将来どんな場合でも変更される可能性があります。
親クラスのメソッドを呼び出す
オリジナルの関数テーブルをバックアップしている場合、オリジナルの関数テーブルのエントリに含まれる関数を呼び出すことができます - これが親メソッドです。
場合によっては、Connection::stmt_init() のように、派生メソッドで他のあらゆる処理より先に親メソッドを呼び出すことが決定的に重要な場合があります。
プロパティを拡張する
mysqlndオブジェクトは C言語の構造体で表現されます。実行時に、C言語の構造体に新たにメンバを追加することは不可能です。mysqlndオブジェクト のユーザーは、プロパティを単純にオブジェクトに追加することはできません。
mysqlnd_plugin_get_plugin_<object>_data()ファミリーの適切な関数を使って、任意のデータ (プロパティ) を mysqlnd オブジェクトに追加することができます。オブジェクトをメモリに割り付ける際に、mysqlnd はオブジェクトの最後に 任意のデータ向けの void * ポインタを保持するためのメモリ空間を予約しておきます。 mysqlnd は プラグインひとつにつき、ひとつの void *ポインタ を保持する空間を予約しています。
次の表で、特定のプラグインでポインタの位置を計算する方法を示します:
mysqlnd のポインタの位置を計算する方法
メモリアドレス
メモリの内容
0
mysqlndオブジェクト を表現する構造体の開始
n
mysqlndオブジェクト を表現する構造体の終了
n + (m x sizeof(void*))
m 番目のプラグインのオブジェクトデータを表現する void* ポインタ
mysqlndオブジェクト のコンストラクタを継承する計画がある場合、それが許可されていることを必ず頭にいれておいてください!
次のコードはプロパティを拡張する方法を示しています:
persistent);
(*props)->query_counter = 0;
}
return props;
}
]]>
プラグインの開発者には、プラグイン用のデータに使われるメモリを管理する責任があります。
mysqlnd のメモリアロケータを使うことを推奨します。これらのメモリアロケータ関数は次のような規約に従って命名されています: mnd_*loc() mysqlnd のメモリアロケーターには役に立つ機能がいくつかあります。たとえばデバッグビルドでない環境でデバッグ用のアロケータを使う機能などです。
いつ、どのように継承するか
いつ継承するか?
各々のインスタンスが自分のプライベートな関数テーブルを持っているか?
どのように継承するか?
Connection (MYSQLND)
MINIT
No
mysqlnd_conn_get_methods()
Resultset (MYSQLND_RES)
MINIT (モジュール初期化) 時 またはその後
Yes
mysqlnd_result_get_methods() または、オブジェクトのメソッド関数テーブルを変更する
Resultset Meta (MYSQLND_RES_METADATA)
MINIT (モジュール初期化) 時
No
mysqlnd_result_metadata_get_methods()
Statement (MYSQLND_STMT)
MINIT (モジュール初期化) 時
No
mysqlnd_stmt_get_methods()
Network (MYSQLND_NET)
MINIT (モジュール初期化) 時 またはその後
Yes
mysqlnd_net_get_methods() または、オブジェクトのメソッド関数テーブルを変更する
Wire protocol (MYSQLND_PROTOCOL)
MINIT (モジュール初期化) 時 またはその後
Yes
mysqlnd_protocol_get_methods() または、オブジェクトのメソッド関数テーブルを変更する
上記の表で許可されていない場合は、モジュールを初期化した(MINIT)後のいかなる場合であっても関数テーブルを変更してはいけません。
クラスによっては、メソッドの関数テーブルへのポインタが含まれている場合があります。このようなクラスのインスタンスはすべて、同じ関数テーブルを共有しています。混乱を避けるため、特にマルチスレッドの環境下では、このような関数テーブルは MINIT (モジュール初期化) 時にだけ変更するようにしてください。
そうでないクラスでは、グローバルに共有された関数テーブルのコピーを使っています。クラスの関数テーブルのコピーがオブジェクトとともに作成されます。それぞれのオブジェクトは自分の関数テーブルを使います。これによって開発者は二つの選択肢が得られます: MINIT(モジュール初期化) 時にオブジェクトのデフォルトの関数テーブルを変更するか、同じクラスの他のインスタンスに影響を与えることなくオブジェクトのメソッドを追加で変更することができます。
関数テーブルを共有する利点は、パフォーマンスの向上です。関数テーブルをそれぞれの、すべてのオブジェクトにコピーする必要がないからです。
コンストラクタの状態
メモリ割り当て、オブジェクトの生成、リセット
変更可能か?
呼び出し元
Connection (MYSQLND)
mysqlnd_init()
No
mysqlnd_connect()
Resultset(MYSQLND_RES)
メモリ割り当て:
Connection::result_init()
リセットし、再初期化されるタイミング:
Result::use_result()
Result::store_result
Yes, ただし親メソッドを呼び出すこと!
Connection::list_fields()
Statement::get_result()
Statement::prepare() (メタデータのみ)
Statement::resultMetaData()
Resultset Meta (MYSQLND_RES_METADATA)
Connection::result_meta_init()
Yes, ただし親メソッドを呼び出すこと!
Result::read_result_metadata()
Statement (MYSQLND_STMT)
Connection::stmt_init()
Yes, ただし親メソッドを呼び出すこと!
Connection::stmt_init()
Network (MYSQLND_NET)
mysqlnd_net_init()
No
Connection::init()
Wire protocol (MYSQLND_PROTOCOL)
mysqlnd_protocol_init()
No
Connection::init()
コンストラクタを全面的に置き換えないことを強く推奨します。コンストラクタはメモリへの割り当てを実行します。mysqlndプラグインAPI と オブジェクトのロジックにとってメモリへの割り当ては決定的に重要です。開発者が警告を無視してコンストラクタへのフックを強行する場合、コンストラクタで何かをする前に親のコンストラクタを少なくとも呼び出すべきです。
すべての警告に関わらず、コンストラクタを継承することが役に立つ場合があります。コンストラクタは、オブジェクトの関数テーブルを共有されていないオブジェクトの関数テーブルと一緒に修正するのにぴったりな場所です。共有されていないオブジェクトの関数テーブルの例としては、結果セットやネットワーク、wire protocol が挙げられます。
オブジェクトの破棄に関する状態
Type
継承したメソッドは親メソッドを呼ばねばならないか?
デストラクタ
Connection
Yes, メソッドの実行後に呼び出さなければなりません
free_contents(), end_psession()
Resultset
Yes, メソッドの実行後に呼び出さなければなりません
free_result()
Resultset Meta
Yes, メソッドの実行後に呼び出さなければなりません
free()
Statement
Yes, メソッドの実行後に呼び出さなければなりません
dtor(), free_stmt_content()
Network
Yes, メソッドの実行後に呼び出さなければなりません
free()
Wire protocol
Yes, メソッドの実行後に呼び出さなければなりません
free()
デストラクタは、mysqlnd_plugin_get_plugin_<object>_data() で取得したプロパティを破棄するのに適切な場所です。
ここで挙げたデストラクタは、オブジェクトそのものを破棄する 実際の mysqlnd メソッドと一致しないかもしれません。しかし、これらのデストラクタは、開発者がフックし、プラグインデータを解放する最良の場所なのです。コンストラクタに関しては、開発者がメソッドを完全に置き換えることができるものの、推奨されません。上の表に複数のメソッドが示されていた場合、mysqlnd がどのメソッドをはじめに呼び出したかに関わらず、開発者はここで示されているすべてのメソッドをフックし、プラグインのデータを解放する必要があります。
プラグインに推奨されるやり方は、単純にメソッドをフックし、メモリを解放した後に親クラスの実装を速やかに呼び出すことです。
mysqlnd のプラグインAPI
mysqlndプラグインAPI で提供されている関数のリストを以下に示します:
mysqlnd_plugin_register()
mysqlnd_plugin_count()
mysqlnd_plugin_get_plugin_connection_data()
mysqlnd_plugin_get_plugin_result_data()
mysqlnd_plugin_get_plugin_stmt_data()
mysqlnd_plugin_get_plugin_net_data()
mysqlnd_plugin_get_plugin_protocol_data()
mysqlnd_conn_get_methods()
mysqlnd_result_get_methods()
mysqlnd_result_meta_get_methods()
mysqlnd_stmt_get_methods()
mysqlnd_net_get_methods()
mysqlnd_protocol_get_methods()
プラグインが何かとか、どのようにプラグインが機能するのか、という疑問に対する公式な定義はありません。
プラグインのメカニズムでよく見つかるコンポーネントは以下の通りです:
プラグインマネージャー
プラグインAPI
アプリケーションサービス(またはモジュール)
アプリケーションサービスAPI (またはモジュールAPI)
mysqlndプラグインのコンセプトにはこれらが取り入れられており、追加でオープンなアーキテクチャの恩恵を受けています。
mysqlnd の内部には無制限にアクセスできる
プラグインは mysqlnd の内部にすべてアクセスできます。セキュリティ上の限界や制限はありません。mysqlnd に親和性が高い、または不利なアルゴリズムを実装するためにすべてを置き換えることができます。よって、信頼できる配布元からのプラグインだけをデプロイすることを推奨します。
以前議論したとおり、プラグインはポインタを自由に使えます。これらのポインタはあらゆる点で制限されていないので、別のプラグインのデータを指すこともできます。簡単にオフセットを計算するだけで別のプラグインのデータを使うことができます。
mysqlnd と協調的なプラグインを書くこと、そして開発者はいつも親メソッドを呼び出すことを推奨します。プラグインはいつも mysqlnd と協調的であるべきです。
問題: 呼び出しの連鎖と協調の例
拡張モジュール
mysqlnd.query() の関数ポインタ
親メソッドを呼んだ場合のコールスタック
ext/mysqlnd
mysqlnd.query()
mysqlnd.query
ext/mysqlnd_cache
mysqlnd_cache.query()
mysqlnd_cache.query()
mysqlnd.query
ext/mysqlnd_monitor
mysqlnd_monitor.query()
mysqlnd_monitor.query()
mysqlnd_cache.query()
mysqlnd.query
このシナリオでは、キャッシュ(ext/mysqlnd_cache) と モニタ(ext/mysqlnd_monitor) プラグインが読み込まれています。両方とも Connection::query() を継承しています。プラグインの登録が 以前説明したロジックを使って MINIT(モジュール初期化) 時に発生します。PHP は拡張モジュールをデフォルトではアルファベット順に呼び出します。プラグインはお互いのことを知りませんし、拡張モジュールの依存性についても設定しません。
デフォルトで、プラグインは派生したメソッドの中で query メソッドの親クラスの実装を呼び出します。
PHP 拡張モジュールの動きを再現する
サンプルのプラグイン ext/mysqlnd_plugin を使うと何が起こるかを以下で再現します。このプラグインは、mysqlnd の CプラグインAPI をPHPに公開しています。
PHP で書かれた MySQL アプリケーションが 192.168.2.29 に接続を確立しようとします。
PHP アプリケーションは ext/mysql, ext/mysqli または PDO_MYSQL のいずれかを使うでしょう。これら3つの PHP MySQL 拡張モジュールは mysqlnd を使って 192.168.2.29 に接続を確立しようとします。
mysqlnd は ext/mysqlnd_plugin を継承した自分自身の connect メソッドを呼び出します。
ext/mysqlnd_plugin は ユーザーが登録した ユーザースペースのフックである proxy::connect() を呼び出します。
ユーザースペースのフックは接続先のホストIPアドレスを 192.168.2.29 から 127.0.0.1 に書き換え、parent::connect() によって確立された接続を返します。
ext/mysqlnd_plugin は オリジナルの mysqlndのメソッドを接続を確立するために呼び出します。これによって、parent::connect(127.0.0.1) を実行するのと同じことをします。
ext/mysqlnd は、接続を確立し、ext/mysqlnd_plugin に接続を返します。ext/mysqlnd_plugin も同じことをします。
どんな PHP MySQL 拡張モジュールをアプリケーションで使っていても、127.0.0.1 への接続を受け取ります。PHP MySQL 拡張モジュールそれ自体は、PHPアプリケーションにそれを返し、実行は終了します。
mysqlndプラグインの開発をはじめよう
mysqlndプラグイン それ自体が PHP拡張モジュール であることを覚えておくことが重要です。
次のコードは、典型的な mysqlnd プラグインで使われる MINIT 関数の基本構造を示します。
query = MYSQLND_METHOD(mysqlnd_plugin_conn, query);
conn_m->connect = MYSQLND_METHOD(mysqlnd_plugin_conn, connect);
}
]]>
タスクの解析: C言語からユーザースペースへ
プロセス:
PHP: ユーザーがプラグインのコールバックを登録する
PHP: ユーザーがMySQLに接続するため、任意の PHP MySQL API を呼び出す
C: ext/*mysql* が mysqlnd のメソッドを呼び出す
C: mysqlnd が ext/mysqlnd_plugin の中で終了する
C: ext/mysqlnd_plugin
ユーザースペースで登録されたコールバックを呼び出す
また、ユーザースペースのコールバックが登録されていない場合は、mysqlnd のメソッドを呼び出す。
次のことを実行する必要があります:
"mysqlnd_plugin_connection" というクラスを C言語で書く
"mysqlnd_plugin_set_conn_proxy()" 関数を使って、プロキシオブジェクトを受け入れ、登録する
C言語から、ユーザースペースへのプロキシメソッドを呼び出す (最適化 - zend_interface.h)
ユーザースペースのオブジェクトメソッドは、call_user_function() を使うか、zend_call_method() を使って Zend Engine に近いレベルで操作を行うことができます。
最適化: C言語から zend_call_method を使ってメソッドを呼び出す
次に示すコードの断片は、zend_call_method のプロトタイプを示しています。これは zend_interface.h からとってきたものです。
Zend API は2つの引数しかサポートしていません。それ以上必要な場合は、たとえば次のようにします。
この問題に対処するには、zend_call_method() のコピーを作り、追加の引数の入れ物を追加する必要があるでしょう。これは、MY_ZEND_CALL_METHOD_WRAPPER マクロの組を作ることで実現できます。
PHP のユーザースペースを呼び出す
以下のコードは、C言語からユーザースペースの関数を呼ぶための最適化されたやり方を示しています:
ユーザースペースを呼び出す: 引数が一つの場合
ユーザースペースを呼び出す: 構造体を引数にする場合
多くの mysqlnd のメソッドがはじめに引数としてとるのが C の "object" です。たとえば、 connect() メソッドのはじめの引数は MYSQLND構造体 へのポインタです。MYSQNND構造体 は、mysqlnd 接続オブジェクトを表現します。
mysqlnd接続オブジェクトへのポインタは、標準のI/Oファイルハンドルと比較できます。標準の I/Oファイルハンドルのように mysqlnd接続オブジェクトは PHP のリソース変数タイプを使ってユーザースペースに結びついていなければならないのです。
C言語とユーザースペースを行き来する
PHP ユーザーは、オーバーライドしたメソッドの親の実装を呼び出し可能にしなければいけません。
継承することで、ユーザーが選択したメソッドだけを改善し、"pre" や "post" フックを持たせるかどうかを選択できます。
ビルトインのクラス:
mysqlnd_plugin_connection::connect()