-
Notifications
You must be signed in to change notification settings - Fork 79
/
Copy pathplugin.xml
1198 lines (1178 loc) · 52.8 KB
/
plugin.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<!-- EN-Revision: e50e79746736dbdfbabe9bd3566793b3ddf38f58 Maintainer: mumumu Status: ready -->
<chapter xml:id="mysqlnd.plugin" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>MySQL Native Driver プラグインAPI</title>
<para>
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) を学ぶ必要もありません。
</para>
<para>
<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>プラグイン はクライアントアプリケーションからこれらの拡張モジュールへの呼び出しの制御をすべて奪うことができるのです。
</para>
<para>
内部的な <literal>mysqlnd</literal> 関数の呼び出しも制御を奪ったり、処理を置き換えたりすることができます。<literal>mysqlnd</literal> の内部的な関数テーブルを管理することに関しても全く制限がありません。ある <literal>mysqlnd</literal> 関数が <literal>mysqlnd</literal> を使う拡張モジュールによって呼び出される場合に、適切な <literal>mysqlnd</literal>プラグインの適切な関数に処理を転送するようにセットアップすることが可能です。このように、<literal>mysqlnd</literal> の内部関数テーブルを管理できることで、プラグインの柔軟性が最大限に発揮できるのです。
</para>
<para>
<literal>mysqlnd</literal>プラグインは、実際は <literal>mysqlnd</literal> のプラグインAPI (これは MySQL Native Driver, 略して <literal>mysqlnd</literal> に組み込まれています) を使い、C言語 で書かれた PHP拡張モジュール です。 プラグインは PHPアプリケーションに対して 100% 透過的です。つまり、プラグインがPHPアプリケーションとは異なるレイヤで動作するため、アプリケーションを変更する必要がないのです。<literal>mysqlnd</literal>プラグイン は、<literal>mysqlnd</literal> のひとつ下のレイヤで動くと考えることができます。
</para>
<para>
<literal>mysqlnd</literal>プラグインで実現可能なアプリケーションのリストをいくつか以下に示します
</para>
<itemizedlist>
<listitem>
<para>
ロードバランシング
</para>
<itemizedlist>
<listitem>
<para>
読み取り/書き込み の分割。例として、PECL/mysqlnd_ms (Master Slave) が挙げられます。この 拡張モジュール は 読み取り/書き込み のクエリをレプリケーションのセットアップ向けに分割します。
</para>
</listitem>
<listitem>
<para>
フェイルオーバー
</para>
</listitem>
<listitem>
<para>
ラウンドロビン, 負荷が一番低いサーバーへの転送
</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para>
サーバーの監視
</para>
<itemizedlist>
<listitem>
<para>
クエリのロギング
</para>
</listitem>
<listitem>
<para>
クエリの分析
</para>
</listitem>
<listitem>
<para>
クエリの監査。例として、PECL/mysqlnd_sip (SQL Injection Protection) が挙げられます。この 拡張モジュール はクエリを調べ、ルールセットに従って許可されたクエリのみを実行します。
</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para>
パフォーマンスの向上
</para>
<itemizedlist>
<listitem>
<para>
キャッシュ。例として、PECL/mysqlnd_qc (Query Cache) が挙げられます。
</para>
</listitem>
<listitem>
<para>
トラフィックの調整
</para>
</listitem>
<listitem>
<para>
シャーディング。例として、PECL/mysqlnd_mc (Multi Connect) が挙げられます。この拡張モジュール は、SELECT ステートメントを SELECT ... LIMIT part1, SELECT LIMIT part_n という形でn個に分割します。そしてクエリを別々の MySQLサーバーに送り、結果をクライアント側でマージします。
</para>
</listitem>
</itemizedlist>
</listitem>
</itemizedlist>
<para>
<emphasis role="bold">利用可能な MySQL Native Driverプラグイン</emphasis>
</para>
<para>
既にたくさんの mysqlnd プラグインが利用可能になっています。以下が含まれます。
</para>
<itemizedlist>
<listitem>
<para>
<emphasis role="bold">PECL/mysqlnd_mc</emphasis> - 複数接続ができるプラグイン
</para>
</listitem>
<listitem>
<para>
<emphasis role="bold">PECL/mysqlnd_ms</emphasis> - マスタースレーブ構成用のプラグイン
</para>
</listitem>
<listitem>
<para>
<emphasis role="bold">PECL/mysqlnd_qc</emphasis> - クエリキャッシュプラグイン
</para>
</listitem>
<listitem>
<para>
<emphasis role="bold">PECL/mysqlnd_pscache</emphasis> - プリペアドステートメントハンドルをキャッシュするプラグイン
</para>
</listitem>
<listitem>
<para>
<emphasis role="bold">PECL/mysqlnd_sip</emphasis> - SQLインジェクションから防御するためのプラグイン
</para>
</listitem>
<listitem>
<para>
<emphasis role="bold">PECL/mysqlnd_uh</emphasis> - ユーザーハンドラープラグイン
</para>
</listitem>
</itemizedlist>
<section xml:id="mysqlnd.plugin.mysql-proxy">
<title>mysqlndプラグイン と MySQL Proxyの比較</title>
<para>
<literal>mysqlnd</literal>プラグインと MySQL Proxyは、異なるアプローチを用いた異なる技術です。どちらもロードバランシングや監視、パフォーマンスの向上のような共通の様々な課題を解決するのに適したツールです。重要な違いは、MySQL Proxyがあらゆる MySQLクライアント と協調して動作するのに対して、<literal>mysqlnd</literal>プラグインは PHPアプリケーション との協調動作に特化しているということです。
</para>
<para>
PHP拡張モジュールとして、<literal>mysqlnd</literal>プラグイン はPHPの残りの部分とともに、PHPのアプリケーションサーバーにインストールされます。MySQL Proxy はPHPアプリケーションサーバー上でも動作しますし、複数のPHPアプリケーションサーバーを扱う専用マシンにもインストールできます。
</para>
<para>
MySQL Proxyをアプリケーションサーバーにデプロイすることにはふたつの利点があります:
</para>
<orderedlist>
<listitem>
<para>
単一障害点がありません
</para>
</listitem>
<listitem>
<para>
スケールアウトしやすい (水平方向へのスケールアウト、クライアントによるスケーリング)
</para>
</listitem>
</orderedlist>
<para>
MySQL Proxy (と、<literal>mysqlnd</literal>プラグイン) は、他のやり方だと既存のアプリケーションを変更しなければならない問題にも容易に対処することができます。
</para>
<para>
しかし、MySQL Proxyにはいくつか欠点があります:
</para>
<itemizedlist>
<listitem>
<para>
MySQL Proxy のコンポーネントと技術について新たにマスターし、デプロイしなければなりません。
</para>
</listitem>
<listitem>
<para>
MySQL Proxy は Lua スクリプト言語の知識が必要です。
</para>
</listitem>
</itemizedlist>
<para>
MySQL Proxy は C言語と プログラミング言語 Lua によってカスタマイズできます。Lua は MySQL Proxy において好ましいスクリプト言語です。ほとんどのPHPエキスパートにとって、Lua は新しく学ばなければならない言語です。<literal>mysqlnd</literal>プラグインは C言語で書くことができます。<link xlink:href="http://pecl.php.net/package/mysqlnd_uh">PECL/mysqlnd_uh</link> を使って、プラグインを PHP で書くこともできます。
</para>
<para>
MySQL Proxy はデーモン - バックグラウンドのプロセスとして動作します。MySQL Proxy は初期の決定を取り消すこともできますが、すべての状態を保持することできます。しかしながら <literal>mysqlnd</literal>プラグインは、PHPのリクエスト単位のライフサイクルに結びついています。MySQL Proxy は一度計算された結果を複数のアプリケーションサーバーで共有できます。<literal>mysqlnd</literal>プラグインでこれを行うには、永続的なストレージにデータを保存する必要があります。この目的のためには Memcache のような別のデーモンが必要です。この場合は、MySQL Proxy に有利です。
</para>
<para>
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 のスクリプトも変更する必要があります。
</para>
<para>
<literal>mysqlnd</literal>プラグインは C言語のAPI上で動作します。このAPIは <literal>libmysqlclient</literal>クライアント の動きをそのままコピーしています。この C言語のAPI は、 基本的に MySQLクライアントサーバープロトコル、時に wire protocol と呼ばれるもののラッパーです。開発者は C言語のAPI呼び出しの制御を奪うことができます。それゆえに、wire protocol レベルのプログラムに一切変更を加えることなく、すべてのPHP呼び出しをフックできるのです。
</para>
<para>
<literal>mysqlnd</literal> は wire protocol を実装しています。そのため、プラグインは通信プロトコルを解析し、リバースエンジニアリングし、管理できるばかりか、通信プロトコルを置き換えることだってできます。ただ、こんなことをする必要は通常ありません。
</para>
<para>
プラグインが 2つのレベル(C言語のAPI と wire protocol) を使って実装できるので、MySQL Proxy よりも大きな柔軟性を得られます。 <literal>mysqlnd</literal>プラグインが C言語のAPI を使って実装されれば、wire protocol に対していかなる変更が行われても、プラグインへの変更は必要ありません。
</para>
</section>
<section xml:id="mysqlnd.plugin.obtaining">
<title>mysqlnd plugin APIを取得する</title>
<para>
<literal>mysqlnd</literal>プラグインAPI は、MySQL Native Driver PHP拡張モジュールである <literal>ext/mysqlnd</literal> の一部です。2009年12月に <literal>mysqlnd</literal>プラグインAPI の開発がスタートしました。これは PHP のソースリポジトリの一部として開発が進められ、git経由で公開されたり、ソースコードのスナップショットがダウンロード可能になっています。
</para>
<para>
プラグインの開発者は <literal>mysqlnd</literal> のバージョンを <literal>MYSQLND_VERSION</literal> にアクセスすることで決めることができます。この値は、<quote>mysqlnd 5.0.7-dev - 091210 - $Revision: 300535</quote> という文字列フォーマットです。<literal>MYSQLND_VERSION_ID</literal> という値にもアクセスできます。これはたとえば、50007 のような数値です。 開発者は次のようにしてバージョン番号を計算することができます:
</para>
<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>
<para>
開発している間は、開発者は <literal>mysqlnd</literal> のバージョン番号を互換性と対応バージョンのテストとして参照すべきです。PHP本体のひとつのバージョンにおける開発のライフサイクルの間に、<literal>mysqlnd</literal> の開発イテレーションは複数行われる場合があるためです。
</para>
</section>
<section xml:id="mysqlnd.plugin.architecture">
<title>MySQL Native Driverプラグイン のアーキテクチャ</title>
<para>
このセクションでは、<literal>mysqlnd</literal>プラグイン のアーキテクチャについての概要を示します。
</para>
<para>
<emphasis role="bold"> MySQL Native Driver の概要</emphasis>
</para>
<para>
<literal>mysqlnd</literal>プラグイン を開発する前に、<literal>mysqlnd</literal> そのものがどのように成り立っているのかを少し知っておくと役に立ちます。<literal>mysqlnd</literal> は次に示すモジュールからなります:
</para>
<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>
<para>
<emphasis role="bold">C言語のオブジェクト指向パラダイム</emphasis>
</para>
<para>
ソースコードレベルで、<literal>mysqlnd</literal> は オブジェクト指向を実装するためのパターンを採用しています。
</para>
<para>
C言語では、オブジェクトを表現するために <literal>struct</literal>(構造体) を使います。struct のメンバがオブジェクトのプロパティを表現します。関数を指している struct のメンバがメソッドを表現します。
</para>
<para>
C++ や Java のような言語と異なり、C言語におけるオブジェクト指向のパラダイムでは決まった継承のルールがありませんが、従う必要があるルールがいくつかあります。このルールについては後に述べます。
</para>
<para>
<emphasis role="bold">PHP のライフサイクル</emphasis>
</para>
<para>
PHP のライフサイクルを考えるとき、ふたつの基本的なサイクルが存在します。
</para>
<itemizedlist>
<listitem>
<para>
PHPエンジンの起動と終了までのサイクル
</para>
</listitem>
<listitem>
<para>
リクエスト毎のライフサイクル
</para>
</listitem>
</itemizedlist>
<para>
PHPエンジンが起動するとき、PHP はモジュールを初期化する (MINIT) 関数を登録された拡張モジュールごとに呼び出します。これによって、各々のモジュールが PHPエンジンが処理を行うライフサイクルの間存在するリソースを割り当てたり、変数を定義することができます。PHPエンジンが終了するときには、 エンジンが終了(MSHUTDOWN)関数を拡張モジュール毎に呼び出します。
</para>
<para>
PHPエンジンが起動している間、エンジンはたくさんのリクエストを受けとります。それぞれのリクエストは別のライフサイクルを構成します。リクエスト毎にPHPエンジンはリクエストの初期化関数を拡張モジュール毎に呼び出します。拡張モジュール側では、リクエストの処理に必要な変数の定義やリソースの割り当てを行うことができます。リクエストのサイクルが終了するときは、PHPエンジンがリクエストの終了(RSHUTDOWN)関数を拡張モジュール毎に呼び出します。これによって、拡張モジュールは必要となるあらゆるクリーンアップ処理を行うことができます。
</para>
<para>
<emphasis role="bold">プラグインはどうやって動くか</emphasis>
</para>
<para>
<literal>mysqlnd</literal>プラグイン は <literal>mysqlnd</literal> を使う拡張モジュールが <literal>mysqlnd</literal> を呼び出すときの制御を奪うことによって動作します。これは <literal>mysqlnd</literal> の関数テーブルを取得し、バックアップし、カスタムの関数テーブルと置き換えることによって実現されます。この関数テーブルが、プラグインが必要とする関数を呼び出すのです。
</para>
<para>
次のコードは、<literal>mysqlnd</literal> の関数テーブルを置き換える方法を示しています:
</para>
<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>
<para>
接続関数テーブルの管理は、モジュールを初期化(MINIT)している間に行わなければなりません。関数テーブルはグローバルに共有されるリソースです。マルチスレッド環境で、TSRMを有効にしてPHPをビルドした環境では、グローバルに共有されたリソースをリクエストを処理している間に操作すると、ほぼ確実に衝突が起こります。
</para>
<note>
<para>
<literal>mysqlnd</literal> の関数テーブルを管理するときに、固定サイズを割り当てるロジックは絶対に使わないでください。新しいメソッドが関数テーブルの最後に追加される可能性があるからです。関数テーブルは将来どんな場合でも変更される可能性があります。
</para>
</note>
<para>
<emphasis role="bold">親クラスのメソッドを呼び出す</emphasis>
</para>
<para>
オリジナルの関数テーブルをバックアップしている場合、オリジナルの関数テーブルのエントリに含まれる関数を呼び出すことができます - これが親メソッドです。
</para>
<para>
場合によっては、<literal>Connection::stmt_init()</literal> のように、派生メソッドで他のあらゆる処理より先に親メソッドを呼び出すことが決定的に重要な場合があります。
</para>
<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>
<para>
<emphasis role="bold">プロパティを拡張する</emphasis>
</para>
<para>
<literal>mysqlnd</literal>オブジェクトは C言語の構造体で表現されます。実行時に、C言語の構造体に新たにメンバを追加することは不可能です。<literal>mysqlnd</literal>オブジェクト のユーザーは、プロパティを単純にオブジェクトに追加することはできません。
</para>
<para>
<literal>mysqlnd_plugin_get_plugin_<object>_data()</literal>ファミリーの適切な関数を使って、任意のデータ (プロパティ) を <literal>mysqlnd</literal> オブジェクトに追加することができます。オブジェクトをメモリに割り付ける際に、<literal>mysqlnd</literal> はオブジェクトの最後に 任意のデータ向けの <literal>void *</literal> ポインタを保持するためのメモリ空間を予約しておきます。 <literal>mysqlnd</literal> は プラグインひとつにつき、ひとつの <literal>void *</literal>ポインタ を保持する空間を予約しています。
</para>
<para>
次の表で、特定のプラグインでポインタの位置を計算する方法を示します:
</para>
<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>
<para>
<literal>mysqlnd</literal>オブジェクト のコンストラクタを継承する計画がある場合、それが許可されていることを必ず頭にいれておいてください!
</para>
<para>
次のコードはプロパティを拡張する方法を示しています:
</para>
<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>
<para>
プラグインの開発者には、プラグイン用のデータに使われるメモリを管理する責任があります。
</para>
<para>
<literal>mysqlnd</literal> のメモリアロケータを使うことを推奨します。これらのメモリアロケータ関数は次のような規約に従って命名されています: <literal>mnd_*loc()</literal> <literal>mysqlnd</literal> のメモリアロケーターには役に立つ機能がいくつかあります。たとえばデバッグビルドでない環境でデバッグ用のアロケータを使う機能などです。
</para>
<table xml:id="mysqlnd.plugin.subclass">
<title>いつ、どのように継承するか</title>
<tgroup cols="4">
<thead>
<row>
<entry></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>
<para>
上記の表で許可されていない場合は、モジュールを初期化した(MINIT)後のいかなる場合であっても関数テーブルを変更してはいけません。
</para>
<para>
クラスによっては、メソッドの関数テーブルへのポインタが含まれている場合があります。このようなクラスのインスタンスはすべて、同じ関数テーブルを共有しています。混乱を避けるため、特にマルチスレッドの環境下では、このような関数テーブルは MINIT (モジュール初期化) 時にだけ変更するようにしてください。
</para>
<para>
そうでないクラスでは、グローバルに共有された関数テーブルのコピーを使っています。クラスの関数テーブルのコピーがオブジェクトとともに作成されます。それぞれのオブジェクトは自分の関数テーブルを使います。これによって開発者は二つの選択肢が得られます: MINIT(モジュール初期化) 時にオブジェクトのデフォルトの関数テーブルを変更するか、同じクラスの他のインスタンスに影響を与えることなくオブジェクトのメソッドを追加で変更することができます。
</para>
<para>
関数テーブルを共有する利点は、パフォーマンスの向上です。関数テーブルをそれぞれの、すべてのオブジェクトにコピーする必要がないからです。
</para>
<table xml:id="mysqlnd.plugin.constatus">
<title>コンストラクタの状態</title>
<tgroup cols="4">
<thead>
<row>
<entry></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><para>
メモリ割り当て:
</para>
<itemizedlist>
<listitem>
<para>
Connection::result_init()
</para>
</listitem>
</itemizedlist>
<para>
リセットし、再初期化されるタイミング:
</para>
<itemizedlist>
<listitem>
<para>
Result::use_result()
</para>
</listitem>
<listitem>
<para>
Result::store_result
</para>
</listitem>
</itemizedlist></entry>
<entry>Yes, ただし親メソッドを呼び出すこと!</entry>
<entry><itemizedlist>
<listitem>
<para>
Connection::list_fields()
</para>
</listitem>
<listitem>
<para>
Statement::get_result()
</para>
</listitem>
<listitem>
<para>
Statement::prepare() (メタデータのみ)
</para>
</listitem>
<listitem>
<para>
Statement::resultMetaData()
</para>
</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>
<para>
コンストラクタを全面的に置き換えないことを強く推奨します。コンストラクタはメモリへの割り当てを実行します。<literal>mysqlnd</literal>プラグインAPI と オブジェクトのロジックにとってメモリへの割り当ては決定的に重要です。開発者が警告を無視してコンストラクタへのフックを強行する場合、コンストラクタで何かをする前に親のコンストラクタを少なくとも呼び出すべきです。
</para>
<para>
すべての警告に関わらず、コンストラクタを継承することが役に立つ場合があります。コンストラクタは、オブジェクトの関数テーブルを共有されていないオブジェクトの関数テーブルと一緒に修正するのにぴったりな場所です。共有されていないオブジェクトの関数テーブルの例としては、結果セットやネットワーク、wire protocol が挙げられます。
</para>
<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>
<para>
デストラクタは、<literal>mysqlnd_plugin_get_plugin_<replaceable><object></replaceable>_data()</literal> で取得したプロパティを破棄するのに適切な場所です。
</para>
<para>
ここで挙げたデストラクタは、オブジェクトそのものを破棄する 実際の <literal>mysqlnd</literal> メソッドと一致しないかもしれません。しかし、これらのデストラクタは、開発者がフックし、プラグインデータを解放する最良の場所なのです。コンストラクタに関しては、開発者がメソッドを完全に置き換えることができるものの、推奨されません。上の表に複数のメソッドが示されていた場合、<literal>mysqlnd</literal> がどのメソッドをはじめに呼び出したかに関わらず、開発者はここで示されているすべてのメソッドをフックし、プラグインのデータを解放する必要があります。
</para>
<para>
プラグインに推奨されるやり方は、単純にメソッドをフックし、メモリを解放した後に親クラスの実装を速やかに呼び出すことです。
</para>
</section>
<section xml:id="mysqlnd.plugin.api">
<title>mysqlnd のプラグインAPI</title>
<para>
<literal>mysqlnd</literal>プラグインAPI で提供されている関数のリストを以下に示します:
</para>
<itemizedlist>
<listitem>
<para>
mysqlnd_plugin_register()
</para>
</listitem>
<listitem>
<para>
mysqlnd_plugin_count()
</para>
</listitem>
<listitem>
<para>
mysqlnd_plugin_get_plugin_connection_data()
</para>
</listitem>
<listitem>
<para>
mysqlnd_plugin_get_plugin_result_data()
</para>
</listitem>
<listitem>
<para>
mysqlnd_plugin_get_plugin_stmt_data()
</para>
</listitem>
<listitem>
<para>
mysqlnd_plugin_get_plugin_net_data()
</para>
</listitem>
<listitem>
<para>
mysqlnd_plugin_get_plugin_protocol_data()
</para>
</listitem>
<listitem>
<para>
mysqlnd_conn_get_methods()
</para>
</listitem>
<listitem>
<para>
mysqlnd_result_get_methods()
</para>
</listitem>
<listitem>
<para>
mysqlnd_result_meta_get_methods()
</para>
</listitem>
<listitem>
<para>
mysqlnd_stmt_get_methods()
</para>
</listitem>
<listitem>
<para>
mysqlnd_net_get_methods()
</para>
</listitem>
<listitem>
<para>
mysqlnd_protocol_get_methods()
</para>
</listitem>
</itemizedlist>
<para>
プラグインが何かとか、どのようにプラグインが機能するのか、という疑問に対する公式な定義はありません。
</para>
<para>
プラグインのメカニズムでよく見つかるコンポーネントは以下の通りです:
</para>
<itemizedlist>
<listitem>
<para>
プラグインマネージャー
</para>
</listitem>
<listitem>
<para>
プラグインAPI
</para>
</listitem>
<listitem>
<para>
アプリケーションサービス(またはモジュール)
</para>
</listitem>
<listitem>
<para>
アプリケーションサービスAPI (またはモジュールAPI)
</para>
</listitem>
</itemizedlist>
<para>
<literal>mysqlnd</literal>プラグインのコンセプトにはこれらが取り入れられており、追加でオープンなアーキテクチャの恩恵を受けています。
</para>
<para>
<emphasis role="bold"> mysqlnd の内部には無制限にアクセスできる </emphasis>
</para>
<para>
プラグインは <literal>mysqlnd</literal> の内部にすべてアクセスできます。セキュリティ上の限界や制限はありません。mysqlnd に親和性が高い、または不利なアルゴリズムを実装するためにすべてを置き換えることができます。よって、信頼できる配布元からのプラグインだけをデプロイすることを推奨します。
</para>
<para>
以前議論したとおり、プラグインはポインタを自由に使えます。これらのポインタはあらゆる点で制限されていないので、別のプラグインのデータを指すこともできます。簡単にオフセットを計算するだけで別のプラグインのデータを使うことができます。
</para>
<para>
mysqlnd と協調的なプラグインを書くこと、そして開発者はいつも親メソッドを呼び出すことを推奨します。プラグインはいつも <literal>mysqlnd</literal> と協調的であるべきです。
</para>
<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>
<para>
mysqlnd_cache.query()
</para>
</listitem>
<listitem>
<para>
mysqlnd.query
</para>
</listitem>
</orderedlist></entry>
</row>
<row>
<entry>ext/mysqlnd_monitor</entry>
<entry>mysqlnd_monitor.query()</entry>
<entry><orderedlist>
<listitem>
<para>
mysqlnd_monitor.query()
</para>
</listitem>
<listitem>
<para>
mysqlnd_cache.query()
</para>
</listitem>
<listitem>
<para>
mysqlnd.query
</para>
</listitem>
</orderedlist></entry>
</row>
</tbody>
</tgroup>
</table>
<para>
このシナリオでは、キャッシュ(<literal>ext/mysqlnd_cache</literal>) と モニタ(<literal>ext/mysqlnd_monitor</literal>) プラグインが読み込まれています。両方とも <literal>Connection::query()</literal> を継承しています。プラグインの登録が 以前説明したロジックを使って <literal>MINIT</literal>(モジュール初期化) 時に発生します。PHP は拡張モジュールをデフォルトではアルファベット順に呼び出します。プラグインはお互いのことを知りませんし、拡張モジュールの依存性についても設定しません。
</para>
<para>
デフォルトで、プラグインは派生したメソッドの中で query メソッドの親クラスの実装を呼び出します。
</para>
<para>
<emphasis role="bold"> PHP 拡張モジュールの動きを再現する </emphasis>
</para>
<para>
サンプルのプラグイン <literal>ext/mysqlnd_plugin</literal> を使うと何が起こるかを以下で再現します。このプラグインは、<literal>mysqlnd</literal> の CプラグインAPI をPHPに公開しています。
</para>
<itemizedlist>
<listitem>
<para>
PHP で書かれた MySQL アプリケーションが 192.168.2.29 に接続を確立しようとします。
</para>
</listitem>
<listitem>
<para>
PHP アプリケーションは <literal>ext/mysql</literal>, <literal>ext/mysqli</literal> または <literal>PDO_MYSQL</literal> のいずれかを使うでしょう。これら3つの PHP MySQL 拡張モジュールは <literal>mysqlnd</literal> を使って 192.168.2.29 に接続を確立しようとします。
</para>
</listitem>
<listitem>
<para>
<literal>mysqlnd</literal> は <literal>ext/mysqlnd_plugin</literal> を継承した自分自身の connect メソッドを呼び出します。
</para>
</listitem>
<listitem>
<para>
<literal>ext/mysqlnd_plugin</literal> は ユーザーが登録した ユーザースペースのフックである <literal>proxy::connect()</literal> を呼び出します。
</para>
</listitem>
<listitem>
<para>
ユーザースペースのフックは接続先のホストIPアドレスを 192.168.2.29 から 127.0.0.1 に書き換え、<literal>parent::connect()</literal> によって確立された接続を返します。
</para>
</listitem>
<listitem>
<para>
<literal>ext/mysqlnd_plugin</literal> は オリジナルの <literal>mysqlnd</literal>のメソッドを接続を確立するために呼び出します。これによって、<literal>parent::connect(127.0.0.1)</literal> を実行するのと同じことをします。
</para>
</listitem>
<listitem>
<para>
<literal>ext/mysqlnd</literal> は、接続を確立し、<literal>ext/mysqlnd_plugin</literal> に接続を返します。<literal>ext/mysqlnd_plugin</literal> も同じことをします。
</para>
</listitem>
<listitem>
<para>
どんな PHP MySQL 拡張モジュールをアプリケーションで使っていても、127.0.0.1 への接続を受け取ります。PHP MySQL 拡張モジュールそれ自体は、PHPアプリケーションにそれを返し、実行は終了します。
</para>
</listitem>
</itemizedlist>
</section>
<section xml:id="mysqlnd.plugin.developing">
<title>mysqlndプラグインの開発をはじめよう</title>
<para>
<literal>mysqlnd</literal>プラグイン それ自体が PHP拡張モジュール であることを覚えておくことが重要です。
</para>
<para>
次のコードは、典型的な <literal>mysqlnd</literal> プラグインで使われる MINIT 関数の基本構造を示します。
</para>
<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>
<para>
<emphasis role="bold">タスクの解析: C言語からユーザースペースへ</emphasis>
</para>
<programlisting>
<![CDATA[
class proxy extends mysqlnd_plugin_connection {
public function connect($host, ...) { .. }
}
mysqlnd_plugin_set_conn_proxy(new proxy());
]]>
</programlisting>
<para>
プロセス:
</para>
<orderedlist>
<listitem>
<para>
PHP: ユーザーがプラグインのコールバックを登録する
</para>
</listitem>
<listitem>
<para>
PHP: ユーザーがMySQLに接続するため、任意の PHP MySQL API を呼び出す
</para>
</listitem>
<listitem>
<para>
C: ext/*mysql* が mysqlnd のメソッドを呼び出す
</para>
</listitem>
<listitem>
<para>
C: mysqlnd が ext/mysqlnd_plugin の中で終了する
</para>
</listitem>
<listitem>
<para>
C: ext/mysqlnd_plugin
<orderedlist>
<listitem>
<para>
ユーザースペースで登録されたコールバックを呼び出す
</para>
</listitem>
<listitem>
<para>
また、ユーザースペースのコールバックが登録されていない場合は、<literal>mysqlnd</literal> のメソッドを呼び出す。
</para>
</listitem>
</orderedlist>
</para>
</listitem>
</orderedlist>
<para>
次のことを実行する必要があります:
</para>
<orderedlist>
<listitem>
<para>
"mysqlnd_plugin_connection" というクラスを C言語で書く
</para>
</listitem>
<listitem>
<para>
"mysqlnd_plugin_set_conn_proxy()" 関数を使って、プロキシオブジェクトを受け入れ、登録する
</para>
</listitem>
<listitem>