mirror of
https://github.com/php/doc-ja.git
synced 2026-03-24 07:02:08 +01:00
1198 lines
54 KiB
XML
1198 lines
54 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<!-- $Revision$ -->
|
|
<!-- EN-Revision: 9598935f21bc472f22383fb989625f0b22785331 Maintainer: mumumu Status: ready -->
|
|
<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="mysqlnd.plugin">
|
|
<title>MySQL Native Driver プラグインAPI</title>
|
|
<simpara>
|
|
The MySQL Native Driver プラグインAPI は MySQL Native Driver、略して <literal>mysqlnd</literal> が持つ機能の一つです。<literal>mysqlnd</literal> プラグインは、PHPアプリケーションと MySQL サーバーの間にあるレイヤで動きます。これは MySQL Proxy と比較できます。MySQL Proxy はあらゆる MySQL クライアントアプリケーション、たとえば PHPアプリケーション と MySQL サーバーの間のレイヤで動きます。<literal>mysqlnd</literal> プラグインは典型的な MySQL Proxy のタスク、たとえばロードバランシングやモニタリング、パフォーマンスの最適化をこなせます。アーキテクチャや置かれる場所が異なるため、<literal>mysqlnd</literal> プラグインは MySQL Proxy が持ついくつかの欠点がありません。たとえば、プラグインを使えば、単一障害点が存在しませんし、専用のプロキシサーバーをデプロイする必要もありませんし、新しいプログラミング言語(Lua) を学ぶ必要もありません。
|
|
</simpara>
|
|
<simpara>
|
|
<literal>mysqlnd</literal>プラグインは <literal>mysqlnd</literal>の拡張と考えることができます。プラグインは 多くの <literal>mysqlnd</literal> 関数の制御を奪い取ることができます。<literal>mysqlnd</literal> 関数は <literal>ext/mysql</literal> や <literal>ext/mysqli</literal>、<literal>PDO_MYSQL</literal> のような PHP の MySQL拡張モジュールによって呼び出されます。その結果として、<literal>mysqlnd</literal>プラグイン はクライアントアプリケーションからこれらの拡張モジュールへの呼び出しの制御をすべて奪うことができるのです。
|
|
</simpara>
|
|
<simpara>
|
|
内部的な <literal>mysqlnd</literal> 関数の呼び出しも制御を奪ったり、処理を置き換えたりすることができます。<literal>mysqlnd</literal> の内部的な関数テーブルを管理することに関しても全く制限がありません。ある <literal>mysqlnd</literal> 関数が <literal>mysqlnd</literal> を使う拡張モジュールによって呼び出される場合に、適切な <literal>mysqlnd</literal>プラグインの適切な関数に処理を転送するようにセットアップすることが可能です。このように、<literal>mysqlnd</literal> の内部関数テーブルを管理できることで、プラグインの柔軟性が最大限に発揮できるのです。
|
|
</simpara>
|
|
<simpara>
|
|
<literal>mysqlnd</literal>プラグインは、実際は <literal>mysqlnd</literal> のプラグインAPI (これは MySQL Native Driver, 略して <literal>mysqlnd</literal> に組み込まれています) を使い、C言語 で書かれた PHP拡張モジュール です。 プラグインは PHPアプリケーションに対して 100% 透過的です。つまり、プラグインがPHPアプリケーションとは異なるレイヤで動作するため、アプリケーションを変更する必要がないのです。<literal>mysqlnd</literal>プラグイン は、<literal>mysqlnd</literal> のひとつ下のレイヤで動くと考えることができます。
|
|
</simpara>
|
|
<simpara>
|
|
<literal>mysqlnd</literal>プラグインで実現可能なアプリケーションのリストをいくつか以下に示します
|
|
</simpara>
|
|
<itemizedlist>
|
|
<listitem>
|
|
<simpara>
|
|
ロードバランシング
|
|
</simpara>
|
|
<itemizedlist>
|
|
<listitem>
|
|
<simpara>
|
|
読み取り/書き込み の分割。例として、PECL/mysqlnd_ms (Master Slave) が挙げられます。この 拡張モジュール は 読み取り/書き込み のクエリをレプリケーションのセットアップ向けに分割します。
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
フェイルオーバー
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
ラウンドロビン, 負荷が一番低いサーバーへの転送
|
|
</simpara>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
サーバーの監視
|
|
</simpara>
|
|
<itemizedlist>
|
|
<listitem>
|
|
<simpara>
|
|
クエリのロギング
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
クエリの分析
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
クエリの監査。例として、PECL/mysqlnd_sip (SQL Injection Protection) が挙げられます。この 拡張モジュール はクエリを調べ、ルールセットに従って許可されたクエリのみを実行します。
|
|
</simpara>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
パフォーマンスの向上
|
|
</simpara>
|
|
<itemizedlist>
|
|
<listitem>
|
|
<simpara>
|
|
キャッシュ。例として、PECL/mysqlnd_qc (Query Cache) が挙げられます。
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
トラフィックの調整
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
シャーディング。例として、PECL/mysqlnd_mc (Multi Connect) が挙げられます。この拡張モジュール は、SELECT ステートメントを SELECT ... LIMIT part1, SELECT LIMIT part_n という形でn個に分割します。そしてクエリを別々の MySQLサーバーに送り、結果をクライアント側でマージします。
|
|
</simpara>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</listitem>
|
|
</itemizedlist>
|
|
<simpara>
|
|
<emphasis role="bold">利用可能な MySQL Native Driverプラグイン</emphasis>
|
|
</simpara>
|
|
<simpara>
|
|
既にたくさんの mysqlnd プラグインが利用可能になっています。以下が含まれます。
|
|
</simpara>
|
|
<itemizedlist>
|
|
<listitem>
|
|
<simpara>
|
|
<emphasis role="bold">PECL/mysqlnd_mc</emphasis> - 複数接続ができるプラグイン
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<emphasis role="bold">PECL/mysqlnd_ms</emphasis> - マスタースレーブ構成用のプラグイン
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<emphasis role="bold">PECL/mysqlnd_qc</emphasis> - クエリキャッシュプラグイン
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<emphasis role="bold">PECL/mysqlnd_pscache</emphasis> - プリペアドステートメントハンドルをキャッシュするプラグイン
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<emphasis role="bold">PECL/mysqlnd_sip</emphasis> - SQLインジェクションから防御するためのプラグイン
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<emphasis role="bold">PECL/mysqlnd_uh</emphasis> - ユーザーハンドラープラグイン
|
|
</simpara>
|
|
</listitem>
|
|
</itemizedlist>
|
|
<section xml:id="mysqlnd.plugin.mysql-proxy">
|
|
<title>mysqlndプラグイン と MySQL Proxyの比較</title>
|
|
<simpara>
|
|
<literal>mysqlnd</literal>プラグインと MySQL Proxyは、異なるアプローチを用いた異なる技術です。どちらもロードバランシングや監視、パフォーマンスの向上のような共通の様々な課題を解決するのに適したツールです。重要な違いは、MySQL Proxyがあらゆる MySQLクライアント と協調して動作するのに対して、<literal>mysqlnd</literal>プラグインは PHPアプリケーション との協調動作に特化しているということです。
|
|
</simpara>
|
|
<simpara>
|
|
PHP拡張モジュールとして、<literal>mysqlnd</literal>プラグイン はPHPの残りの部分とともに、PHPのアプリケーションサーバーにインストールされます。MySQL Proxy はPHPアプリケーションサーバー上でも動作しますし、複数のPHPアプリケーションサーバーを扱う専用マシンにもインストールできます。
|
|
</simpara>
|
|
<simpara>
|
|
MySQL Proxyをアプリケーションサーバーにデプロイすることにはふたつの利点があります:
|
|
</simpara>
|
|
<orderedlist>
|
|
<listitem>
|
|
<simpara>
|
|
単一障害点がありません
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
スケールアウトしやすい (水平方向へのスケールアウト、クライアントによるスケーリング)
|
|
</simpara>
|
|
</listitem>
|
|
</orderedlist>
|
|
<simpara>
|
|
MySQL Proxy (と、<literal>mysqlnd</literal>プラグイン) は、他のやり方だと既存のアプリケーションを変更しなければならない問題にも容易に対処することができます。
|
|
</simpara>
|
|
<simpara>
|
|
しかし、MySQL Proxyにはいくつか欠点があります:
|
|
</simpara>
|
|
<itemizedlist>
|
|
<listitem>
|
|
<simpara>
|
|
MySQL Proxy のコンポーネントと技術について新たにマスターし、デプロイしなければなりません。
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
MySQL Proxy は Lua スクリプト言語の知識が必要です。
|
|
</simpara>
|
|
</listitem>
|
|
</itemizedlist>
|
|
<simpara>
|
|
MySQL Proxy は C言語と プログラミング言語 Lua によってカスタマイズできます。Lua は MySQL Proxy において好ましいスクリプト言語です。ほとんどのPHPエキスパートにとって、Lua は新しく学ばなければならない言語です。<literal>mysqlnd</literal>プラグインは C言語で書くことができます。<link xlink:href="&url.pecl.package;mysqlnd_uh">PECL/mysqlnd_uh</link> を使って、プラグインを PHP で書くこともできます。
|
|
</simpara>
|
|
<simpara>
|
|
MySQL Proxy はデーモン - バックグラウンドのプロセスとして動作します。MySQL Proxy は初期の決定を取り消すこともできますが、すべての状態を保持することできます。しかしながら <literal>mysqlnd</literal>プラグインは、PHPのリクエスト単位のライフサイクルに結びついています。MySQL Proxy は一度計算された結果を複数のアプリケーションサーバーで共有できます。<literal>mysqlnd</literal>プラグインでこれを行うには、永続的なストレージにデータを保存する必要があります。この目的のためには Memcache のような別のデーモンが必要です。この場合は、MySQL Proxy に有利です。
|
|
</simpara>
|
|
<simpara>
|
|
MySQL Proxy は wire protocol (訳注:ネットワークを通じてデータを転送するプロトコル。<link xlink:href="http://en.wikipedia.org/wiki/Wire_protocol">WikipediaでのWire Protocolの説明</link>, <link xlink:href="http://stackoverflow.com/questions/2324089/can-someone-explain-what-a-wire-level-protocol-is">StackOverflow でのWire Protocolの説明</link>) の上で動作します。MySQL Proxy を使うと、MySQLクライアントサーバープロトコルを解析し、リバースエンジニアリングしなければなりません。MySQL Proxy でできることは、通信プロトコルを管理することで達成できることに限られます。wire protocol が変更(滅多にありませんが)されると、MySQL Proxy のスクリプトも変更する必要があります。
|
|
</simpara>
|
|
<simpara>
|
|
<literal>mysqlnd</literal>プラグインは C言語のAPI上で動作します。このAPIは <literal>libmysqlclient</literal>クライアント の動きをそのままコピーしています。この C言語のAPI は、 基本的に MySQLクライアントサーバープロトコル、時に wire protocol と呼ばれるもののラッパーです。開発者は C言語のAPI呼び出しの制御を奪うことができます。それゆえに、wire protocol レベルのプログラムに一切変更を加えることなく、すべてのPHP呼び出しをフックできるのです。
|
|
</simpara>
|
|
<simpara>
|
|
<literal>mysqlnd</literal> は wire protocol を実装しています。そのため、プラグインは通信プロトコルを解析し、リバースエンジニアリングし、管理できるばかりか、通信プロトコルを置き換えることだってできます。ただ、こんなことをする必要は通常ありません。
|
|
</simpara>
|
|
<simpara>
|
|
プラグインが 2つのレベル(C言語のAPI と wire protocol) を使って実装できるので、MySQL Proxy よりも大きな柔軟性を得られます。 <literal>mysqlnd</literal>プラグインが C言語のAPI を使って実装されれば、wire protocol に対していかなる変更が行われても、プラグインへの変更は必要ありません。
|
|
</simpara>
|
|
</section>
|
|
<section xml:id="mysqlnd.plugin.obtaining">
|
|
<title>mysqlnd plugin APIを取得する</title>
|
|
<simpara>
|
|
<literal>mysqlnd</literal>プラグインAPI は、MySQL Native Driver PHP拡張モジュールである <literal>ext/mysqlnd</literal> の一部です。2009年12月に <literal>mysqlnd</literal>プラグインAPI の開発がスタートしました。これは PHP のソースリポジトリの一部として開発が進められ、git経由で公開されたり、ソースコードのスナップショットがダウンロード可能になっています。
|
|
</simpara>
|
|
<simpara>
|
|
プラグインの開発者は <literal>mysqlnd</literal> のバージョンを <literal>MYSQLND_VERSION</literal> にアクセスすることで決めることができます。この値は、<quote>mysqlnd 8.3.17</quote> という文字列フォーマットです。<literal>MYSQLND_VERSION_ID</literal> という値にもアクセスできます。これはたとえば、50007 のような数値です。 開発者は次のようにしてバージョン番号を計算することができます:
|
|
</simpara>
|
|
<table xml:id="mysqlnd.plugin.version-id">
|
|
<title>MYSQLND_VERSION_ID 計算テーブル</title>
|
|
<tgroup cols="2">
|
|
<thead>
|
|
<row>
|
|
<entry>Version (一部)</entry>
|
|
<entry>例</entry>
|
|
</row>
|
|
</thead>
|
|
<tbody>
|
|
<row>
|
|
<entry>Major*10000</entry>
|
|
<entry>5*10000 = 50000</entry>
|
|
</row>
|
|
<row>
|
|
<entry>Minor*100</entry>
|
|
<entry>0*100 = 0</entry>
|
|
</row>
|
|
<row>
|
|
<entry>Patch</entry>
|
|
<entry>7 = 7</entry>
|
|
</row>
|
|
<row>
|
|
<entry>MYSQLND_VERSION_ID</entry>
|
|
<entry>50007</entry>
|
|
</row>
|
|
</tbody>
|
|
</tgroup>
|
|
</table>
|
|
<simpara>
|
|
開発している間は、開発者は <literal>mysqlnd</literal> のバージョン番号を互換性と対応バージョンのテストとして参照すべきです。PHP本体のひとつのバージョンにおける開発のライフサイクルの間に、<literal>mysqlnd</literal> の開発イテレーションは複数行われる場合があるためです。
|
|
</simpara>
|
|
</section>
|
|
<section xml:id="mysqlnd.plugin.architecture">
|
|
<title>MySQL Native Driverプラグイン のアーキテクチャ</title>
|
|
<simpara>
|
|
このセクションでは、<literal>mysqlnd</literal>プラグイン のアーキテクチャについての概要を示します。
|
|
</simpara>
|
|
<simpara>
|
|
<emphasis role="bold"> MySQL Native Driver の概要</emphasis>
|
|
</simpara>
|
|
<simpara>
|
|
<literal>mysqlnd</literal>プラグイン を開発する前に、<literal>mysqlnd</literal> そのものがどのように成り立っているのかを少し知っておくと役に立ちます。<literal>mysqlnd</literal> は次に示すモジュールからなります:
|
|
</simpara>
|
|
<table xml:id="mysqlnd.plugin.orgchart">
|
|
<title>mysqlnd のモジュール毎のソースコードの組み合わせを示した表</title>
|
|
<tgroup cols="2">
|
|
<thead>
|
|
<row>
|
|
<entry>モジュールの統計情報</entry>
|
|
<entry>mysqlnd_statistics.c</entry>
|
|
</row>
|
|
</thead>
|
|
<tbody>
|
|
<row>
|
|
<entry>データベース接続</entry>
|
|
<entry>mysqlnd.c</entry>
|
|
</row>
|
|
<row>
|
|
<entry>結果セット</entry>
|
|
<entry>mysqlnd_result.c</entry>
|
|
</row>
|
|
<row>
|
|
<entry>結果セットのメタデータ</entry>
|
|
<entry>mysqlnd_result_meta.c</entry>
|
|
</row>
|
|
<row>
|
|
<entry>プリペアドステートメント</entry>
|
|
<entry>mysqlnd_ps.c</entry>
|
|
</row>
|
|
<row>
|
|
<entry>ネットワーク</entry>
|
|
<entry>mysqlnd_net.c</entry>
|
|
</row>
|
|
<row>
|
|
<entry>Wire protocol</entry>
|
|
<entry>mysqlnd_wireprotocol.c</entry>
|
|
</row>
|
|
</tbody>
|
|
</tgroup>
|
|
</table>
|
|
<simpara>
|
|
<emphasis role="bold">C言語のオブジェクト指向パラダイム</emphasis>
|
|
</simpara>
|
|
<simpara>
|
|
ソースコードレベルで、<literal>mysqlnd</literal> は オブジェクト指向を実装するためのパターンを採用しています。
|
|
</simpara>
|
|
<simpara>
|
|
C言語では、オブジェクトを表現するために <literal>struct</literal>(構造体) を使います。struct のメンバがオブジェクトのプロパティを表現します。関数を指している struct のメンバがメソッドを表現します。
|
|
</simpara>
|
|
<simpara>
|
|
C++ や Java のような言語と異なり、C言語におけるオブジェクト指向のパラダイムでは決まった継承のルールがありませんが、従う必要があるルールがいくつかあります。このルールについては後に述べます。
|
|
</simpara>
|
|
<simpara>
|
|
<emphasis role="bold">PHP のライフサイクル</emphasis>
|
|
</simpara>
|
|
<simpara>
|
|
PHP のライフサイクルを考えるとき、ふたつの基本的なサイクルが存在します。
|
|
</simpara>
|
|
<itemizedlist>
|
|
<listitem>
|
|
<simpara>
|
|
PHPエンジンの起動と終了までのサイクル
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
リクエスト毎のライフサイクル
|
|
</simpara>
|
|
</listitem>
|
|
</itemizedlist>
|
|
<simpara>
|
|
PHPエンジンが起動するとき、PHP はモジュールを初期化する (MINIT) 関数を登録された拡張モジュールごとに呼び出します。これによって、各々のモジュールが PHPエンジンが処理を行うライフサイクルの間存在するリソースを割り当てたり、変数を定義することができます。PHPエンジンが終了するときには、 エンジンが終了(MSHUTDOWN)関数を拡張モジュール毎に呼び出します。
|
|
</simpara>
|
|
<simpara>
|
|
PHPエンジンが起動している間、エンジンはたくさんのリクエストを受けとります。それぞれのリクエストは別のライフサイクルを構成します。リクエスト毎にPHPエンジンはリクエストの初期化関数を拡張モジュール毎に呼び出します。拡張モジュール側では、リクエストの処理に必要な変数の定義やリソースの割り当てを行うことができます。リクエストのサイクルが終了するときは、PHPエンジンがリクエストの終了(RSHUTDOWN)関数を拡張モジュール毎に呼び出します。これによって、拡張モジュールは必要となるあらゆるクリーンアップ処理を行うことができます。
|
|
</simpara>
|
|
<simpara>
|
|
<emphasis role="bold">プラグインはどうやって動くか</emphasis>
|
|
</simpara>
|
|
<simpara>
|
|
<literal>mysqlnd</literal>プラグイン は <literal>mysqlnd</literal> を使う拡張モジュールが <literal>mysqlnd</literal> を呼び出すときの制御を奪うことによって動作します。これは <literal>mysqlnd</literal> の関数テーブルを取得し、バックアップし、カスタムの関数テーブルと置き換えることによって実現されます。この関数テーブルが、プラグインが必要とする関数を呼び出すのです。
|
|
</simpara>
|
|
<simpara>
|
|
次のコードは、<literal>mysqlnd</literal> の関数テーブルを置き換える方法を示しています:
|
|
</simpara>
|
|
<programlisting>
|
|
<![CDATA[
|
|
/* a place to store original function table */
|
|
struct st_mysqlnd_conn_methods org_methods;
|
|
|
|
void minit_register_hooks(TSRMLS_D) {
|
|
/* active function table */
|
|
struct st_mysqlnd_conn_methods * current_methods
|
|
= mysqlnd_conn_get_methods();
|
|
|
|
/* backup original function table */
|
|
memcpy(&org_methods, current_methods,
|
|
sizeof(struct st_mysqlnd_conn_methods);
|
|
|
|
/* install new methods */
|
|
current_methods->query = MYSQLND_METHOD(my_conn_class, query);
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
<simpara>
|
|
接続関数テーブルの管理は、モジュールを初期化(MINIT)している間に行わなければなりません。関数テーブルはグローバルに共有されるリソースです。マルチスレッド環境で、TSRMを有効にしてPHPをビルドした環境では、グローバルに共有されたリソースをリクエストを処理している間に操作すると、ほぼ確実に衝突が起こります。
|
|
</simpara>
|
|
<note>
|
|
<simpara>
|
|
<literal>mysqlnd</literal> の関数テーブルを管理するときに、固定サイズを割り当てるロジックは絶対に使わないでください。新しいメソッドが関数テーブルの最後に追加される可能性があるからです。関数テーブルは将来どんな場合でも変更される可能性があります。
|
|
</simpara>
|
|
</note>
|
|
<simpara>
|
|
<emphasis role="bold">親クラスのメソッドを呼び出す</emphasis>
|
|
</simpara>
|
|
<simpara>
|
|
オリジナルの関数テーブルをバックアップしている場合、オリジナルの関数テーブルのエントリに含まれる関数を呼び出すことができます - これが親メソッドです。
|
|
</simpara>
|
|
<simpara>
|
|
場合によっては、<literal>Connection::stmt_init()</literal> のように、派生メソッドで他のあらゆる処理より先に親メソッドを呼び出すことが決定的に重要な場合があります。
|
|
</simpara>
|
|
<programlisting>
|
|
<![CDATA[
|
|
MYSQLND_METHOD(my_conn_class, query)(MYSQLND *conn,
|
|
const char *query, unsigned int query_len TSRMLS_DC) {
|
|
|
|
php_printf("my_conn_class::query(query = %s)\n", query);
|
|
|
|
query = "SELECT 'query rewritten' FROM DUAL";
|
|
query_len = strlen(query);
|
|
|
|
return org_methods.query(conn, query, query_len); /* return with call to parent */
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
<simpara>
|
|
<emphasis role="bold">プロパティを拡張する</emphasis>
|
|
</simpara>
|
|
<simpara>
|
|
<literal>mysqlnd</literal>オブジェクトは C言語の構造体で表現されます。実行時に、C言語の構造体に新たにメンバを追加することは不可能です。<literal>mysqlnd</literal>オブジェクト のユーザーは、プロパティを単純にオブジェクトに追加することはできません。
|
|
</simpara>
|
|
<simpara>
|
|
<literal>mysqlnd_plugin_get_plugin_<object>_data()</literal>ファミリーの適切な関数を使って、任意のデータ (プロパティ) を <literal>mysqlnd</literal> オブジェクトに追加することができます。オブジェクトをメモリに割り付ける際に、<literal>mysqlnd</literal> はオブジェクトの最後に 任意のデータ向けの <literal>void *</literal> ポインタを保持するためのメモリ空間を予約しておきます。 <literal>mysqlnd</literal> は プラグインひとつにつき、ひとつの <literal>void *</literal>ポインタ を保持する空間を予約しています。
|
|
</simpara>
|
|
<simpara>
|
|
次の表で、特定のプラグインでポインタの位置を計算する方法を示します:
|
|
</simpara>
|
|
<table xml:id="mysqlnd.plugin.pointercalc">
|
|
<title>mysqlnd のポインタの位置を計算する方法</title>
|
|
<tgroup cols="2">
|
|
<thead>
|
|
<row>
|
|
<entry>メモリアドレス</entry>
|
|
<entry>メモリの内容</entry>
|
|
</row>
|
|
</thead>
|
|
<tbody>
|
|
<row>
|
|
<entry>0</entry>
|
|
<entry>mysqlndオブジェクト を表現する構造体の開始</entry>
|
|
</row>
|
|
<row>
|
|
<entry>n</entry>
|
|
<entry>mysqlndオブジェクト を表現する構造体の終了</entry>
|
|
</row>
|
|
<row>
|
|
<entry>n + (m x sizeof(void*))</entry>
|
|
<entry>m 番目のプラグインのオブジェクトデータを表現する void* ポインタ</entry>
|
|
</row>
|
|
</tbody>
|
|
</tgroup>
|
|
</table>
|
|
<simpara>
|
|
<literal>mysqlnd</literal>オブジェクト のコンストラクタを継承する計画がある場合、それが許可されていることを必ず頭にいれておいてください!
|
|
</simpara>
|
|
<simpara>
|
|
次のコードはプロパティを拡張する方法を示しています:
|
|
</simpara>
|
|
<programlisting>
|
|
<![CDATA[
|
|
/* any data we want to associate */
|
|
typedef struct my_conn_properties {
|
|
unsigned long query_counter;
|
|
} MY_CONN_PROPERTIES;
|
|
|
|
/* plugin id */
|
|
unsigned int my_plugin_id;
|
|
|
|
void minit_register_hooks(TSRMLS_D) {
|
|
/* obtain unique plugin ID */
|
|
my_plugin_id = mysqlnd_plugin_register();
|
|
/* snip - see Extending Connection: methods */
|
|
}
|
|
|
|
static MY_CONN_PROPERTIES** get_conn_properties(const MYSQLND *conn TSRMLS_DC) {
|
|
MY_CONN_PROPERTIES** props;
|
|
props = (MY_CONN_PROPERTIES**)mysqlnd_plugin_get_plugin_connection_data(
|
|
conn, my_plugin_id);
|
|
if (!props || !(*props)) {
|
|
*props = mnd_pecalloc(1, sizeof(MY_CONN_PROPERTIES), conn->persistent);
|
|
(*props)->query_counter = 0;
|
|
}
|
|
return props;
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
<simpara>
|
|
プラグインの開発者には、プラグイン用のデータに使われるメモリを管理する責任があります。
|
|
</simpara>
|
|
<simpara>
|
|
<literal>mysqlnd</literal> のメモリアロケータを使うことを推奨します。これらのメモリアロケータ関数は次のような規約に従って命名されています: <literal>mnd_*loc()</literal> <literal>mysqlnd</literal> のメモリアロケーターには役に立つ機能がいくつかあります。たとえばデバッグビルドでない環境でデバッグ用のアロケータを使う機能などです。
|
|
</simpara>
|
|
<table xml:id="mysqlnd.plugin.subclass">
|
|
<title>いつ、どのように継承するか</title>
|
|
<tgroup cols="4">
|
|
<thead>
|
|
<row>
|
|
<entry/>
|
|
<entry>いつ継承するか?</entry>
|
|
<entry>各々のインスタンスが自分のプライベートな関数テーブルを持っているか?</entry>
|
|
<entry>どのように継承するか?</entry>
|
|
</row>
|
|
</thead>
|
|
<tbody>
|
|
<row>
|
|
<entry>Connection (MYSQLND)</entry>
|
|
<entry>MINIT</entry>
|
|
<entry>No</entry>
|
|
<entry>mysqlnd_conn_get_methods()</entry>
|
|
</row>
|
|
<row>
|
|
<entry>Resultset (MYSQLND_RES)</entry>
|
|
<entry>MINIT (モジュール初期化) 時 またはその後</entry>
|
|
<entry>Yes</entry>
|
|
<entry>mysqlnd_result_get_methods() または、オブジェクトのメソッド関数テーブルを変更する</entry>
|
|
</row>
|
|
<row>
|
|
<entry>Resultset Meta (MYSQLND_RES_METADATA)</entry>
|
|
<entry>MINIT (モジュール初期化) 時</entry>
|
|
<entry>No</entry>
|
|
<entry>mysqlnd_result_metadata_get_methods()</entry>
|
|
</row>
|
|
<row>
|
|
<entry>Statement (MYSQLND_STMT)</entry>
|
|
<entry>MINIT (モジュール初期化) 時</entry>
|
|
<entry>No</entry>
|
|
<entry>mysqlnd_stmt_get_methods()</entry>
|
|
</row>
|
|
<row>
|
|
<entry>Network (MYSQLND_NET)</entry>
|
|
<entry>MINIT (モジュール初期化) 時 またはその後</entry>
|
|
<entry>Yes</entry>
|
|
<entry>mysqlnd_net_get_methods() または、オブジェクトのメソッド関数テーブルを変更する</entry>
|
|
</row>
|
|
<row>
|
|
<entry>Wire protocol (MYSQLND_PROTOCOL)</entry>
|
|
<entry>MINIT (モジュール初期化) 時 またはその後</entry>
|
|
<entry>Yes</entry>
|
|
<entry>mysqlnd_protocol_get_methods() または、オブジェクトのメソッド関数テーブルを変更する</entry>
|
|
</row>
|
|
</tbody>
|
|
</tgroup>
|
|
</table>
|
|
<simpara>
|
|
上記の表で許可されていない場合は、モジュールを初期化した(MINIT)後のいかなる場合であっても関数テーブルを変更してはいけません。
|
|
</simpara>
|
|
<simpara>
|
|
クラスによっては、メソッドの関数テーブルへのポインタが含まれている場合があります。このようなクラスのインスタンスはすべて、同じ関数テーブルを共有しています。混乱を避けるため、特にマルチスレッドの環境下では、このような関数テーブルは MINIT (モジュール初期化) 時にだけ変更するようにしてください。
|
|
</simpara>
|
|
<simpara>
|
|
そうでないクラスでは、グローバルに共有された関数テーブルのコピーを使っています。クラスの関数テーブルのコピーがオブジェクトとともに作成されます。それぞれのオブジェクトは自分の関数テーブルを使います。これによって開発者は二つの選択肢が得られます: MINIT(モジュール初期化) 時にオブジェクトのデフォルトの関数テーブルを変更するか、同じクラスの他のインスタンスに影響を与えることなくオブジェクトのメソッドを追加で変更することができます。
|
|
</simpara>
|
|
<simpara>
|
|
関数テーブルを共有する利点は、パフォーマンスの向上です。関数テーブルをそれぞれの、すべてのオブジェクトにコピーする必要がないからです。
|
|
</simpara>
|
|
<table xml:id="mysqlnd.plugin.constatus">
|
|
<title>コンストラクタの状態</title>
|
|
<tgroup cols="4">
|
|
<thead>
|
|
<row>
|
|
<entry/>
|
|
<entry>メモリ割り当て、オブジェクトの生成、リセット</entry>
|
|
<entry>変更可能か?</entry>
|
|
<entry>呼び出し元</entry>
|
|
</row>
|
|
</thead>
|
|
<tbody>
|
|
<row>
|
|
<entry>Connection (MYSQLND)</entry>
|
|
<entry>mysqlnd_init()</entry>
|
|
<entry>No</entry>
|
|
<entry>mysqlnd_connect()</entry>
|
|
</row>
|
|
<row>
|
|
<entry>Resultset(MYSQLND_RES)</entry>
|
|
<entry><simpara>
|
|
メモリ割り当て:
|
|
</simpara>
|
|
<itemizedlist>
|
|
<listitem>
|
|
<simpara>
|
|
Connection::result_init()
|
|
</simpara>
|
|
</listitem>
|
|
</itemizedlist>
|
|
<simpara>
|
|
リセットし、再初期化されるタイミング:
|
|
</simpara>
|
|
<itemizedlist>
|
|
<listitem>
|
|
<simpara>
|
|
Result::use_result()
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
Result::store_result
|
|
</simpara>
|
|
</listitem>
|
|
</itemizedlist></entry>
|
|
<entry>Yes, ただし親メソッドを呼び出すこと!</entry>
|
|
<entry><itemizedlist>
|
|
<listitem>
|
|
<simpara>
|
|
Connection::list_fields()
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
Statement::get_result()
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
Statement::prepare() (メタデータのみ)
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
Statement::resultMetaData()
|
|
</simpara>
|
|
</listitem>
|
|
</itemizedlist></entry>
|
|
</row>
|
|
<row>
|
|
<entry>Resultset Meta (MYSQLND_RES_METADATA)</entry>
|
|
<entry>Connection::result_meta_init()</entry>
|
|
<entry>Yes, ただし親メソッドを呼び出すこと!</entry>
|
|
<entry>Result::read_result_metadata()</entry>
|
|
</row>
|
|
<row>
|
|
<entry>Statement (MYSQLND_STMT)</entry>
|
|
<entry>Connection::stmt_init()</entry>
|
|
<entry>Yes, ただし親メソッドを呼び出すこと!</entry>
|
|
<entry>Connection::stmt_init()</entry>
|
|
</row>
|
|
<row>
|
|
<entry>Network (MYSQLND_NET)</entry>
|
|
<entry>mysqlnd_net_init()</entry>
|
|
<entry>No</entry>
|
|
<entry>Connection::init()</entry>
|
|
</row>
|
|
<row>
|
|
<entry>Wire protocol (MYSQLND_PROTOCOL)</entry>
|
|
<entry>mysqlnd_protocol_init()</entry>
|
|
<entry>No</entry>
|
|
<entry>Connection::init()</entry>
|
|
</row>
|
|
</tbody>
|
|
</tgroup>
|
|
</table>
|
|
<simpara>
|
|
コンストラクタを全面的に置き換えないことを強く推奨します。コンストラクタはメモリへの割り当てを実行します。<literal>mysqlnd</literal>プラグインAPI と オブジェクトのロジックにとってメモリへの割り当ては決定的に重要です。開発者が警告を無視してコンストラクタへのフックを強行する場合、コンストラクタで何かをする前に親のコンストラクタを少なくとも呼び出すべきです。
|
|
</simpara>
|
|
<simpara>
|
|
すべての警告に関わらず、コンストラクタを継承することが役に立つ場合があります。コンストラクタは、オブジェクトの関数テーブルを共有されていないオブジェクトの関数テーブルと一緒に修正するのにぴったりな場所です。共有されていないオブジェクトの関数テーブルの例としては、結果セットやネットワーク、wire protocol が挙げられます。
|
|
</simpara>
|
|
<table xml:id="mysqlnd.plugin.deststatus">
|
|
<title>オブジェクトの破棄に関する状態</title>
|
|
<tgroup cols="3">
|
|
<thead>
|
|
<row>
|
|
<entry>Type</entry>
|
|
<entry>継承したメソッドは親メソッドを呼ばねばならないか?</entry>
|
|
<entry>デストラクタ</entry>
|
|
</row>
|
|
</thead>
|
|
<tbody>
|
|
<row>
|
|
<entry>Connection</entry>
|
|
<entry>Yes, メソッドの実行後に呼び出さなければなりません</entry>
|
|
<entry>free_contents(), end_psession()</entry>
|
|
</row>
|
|
<row>
|
|
<entry>Resultset</entry>
|
|
<entry>Yes, メソッドの実行後に呼び出さなければなりません</entry>
|
|
<entry>free_result()</entry>
|
|
</row>
|
|
<row>
|
|
<entry>Resultset Meta</entry>
|
|
<entry>Yes, メソッドの実行後に呼び出さなければなりません</entry>
|
|
<entry>free()</entry>
|
|
</row>
|
|
<row>
|
|
<entry>Statement</entry>
|
|
<entry>Yes, メソッドの実行後に呼び出さなければなりません</entry>
|
|
<entry>dtor(), free_stmt_content()</entry>
|
|
</row>
|
|
<row>
|
|
<entry>Network</entry>
|
|
<entry>Yes, メソッドの実行後に呼び出さなければなりません</entry>
|
|
<entry>free()</entry>
|
|
</row>
|
|
<row>
|
|
<entry>Wire protocol</entry>
|
|
<entry>Yes, メソッドの実行後に呼び出さなければなりません</entry>
|
|
<entry>free()</entry>
|
|
</row>
|
|
</tbody>
|
|
</tgroup>
|
|
</table>
|
|
<simpara>
|
|
デストラクタは、<literal>mysqlnd_plugin_get_plugin_<replaceable><object></replaceable>_data()</literal> で取得したプロパティを破棄するのに適切な場所です。
|
|
</simpara>
|
|
<simpara>
|
|
ここで挙げたデストラクタは、オブジェクトそのものを破棄する 実際の <literal>mysqlnd</literal> メソッドと一致しないかもしれません。しかし、これらのデストラクタは、開発者がフックし、プラグインデータを解放する最良の場所なのです。コンストラクタに関しては、開発者がメソッドを完全に置き換えることができるものの、推奨されません。上の表に複数のメソッドが示されていた場合、<literal>mysqlnd</literal> がどのメソッドをはじめに呼び出したかに関わらず、開発者はここで示されているすべてのメソッドをフックし、プラグインのデータを解放する必要があります。
|
|
</simpara>
|
|
<simpara>
|
|
プラグインに推奨されるやり方は、単純にメソッドをフックし、メモリを解放した後に親クラスの実装を速やかに呼び出すことです。
|
|
</simpara>
|
|
</section>
|
|
<section xml:id="mysqlnd.plugin.api">
|
|
<title>mysqlnd のプラグインAPI</title>
|
|
<simpara>
|
|
<literal>mysqlnd</literal>プラグインAPI で提供されている関数のリストを以下に示します:
|
|
</simpara>
|
|
<itemizedlist>
|
|
<listitem>
|
|
<simpara>
|
|
mysqlnd_plugin_register()
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
mysqlnd_plugin_count()
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
mysqlnd_plugin_get_plugin_connection_data()
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
mysqlnd_plugin_get_plugin_result_data()
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
mysqlnd_plugin_get_plugin_stmt_data()
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
mysqlnd_plugin_get_plugin_net_data()
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
mysqlnd_plugin_get_plugin_protocol_data()
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
mysqlnd_conn_get_methods()
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
mysqlnd_result_get_methods()
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
mysqlnd_result_meta_get_methods()
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
mysqlnd_stmt_get_methods()
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
mysqlnd_net_get_methods()
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
mysqlnd_protocol_get_methods()
|
|
</simpara>
|
|
</listitem>
|
|
</itemizedlist>
|
|
<simpara>
|
|
プラグインが何かとか、どのようにプラグインが機能するのか、という疑問に対する公式な定義はありません。
|
|
</simpara>
|
|
<simpara>
|
|
プラグインのメカニズムでよく見つかるコンポーネントは以下の通りです:
|
|
</simpara>
|
|
<itemizedlist>
|
|
<listitem>
|
|
<simpara>
|
|
プラグインマネージャー
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
プラグインAPI
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
アプリケーションサービス(またはモジュール)
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
アプリケーションサービスAPI (またはモジュールAPI)
|
|
</simpara>
|
|
</listitem>
|
|
</itemizedlist>
|
|
<simpara>
|
|
<literal>mysqlnd</literal>プラグインのコンセプトにはこれらが取り入れられており、追加でオープンなアーキテクチャの恩恵を受けています。
|
|
</simpara>
|
|
<simpara>
|
|
<emphasis role="bold"> mysqlnd の内部には無制限にアクセスできる </emphasis>
|
|
</simpara>
|
|
<simpara>
|
|
プラグインは <literal>mysqlnd</literal> の内部にすべてアクセスできます。セキュリティ上の限界や制限はありません。mysqlnd に親和性が高い、または不利なアルゴリズムを実装するためにすべてを置き換えることができます。よって、信頼できる配布元からのプラグインだけをデプロイすることを推奨します。
|
|
</simpara>
|
|
<simpara>
|
|
以前議論したとおり、プラグインはポインタを自由に使えます。これらのポインタはあらゆる点で制限されていないので、別のプラグインのデータを指すこともできます。簡単にオフセットを計算するだけで別のプラグインのデータを使うことができます。
|
|
</simpara>
|
|
<simpara>
|
|
mysqlnd と協調的なプラグインを書くこと、そして開発者はいつも親メソッドを呼び出すことを推奨します。プラグインはいつも <literal>mysqlnd</literal> と協調的であるべきです。
|
|
</simpara>
|
|
<table xml:id="mysqlnd.plugin.chaining">
|
|
<title>問題: 呼び出しの連鎖と協調の例</title>
|
|
<tgroup cols="3">
|
|
<thead>
|
|
<row>
|
|
<entry>拡張モジュール</entry>
|
|
<entry>mysqlnd.query() の関数ポインタ</entry>
|
|
<entry>親メソッドを呼んだ場合のコールスタック</entry>
|
|
</row>
|
|
</thead>
|
|
<tbody>
|
|
<row>
|
|
<entry>ext/mysqlnd</entry>
|
|
<entry>mysqlnd.query()</entry>
|
|
<entry>mysqlnd.query</entry>
|
|
</row>
|
|
<row>
|
|
<entry>ext/mysqlnd_cache</entry>
|
|
<entry>mysqlnd_cache.query()</entry>
|
|
<entry><orderedlist>
|
|
<listitem>
|
|
<simpara>
|
|
mysqlnd_cache.query()
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
mysqlnd.query
|
|
</simpara>
|
|
</listitem>
|
|
</orderedlist></entry>
|
|
</row>
|
|
<row>
|
|
<entry>ext/mysqlnd_monitor</entry>
|
|
<entry>mysqlnd_monitor.query()</entry>
|
|
<entry><orderedlist>
|
|
<listitem>
|
|
<simpara>
|
|
mysqlnd_monitor.query()
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
mysqlnd_cache.query()
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
mysqlnd.query
|
|
</simpara>
|
|
</listitem>
|
|
</orderedlist></entry>
|
|
</row>
|
|
</tbody>
|
|
</tgroup>
|
|
</table>
|
|
<simpara>
|
|
このシナリオでは、キャッシュ(<literal>ext/mysqlnd_cache</literal>) と モニタ(<literal>ext/mysqlnd_monitor</literal>) プラグインが読み込まれています。両方とも <literal>Connection::query()</literal> を継承しています。プラグインの登録が 以前説明したロジックを使って <literal>MINIT</literal>(モジュール初期化) 時に発生します。PHP は拡張モジュールをデフォルトではアルファベット順に呼び出します。プラグインはお互いのことを知りませんし、拡張モジュールの依存性についても設定しません。
|
|
</simpara>
|
|
<simpara>
|
|
デフォルトで、プラグインは派生したメソッドの中で query メソッドの親クラスの実装を呼び出します。
|
|
</simpara>
|
|
<simpara>
|
|
<emphasis role="bold"> PHP 拡張モジュールの動きを再現する </emphasis>
|
|
</simpara>
|
|
<simpara>
|
|
サンプルのプラグイン <literal>ext/mysqlnd_plugin</literal> を使うと何が起こるかを以下で再現します。このプラグインは、<literal>mysqlnd</literal> の CプラグインAPI をPHPに公開しています。
|
|
</simpara>
|
|
<itemizedlist>
|
|
<listitem>
|
|
<simpara>
|
|
PHP で書かれた MySQL アプリケーションが 192.168.2.29 に接続を確立しようとします。
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
PHP アプリケーションは <literal>ext/mysql</literal>, <literal>ext/mysqli</literal> または <literal>PDO_MYSQL</literal> のいずれかを使うでしょう。これら3つの PHP MySQL 拡張モジュールは <literal>mysqlnd</literal> を使って 192.168.2.29 に接続を確立しようとします。
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<literal>mysqlnd</literal> は <literal>ext/mysqlnd_plugin</literal> を継承した自分自身の connect メソッドを呼び出します。
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<literal>ext/mysqlnd_plugin</literal> は ユーザーが登録した ユーザースペースのフックである <literal>proxy::connect()</literal> を呼び出します。
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
ユーザースペースのフックは接続先のホストIPアドレスを 192.168.2.29 から 127.0.0.1 に書き換え、<literal>parent::connect()</literal> によって確立された接続を返します。
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<literal>ext/mysqlnd_plugin</literal> は オリジナルの <literal>mysqlnd</literal>のメソッドを接続を確立するために呼び出します。これによって、<literal>parent::connect(127.0.0.1)</literal> を実行するのと同じことをします。
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
<literal>ext/mysqlnd</literal> は、接続を確立し、<literal>ext/mysqlnd_plugin</literal> に接続を返します。<literal>ext/mysqlnd_plugin</literal> も同じことをします。
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
どんな PHP MySQL 拡張モジュールをアプリケーションで使っていても、127.0.0.1 への接続を受け取ります。PHP MySQL 拡張モジュールそれ自体は、PHPアプリケーションにそれを返し、実行は終了します。
|
|
</simpara>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</section>
|
|
<section xml:id="mysqlnd.plugin.developing">
|
|
<title>mysqlndプラグインの開発をはじめよう</title>
|
|
<simpara>
|
|
<literal>mysqlnd</literal>プラグイン それ自体が PHP拡張モジュール であることを覚えておくことが重要です。
|
|
</simpara>
|
|
<simpara>
|
|
次のコードは、典型的な <literal>mysqlnd</literal> プラグインで使われる MINIT 関数の基本構造を示します。
|
|
</simpara>
|
|
<programlisting>
|
|
<![CDATA[
|
|
/* my_php_mysqlnd_plugin.c */
|
|
|
|
static PHP_MINIT_FUNCTION(mysqlnd_plugin) {
|
|
/* globals, ini entries, resources, classes */
|
|
|
|
/* register mysqlnd plugin */
|
|
mysqlnd_plugin_id = mysqlnd_plugin_register();
|
|
|
|
conn_m = mysqlnd_get_conn_methods();
|
|
memcpy(org_conn_m, conn_m,
|
|
sizeof(struct st_mysqlnd_conn_methods));
|
|
|
|
conn_m->query = MYSQLND_METHOD(mysqlnd_plugin_conn, query);
|
|
conn_m->connect = MYSQLND_METHOD(mysqlnd_plugin_conn, connect);
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
<programlisting>
|
|
<![CDATA[
|
|
/* my_mysqlnd_plugin.c */
|
|
|
|
enum_func_status MYSQLND_METHOD(mysqlnd_plugin_conn, query)(/* ... */) {
|
|
/* ... */
|
|
}
|
|
enum_func_status MYSQLND_METHOD(mysqlnd_plugin_conn, connect)(/* ... */) {
|
|
/* ... */
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
<simpara>
|
|
<emphasis role="bold">タスクの解析: C言語からユーザースペースへ</emphasis>
|
|
</simpara>
|
|
<programlisting>
|
|
<![CDATA[
|
|
class proxy extends mysqlnd_plugin_connection {
|
|
public function connect($host, ...) { .. }
|
|
}
|
|
mysqlnd_plugin_set_conn_proxy(new proxy());
|
|
]]>
|
|
</programlisting>
|
|
<simpara>
|
|
プロセス:
|
|
</simpara>
|
|
<orderedlist>
|
|
<listitem>
|
|
<simpara>
|
|
PHP: ユーザーがプラグインのコールバックを登録する
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
PHP: ユーザーがMySQLに接続するため、任意の PHP MySQL API を呼び出す
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
C: ext/*mysql* が mysqlnd のメソッドを呼び出す
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
C: mysqlnd が ext/mysqlnd_plugin の中で終了する
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
C: ext/mysqlnd_plugin
|
|
<orderedlist>
|
|
<listitem>
|
|
<simpara>
|
|
ユーザースペースで登録されたコールバックを呼び出す
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
また、ユーザースペースのコールバックが登録されていない場合は、<literal>mysqlnd</literal> のメソッドを呼び出す。
|
|
</simpara>
|
|
</listitem>
|
|
</orderedlist>
|
|
</para>
|
|
</listitem>
|
|
</orderedlist>
|
|
<simpara>
|
|
次のことを実行する必要があります:
|
|
</simpara>
|
|
<orderedlist>
|
|
<listitem>
|
|
<simpara>
|
|
"mysqlnd_plugin_connection" というクラスを C言語で書く
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
"mysqlnd_plugin_set_conn_proxy()" 関数を使って、プロキシオブジェクトを受け入れ、登録する
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
C言語から、ユーザースペースへのプロキシメソッドを呼び出す (最適化 - zend_interface.h)
|
|
</simpara>
|
|
</listitem>
|
|
</orderedlist>
|
|
<simpara>
|
|
ユーザースペースのオブジェクトメソッドは、<literal>call_user_function()</literal> を使うか、<literal>zend_call_method()</literal> を使って Zend Engine に近いレベルで操作を行うことができます。
|
|
</simpara>
|
|
<simpara>
|
|
<emphasis role="bold"> 最適化: C言語から zend_call_method を使ってメソッドを呼び出す</emphasis>
|
|
</simpara>
|
|
<simpara>
|
|
次に示すコードの断片は、<literal>zend_call_method</literal> のプロトタイプを示しています。これは <filename>zend_interface.h</filename> からとってきたものです。
|
|
</simpara>
|
|
<programlisting>
|
|
<![CDATA[
|
|
ZEND_API zval* zend_call_method(
|
|
zval **object_pp, zend_class_entry *obj_ce,
|
|
zend_function **fn_proxy, char *function_name,
|
|
int function_name_len, zval **retval_ptr_ptr,
|
|
int param_count, zval* arg1, zval* arg2 TSRMLS_DC
|
|
);
|
|
]]>
|
|
</programlisting>
|
|
<simpara>
|
|
Zend API は2つの引数しかサポートしていません。それ以上必要な場合は、たとえば次のようにします。
|
|
</simpara>
|
|
<programlisting>
|
|
<![CDATA[
|
|
enum_func_status (*func_mysqlnd_conn__connect)(
|
|
MYSQLND *conn, const char *host,
|
|
const char * user, const char * passwd,
|
|
unsigned int passwd_len, const char * db,
|
|
unsigned int db_len, unsigned int port,
|
|
const char * socket, unsigned int mysql_flags TSRMLS_DC
|
|
);
|
|
]]>
|
|
</programlisting>
|
|
<simpara>
|
|
この問題に対処するには、<literal>zend_call_method()</literal> のコピーを作り、追加の引数の入れ物を追加する必要があるでしょう。これは、<literal>MY_ZEND_CALL_METHOD_WRAPPER</literal> マクロの組を作ることで実現できます。
|
|
</simpara>
|
|
<simpara>
|
|
<emphasis role="bold">PHP のユーザースペースを呼び出す</emphasis>
|
|
</simpara>
|
|
<simpara>
|
|
以下のコードは、C言語からユーザースペースの関数を呼ぶための最適化されたやり方を示しています:
|
|
</simpara>
|
|
<programlisting>
|
|
<![CDATA[
|
|
/* my_mysqlnd_plugin.c */
|
|
|
|
MYSQLND_METHOD(my_conn_class,connect)(
|
|
MYSQLND *conn, const char *host /* ... */ TSRMLS_DC) {
|
|
enum_func_status ret = FAIL;
|
|
zval * global_user_conn_proxy = fetch_userspace_proxy();
|
|
if (global_user_conn_proxy) {
|
|
/* call userspace proxy */
|
|
ret = MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, host, /*...*/);
|
|
} else {
|
|
/* or original mysqlnd method = do nothing, be transparent */
|
|
ret = org_methods.connect(conn, host, user, passwd,
|
|
passwd_len, db, db_len, port,
|
|
socket, mysql_flags TSRMLS_CC);
|
|
}
|
|
return ret;
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
<simpara>
|
|
<emphasis role="bold"> ユーザースペースを呼び出す: 引数が一つの場合
|
|
</emphasis>
|
|
</simpara>
|
|
<programlisting>
|
|
<![CDATA[
|
|
/* my_mysqlnd_plugin.c */
|
|
|
|
MYSQLND_METHOD(my_conn_class,connect)(
|
|
/* ... */, const char *host, /* ...*/) {
|
|
/* ... */
|
|
if (global_user_conn_proxy) {
|
|
/* ... */
|
|
zval* zv_host;
|
|
MAKE_STD_ZVAL(zv_host);
|
|
ZVAL_STRING(zv_host, host, 1);
|
|
MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, zv_retval, zv_host /*, ...*/);
|
|
zval_ptr_dtor(&zv_host);
|
|
/* ... */
|
|
}
|
|
/* ... */
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
<simpara>
|
|
<emphasis role="bold"> ユーザースペースを呼び出す: 構造体を引数にする場合
|
|
</emphasis>
|
|
</simpara>
|
|
<programlisting>
|
|
<![CDATA[
|
|
/* my_mysqlnd_plugin.c */
|
|
|
|
MYSQLND_METHOD(my_conn_class, connect)(
|
|
MYSQLND *conn, /* ...*/) {
|
|
/* ... */
|
|
if (global_user_conn_proxy) {
|
|
/* ... */
|
|
zval* zv_conn;
|
|
ZEND_REGISTER_RESOURCE(zv_conn, (void *)conn, le_mysqlnd_plugin_conn);
|
|
MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, zv_retval, zv_conn, zv_host /*, ...*/);
|
|
zval_ptr_dtor(&zv_conn);
|
|
/* ... */
|
|
}
|
|
/* ... */
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
<simpara>
|
|
多くの <literal>mysqlnd</literal> のメソッドがはじめに引数としてとるのが C の "object" です。たとえば、 connect() メソッドのはじめの引数は <literal>MYSQLND</literal>構造体 へのポインタです。MYSQNND構造体 は、<literal>mysqlnd</literal> 接続オブジェクトを表現します。
|
|
</simpara>
|
|
<simpara>
|
|
<literal>mysqlnd</literal>接続オブジェクトへのポインタは、標準のI/Oファイルハンドルと比較できます。標準の I/Oファイルハンドルのように <literal>mysqlnd</literal>接続オブジェクトは PHP のリソース変数タイプを使ってユーザースペースに結びついていなければならないのです。
|
|
</simpara>
|
|
<simpara>
|
|
<emphasis role="bold">C言語とユーザースペースを行き来する</emphasis>
|
|
</simpara>
|
|
<programlisting>
|
|
<![CDATA[
|
|
class proxy extends mysqlnd_plugin_connection {
|
|
public function connect($conn, $host, ...) {
|
|
/* "pre" hook */
|
|
printf("Connecting to host = '%s'\n", $host);
|
|
debug_print_backtrace();
|
|
return parent::connect($conn);
|
|
}
|
|
|
|
public function query($conn, $query) {
|
|
/* "post" hook */
|
|
$ret = parent::query($conn, $query);
|
|
printf("Query = '%s'\n", $query);
|
|
return $ret;
|
|
}
|
|
}
|
|
mysqlnd_plugin_set_conn_proxy(new proxy());
|
|
]]>
|
|
</programlisting>
|
|
<simpara>
|
|
PHP ユーザーは、オーバーライドしたメソッドの親の実装を呼び出し可能にしなければいけません。
|
|
</simpara>
|
|
<simpara>
|
|
継承することで、ユーザーが選択したメソッドだけを改善し、"pre" や "post" フックを持たせるかどうかを選択できます。
|
|
</simpara>
|
|
<simpara>
|
|
<emphasis role="bold"> ビルトインのクラス:
|
|
mysqlnd_plugin_connection::connect() </emphasis>
|
|
</simpara>
|
|
<programlisting>
|
|
<![CDATA[
|
|
/* my_mysqlnd_plugin_classes.c */
|
|
|
|
PHP_METHOD("mysqlnd_plugin_connection", connect) {
|
|
/* ... simplified! ... */
|
|
zval* mysqlnd_rsrc;
|
|
MYSQLND* conn;
|
|
char* host; int host_len;
|
|
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs",
|
|
&mysqlnd_rsrc, &host, &host_len) == FAILURE) {
|
|
RETURN_NULL();
|
|
}
|
|
ZEND_FETCH_RESOURCE(conn, MYSQLND* conn, &mysqlnd_rsrc, -1,
|
|
"Mysqlnd Connection", le_mysqlnd_plugin_conn);
|
|
if (PASS == org_methods.connect(conn, host, /* simplified! */ TSRMLS_CC))
|
|
RETVAL_TRUE;
|
|
else
|
|
RETVAL_FALSE;
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</section>
|
|
</chapter>
|
|
<!-- Keep this comment at the end of the file
|
|
Local variables:
|
|
mode: sgml
|
|
sgml-omittag:t
|
|
sgml-shorttag:t
|
|
sgml-minimize-attributes:nil
|
|
sgml-always-quote-attributes:t
|
|
sgml-indent-step:1
|
|
sgml-indent-data:t
|
|
indent-tabs-mode:nil
|
|
sgml-parent-document:nil
|
|
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
|
|
sgml-exposed-tags:nil
|
|
sgml-local-catalogs:nil
|
|
sgml-local-ecat-files:nil
|
|
End:
|
|
vim600: syn=xml fen fdm=syntax fdl=2 si
|
|
vim: et tw=78 syn=sgml
|
|
vi: ts=1 sw=1
|
|
-->
|