第 48章PHP 3の拡張

目次
PHP 3への関数の付加
ユーザー関数のコール方法
エラーの出力

このセクションはPHP3の拡張方法に関するとても古臭いデモです。 PHP4に関心があるのであれば、Zend APIの セクションを読んでください。

PHP 3への関数の付加

関数プロトタイプ

全ての関数は次のような形式です。
void php3_foo(INTERNAL_FUNCTION_PARAMETERS) {
      
}
関数が引数を取らない場合でも、このような形式でコールされます。

関数の引数

引数は常にpval型です。この型は、実際の引数の型をメンバーとする unionを有しています。 関数が二つの引数を取る場合、関数の最初に次のような処理を 行なうことになります。

例 48-1. 引数の取得

pval *arg1, *arg2;
if (ARG_COUNT(ht) != 2 || getParameters(ht,2,&arg1,&arg2)==FAILURE) {
   WRONG_PARAM_COUNT;
}
注意: 引数は値渡しでも参照渡しでもかまいません。どちらの場合でも &(pval *) を getParameters に渡す必要があります。 n 番目のパラメータが参照渡しであるかどうかを調べたい場合、 関数 ParameterPassedByReference(ht,n) を使用することができます。 この関数は、1 または 0 を返します。

渡されたパラメータのどれかを変更する場合、 参照渡しであるか、値渡しであるかにかかわらず、 pval_destructor をコールすることにより初期化するか、 ARRAYにパラメータを加えたい場合には、 返り値を ARRAYとして処理する internal_functions.h の中の関数に類似の関数を使用することが可能です。

また、パラメータを IS_STRING に変更する場合、 estrdup() された文字列と文字列の長さを最初に割り付けた後で始めて 型を IS_STRING に変更することが可能であることに注意してください。 すでに IS_STRING または IS_ARRAY であるパラメータの文字列を 変更する場合、最初に pval_destructor を実行する必要があります。

可変引数

関数は、可変の数の引数をとることができます。 ある関数が 2 つまたは 3 つの引数のどちらかをとる場合、 次のようにしてください。

例 48-2. 可変引数

pval *arg1, *arg2, *arg3;
int arg_count = ARG_COUNT(ht);
 
if (arg_count < 2 || arg_count > 3 ||
    getParameters(ht,arg_count,&arg1,&arg2,&arg3)==FAILURE) {
    WRONG_PARAM_COUNT;
}

引数の使用

各引数の型は pval 型フィールドに保存されます。 この型は、次のどれかとすることができます。

表 48-1. PHP 内部の型

IS_STRING文字列
IS_DOUBLE倍精度浮動小数点
IS_LONG倍精度整数
IS_ARRAY配列
IS_EMPTY
IS_USER_FUNCTION??
IS_INTERNAL_FUNCTION?? (このうちのいくつかは関数に渡すことができません。 - 削除)
IS_CLASS??
IS_OBJECT??

ある型の引数を得たが、他の型で使用したい場合、または、 その引数を強制的にある型で使用したい場合、 次の変換関数のどれかを使用することができます。
convert_to_long(arg1);
convert_to_double(arg1);
convert_to_string(arg1); 
convert_to_boolean_long(arg1); /* 文字列が "" または "0" の場合 0、その他の場合 1 になります */
convert_string_to_number(arg1);  /* 文字列に応じて値を LONG または DOUBLE に変更します  */

これらの関数は、全てその場で変換されます。 値は全く返されません。

実引数は、結合体に保持されます。 メンバーは、次のようになります。

  • IS_STRING: arg1->value.str.val

  • IS_LONG: arg1->value.lval

  • IS_DOUBLE: arg1->value.dval

関数におけるメモリー管理

関数により必要とされる全てのメモリーは、 emalloc() または estrdup() のどちらかにより 確保される必要があります。これらの関数は、 通常の malloc() および strdup() 関数に似た メモリー関連処理を行う抽象関数です。 メモリーは、efree() により開放される必要があります。

このプログラムには、2種類のメモリーがあります。 つまり、パーサーに変数として返されるメモリーと 内部関数での一次的な記憶領域用に必要なメモリーです。 文字列をある変数に代入しパーサーに返したい場合、 まず emalloc() または estrdup() のどちらかにより メモリーを確保する必要があります。 このメモリーは、同じ関数において元の代入値を上書きしない限り 開放することはできません。 (この種のプログラミング法は、推奨されません。)

自分の関数・ライブラリで必要な一時的・永続的メモリを確保するためには、 emalloc()、estrdup()、efree() 関数を使用する必要があります。 これらの関数は、それぞれが対応する関数 (malloc,strdup,free) と全く同じ動作をします。 emalloc() または estrdup() を実行した場合、 ただし、プログラム実行終了時まで確保しつづけるつもりである場合を除き、 どこかで efree() を行う必要があります。 さもなくば、メモリーリークを起こす可能性があります。 関数が対応するそれぞれが関数と全く同じ動作をするという意味は 次のようなものです。 つまり、emalloc() も estrdup() もされていないメモリーについて efree() を行った場合、セグメンテーションフォルトを発生する可能性があります。 このため、消費したメモリーを解放する際には注意してください。

"-DDEBUG" でコンパイルした場合、PHP 3は、指定したスクリプトの実行が 終了した際に、emalloc() および estrdup() で確保したが efree() でまだ開放されていない全メモリーのリストを 表示します。

変数のシンボルテーブルへの設定

シンボルテーブルに変数を設定することを容易にするために 以下のようなマクロが用意されています。

  • SET_VAR_STRING(name,value)

  • SET_VAR_DOUBLE(name,value)

  • SET_VAR_LONG(name,value)

警告

SET_VAR_STRINGを使用する場合は注意が必要です。 value は、メモリ管理コードが後にこのポインタについて free を 行おうとするため、手動で malloc を行う必要があります。 静的に確保されたメモリーを SET_VAR_STRING に渡してはなりません。

PHPのシンボルテーブルは、ハッシュテーブルとして実装されています。 いつなんどきでも &symbol_table は 'main' シンボルテーブルへの ポインターであり、active_symbol_table は現在アクティブな シンボルテーブルを指しています。(この二つは実行開始時には同じですが、 関数の中にいる時には異なっています。)

次の例は 'active_symbol_table' を使用します。 'main' シンボルテーブルを使用したい場合には、 これを &symbol_table で置きかえる必要があります。 また、以下に説明するように同じ関数を配列にも適用することができます。

例 48-3. $foo がシンボルテーブルに存在するかどうかを調べる

if (hash_exists(active_symbol_table,"foo",sizeof("foo"))) { exists... }
else { doesn't exist }

例 48-4. シンボルテーブルにある変数のサイズの調べ方

hash_find(active_symbol_table,"foo",sizeof("foo"),&pvalue);
check(pvalue.type);
PHPにおける配列は、シンボルテーブルと同じくハッシュテーブル により実装されています。 このため、上記の二つの関数は配列の内部で変数を確認するためにも 使用可能です。

シンボルテーブルに配列を新規に定義したい場合、 次のようにする必要があります。

まず、hash_exists() または hash_find() を用いて その配列の存在や、適切に開放されているかどうかを 確認する方が良いでしょう。

次に、その配列を次のように初期化します。

例 48-5. 新規の配列の初期化

pval arr;
   
if (array_init(&arr) == FAILURE) { failed... };
hash_update(active_symbol_table,"foo",sizeof("foo"),&arr,sizeof(pval),NULL);
このコードは、アクティブシンボルテーブルにおいて新規配列を宣言しています。 この配列は空です。

以下に新しいエントリをこの配列に加える例を示します。

例 48-6. 新規配列にエントリを加える

pval entry;
   
entry.type = IS_LONG;
entry.value.lval = 5;
   
/* $foo["bar"] = 5 を定義する */
hash_update(arr.value.ht,"bar",sizeof("bar"),&entry,sizeof(pval),NULL); 
 
/* $foo[7] = 5 を定義する */
hash_index_update(arr.value.ht,7,&entry,sizeof(pval),NULL); 
 
/* $foo[] における次の空きメモリー、つまり $foo[8] を 5 に定義する
 * (php2 と同様に動作します)
 */
hash_next_index_insert(arr.value.ht,&entry,sizeof(pval),NULL);
ハッシュに挿入された値を修正したい場合、 まずハッシュから値を取り出す必要があります。 オーバーヘッドを避けるため、 pval ** を ハッシュ付加関数に与え、 ハッシュの内部に挿入された要素のアドレスを示す pval * を用いて更新を行うことができます。 (上記の全ての例のように)この値が、NULL の場合、このパラメータは 無視されます。

hash_next_index_insert() は、PHP 2.0 の "$foo[] = bar;" とほぼ同じロジックを用いています。

関数から配列を返す構成とする場合、 以下のようにするだけで配列の初期化を行うことができます。

if (array_init(return_value) == FAILURE) { failed...; }

続いて、ヘルパー関数を用いて値を付加します。

add_next_index_long(return_value,long_value);
add_next_index_double(return_value,double_value);
add_next_index_string(return_value,estrdup(string_value));

もちろん、付加が配列の初期化直後に行われない場合には、 配列をまず確認する必要があることでしょう。
pval *arr;
   
if (hash_find(active_symbol_table,"foo",sizeof("foo"),(void **)&arr)==FAILURE) { can't find... }
else { use arr->value.ht... }

hash_find は pval ポインタへのポインタを受け取り、 pval ポインタではないことに注意してください。

ほとんど全てのハッシュ関数は、SUCCESSまたはFAILUREを返します。 (ただし、論理値を返す hash_exists() を除きます。)

簡単な値を返す場合

関数からの返り値を作成することを容易にするための 複数のマクロが利用可能です。

RETURN_* マクロは、全て返り値をセットし関数から戻ります。

  • RETURN

  • RETURN_FALSE

  • RETURN_TRUE

  • RETURN_LONG(l)

  • RETURN_STRING(s,dup) dupがTRUEの場合、文字列をコピーします。

  • RETURN_STRINGL(s,l,dup) 指定された長さ(l)の文字列(s)を返します。

  • RETURN_DOUBLE(d)

RETVAL_* マクロは返り値をセットしますが、関数から戻りません。

  • RETVAL_FALSE

  • RETVAL_TRUE

  • RETVAL_LONG(l)

  • RETVAL_STRING(s,dup) dup が TRUE の場合、文字列をコピーします。

  • RETVAL_STRINGL(s,l,dup) 指定された長さ (l) の文字列 (s) を返します。

  • RETVAL_DOUBLE(d)

上記の文字列マクロは、全て 's' 引数に estrdup() を行います。 このため、マクロをコールした後、引数を安全に開放することができます。 他の選択肢としては、静的に確保されたメモリを使用することもできます。

関数が論理値として成功/エラーの応答を返す場合、 それぞれ RETURN_TRUE および RETURN_FALSE を使用するようにしてください。

複雑な値を返す場合

関数はオブジェクトや配列といった複雑なデータ型を返すことも可能です。

オブジェクトを返すには、次のようにします。

  1. object_init(return_value) をコールします。

  2. オブジェクトを値で埋めます。この際に用いる関数の一覧を 以下に示します。

  3. 可能ならば、このオブジェクト用の関数を登録します。 オブジェクトから値を得るために、関数は、active_symbol_table から "this" を取得する必要があります。 "this" の型は、IS_OBJECT である必要が あります。これは、基本的に通常のハッシュテーブルです。 (すなわち、.value.ht に通常のハッシュ関数が使用可能です) 実際の関数の登録は、次のようにして実行できます。
    add_method( return_value, function_name, function_ptr );

オブジェクトを操作するための関数を以下に示します。

  • add_property_long( return_value, property_name, l ) - long 型で '1' という値を有する 'property_name' という名前のプロパティを追加します

  • add_property_double( return_value, property_name, d ) - 同じですが、倍精度実数を加えるところが違います。

  • add_property_string( return_value, property_name, str ) - 同じですが、文字列を加えるところが違います。

  • add_property_stringl( return_value, property_name, str, l ) - 同じですが、長さ 'l' の文字列を加えるところが違います。

配列を返すには次のようにします。

  1. array_init(return_value) をコールします。

  2. 配列を値で埋めます。この用途に用いる関数の一覧を以下に示します。

配列を用いるための関数は次のようなものです。

  • add_assoc_long(return_value,key,l) - キー が 'key' のエントリと long 値 'l' を加えます。

  • add_assoc_double(return_value,key,d)

  • add_assoc_string(return_value,key,str,duplicate)

  • add_assoc_stringl(return_value,key,str,length,duplicate) 文字列の長さを指定します。

  • add_index_long(return_value,index,l) - 索引が 'index' となるエントリに long 値 'l' を加えます。

  • add_index_double(return_value,index,d)

  • add_index_string(return_value,index,str)

  • add_index_stringl(return_value,index,str,length) - 文字列長さを指定します。

  • add_next_index_long(return_value,l) - 次にアクセスされる配列エントリと long 値 'l' を加えます。

  • add_next_index_double(return_value,d)

  • add_next_index_string(return_value,str)

  • add_next_index_stringl(return_value,str,length) - 文字列長さを指定します。

リソースリストの使用法

PHP は様々な型のリソースを処理する標準的な手段を有しています。 この手段は、PHP 2.0 のリンク付リストを完全に置きかえるものです。

利用可能な関数:

  • php3_list_insert(ptr, type) - 新規に挿入されたリソースの 'id' を返します。

  • php3_list_delete(id) - id で指定されたリソースを削除します。

  • php3_list_find(id,*type) - 指定した id が指すリソースへのポインタを 返し、type にそのリソースの型を代入します。

通常、これらの関数は SQL ドライバー用に使用されますが、 ファイル記述子の管理等の他の目的に 使用することも可能です。

典型的なコードは、次のようになります。

例 48-7. 新規リソースの追加

RESOURCE *resource;
 
/* ...リソース用のメモリを確保し、リソースを獲得します... */
/* 新規のリソースをリストに追加します */
return_value->value.lval = php3_list_insert((void *) resource, LE_RESOURCE_TYPE);
return_value->type = IS_LONG;

例 48-8. 既存のリソースの使用法

pval *resource_id;
RESOURCE *resource;
int type;
 
convert_to_long(resource_id);
resource = php3_list_find(resource_id->value.lval, &type);
if (type != LE_RESOURCE_TYPE) {
php3_error(E_WARNING,"resource index %d has the wrong type",resource_id->value.lval);
    RETURN_FALSE;
}
/* ...リソースを使用します... */

例 48-9. 既存のリソースの削除

pval *resource_id;
RESOURCE *resource;
int type;
 
convert_to_long(resource_id);
php3_list_delete(resource_id->value.lval);
リソース型は php3_list.h の enum list_entry_type にて登録する必要が あります。加えて、全ての新規リソース型の定義の際、 list.c の list_entry_destructor() の中に shutdown 用のコードを 加える必要があります。(shutdown の中で何もする必要がない場合でも、 空のコードを加える必要があります。)

持続的リソーステーブルの使用

PHP は、持続的リソース(すなわち、アクセス間は保持されるリソース) を保存する標準的な手段を有しています。 この機能を用いた最初のモジュールは、MySQL モジュールであり、 mSQL が後に続きました。 このため、mysql.c を読むことにより持続的リソースの必要性に関する 一般的な感覚を得ることができます。 見る必要がある関数は次のようなものです。

php3_mysql_do_connect
php3_mysql_connect()
php3_mysql_pconnect()

持続的モジュールの一般的な考えは次のようなものです。

  1. セクション (9) に記載された標準リソースリストと共に 動作するモジュール全体をコーディングします。

  2. そのリソースが持続的リソースリストに既に存在するかどうかを 確認する特別な接続関数をコーディングします。 この作業が完了している場合、標準のリソースリストに 持続的リソースリストへのポインタとしてこの関数を登録します。 (1.項により、残りのコードは直ちに動作するはずです。) 完了していない場合、これを作成した後、持続的リソースリストに加え、 標準リソースリストにこの関数を指すポインタを加えます。 この場合、このポインタが標準リソースリストにあるため、 コード全体が動作します。 次回接続した際には、そのリソースが持続的リソースリスト にあるため、再度作成されることなく使用されます。 これらのリソースは、別の型として登録する必要があります。 (例えば、非持続的リンク用に LE_MYSQL_LINK、 持続的リンク用に LE_MYSQL_PLINK)

mysql.c を読むと、より複雑な接続関数を除いてはモジュールの他の部分を 変更する必要がないということが分かるでしょう。

'list' が 'plist' に置きかえられるだけで、 全く同じインターフェースが、標準リソースリストと 持続的リソースリストに存在します。

  • php3_plist_insert(ptr, type) - 新規に挿入されたリソースの 'id' を返します。

  • php3_plist_delete(id) - 指定された id のリソースを削除します。

  • php3_plist_find(id,*type) - 指定された id のリソースへのポインタを返し、 'type' をそのリソースの型で更新します。

しかし、持続的モジュールを実装しようとする際に これらの関数が役に立たないことがわかる場合もありえます。 通常の場合、持続的リソースリストがハッシュテーブルそのものである という事実が利用されます。 例えば、MySQL/mSQL モジュールにおいて、pconnect() コール(持続的接続) があった場合、その関数は、関数に渡されるホスト・ユーザー・パスワード から文字列を作成します。そして、この文字列をキーとして SQL リンクをハッシュに加えます。 次に誰かが pconnect() を同じホスト・ユーザー・パスワードでコール した場合、同じキーが生成されます。 この場合、この関数は持続的リンクのリストにある SQL リンクを見つけます。

より詳細な記述が行われるようになるまで、 plist のハッシュテーブル機能の使用法を知るためには、 mysql.c または msql.c を見る必要があります。

注意すべき重要な点:持続的リソースリストに加えられるリソースは、 PHP のメモリーマネージャで確保されたものであってはなりません。 すなわち、emalloc(),estrdup()等で生成されたものであってはなりません。 むしろ、通常の malloc(),strdup() 等を使用するべきです。 その理由は単純です。リクエストの終了時(ヒットの終了時)に PHP のメモリーマネージャを使用して確保された全てのメモリー領域は、 削除されるからです。 持続的リストは、リクエストの終了時に消去されるようにはなっていないので、 このようなリソース確保するために PHPのメモリーマネージャを使用するべきではありません。

持続的リストに加えようとするリソースを登録する際に、 非持続的リストと持続的リストの両方にデストラクタを 加える必要があります。 非持続的リストのデストラクタは、何もする必要がありません。 持続的リストのデストラクタは、この型により得られた全ての リソース(例えば、メモリー、SQLリンク、等)を開放する必要が あります。 非持続的リソースと全く同様に、全てのリソースについてデストラクタ を備える必要があります。 破棄を要する必要がない場合には、デストラクタは空とすることができます。 emalloc() とその派生したものは、持続的リストに関連して 使用するべきではなく、このような場合、efree() も使うべきではないという ことを覚えておいてください。

実行時設定命令の追加

PHPの機能の多くは、実行時に設定を行うことが可能です。 これらの設定用命令は、php3.ini ファイルまたは、Apache モジュール版の 場合、Apache .conf ファイルに書かれています。 この Apache .conf ファイルの中にこれらを記載することの利点は、 ディレクトリ毎に設定が可能なことです。 これは、例えばあるディレクトリはある safemodeexecdir を 有しているとき、他のディレクトリは、別のものを指定することが できることを意味します。 この設定のきめの細かさは、サーバーが複数の仮想ホストを サポートする場合に特に便利です。

新しい命令を加えるために必要な手順は次のようなものです。

  1. mod_php3.h にある php3_ini_structureに命令を加えます。

  2. main.c の php3_module_startup 関数を編集し、適当なcfg_get_string() またはcfg_get_long()コールを加えます。

  3. mod_php3.c の php3_commands 構造体に命令、制約、コメントを 加えます。制約の部分を記録しておいてください。 RSRC_CONFは、Apache .conf ファイルにのみある命令です。 OR_OPTIONS命令は、通常の .htaccessファイルを含むあらゆる 場所に置くことができます。

  4. php3take1handler()またはphp3flaghandler()はどちらも命令に 関する適当なエントリを追加します。

  5. functions/info.c 中の _php3_info() 関数の 設定セクションに、新しい命令を追加する必要があります。

  6. 最後に、もちろん、新しい命令をどこかで使う必要があります。 この命令は、php3_ini.命令 としてアクセスできます。