章 47. 增加 PHP 3

本章中的內容可能已經比較過時了,它講解了如何增加 PHP3。若果對 PHP 4 感興趣,請閱讀 Zend API 一章。

為 PHP 新增函數

函數原型

所有的函數有著如下形式:
void php3_foo(INTERNAL_FUNCTION_PARAMETERS) {

}
即使函數沒有任何參數,這也是它被呼叫的模式。

函數參數

參數總是屬於 pval 類型。該類型包括了各個參數的實際類型。因此,若果您的函數有兩個參數,您應該在您的函數頂部做如下工作:

例子 47-1. 取得函數參數

pval *arg1, *arg2;
if (ARG_COUNT(ht) != 2 || getParameters(ht,2,&arg1,&arg2)==FAILURE) {
   WRONG_PARAM_COUNT;
}
注意:參數既可以被值(value)傳遞又可以被引用(reference)傳遞。兩種情況您都需要將 &(pval *) 傳遞給 getParameters 函數。若果您想檢查第 n 個參數是否通過引用傳遞給您,您可以使用 ParameterPassedByReference(ht,n) 函數。它的返回值為 0 或是 1。

當您改變了任何已傳遞的參數,無論它們是以值或是引用的模式傳遞,您都可以對它使用 pval_destructor 函數來銷毀它。或是若果它是一個您想增加的陣列,您可以使用類似於 internal_functions.h 中定義函數的方法來將返回值 return_value 作為陣列來動作。

此外當你將一個參數改為 IS_STRING 時要確保先給 estrdup() 出來的字串賦值以及給出字串長度,然後才將它的類型改為 IS_STRING。若果要修改一個已經是 IS_STRING 或 IS_ARRAY 的參數應該先對其運行 pval_destructor。

可變函數參數

函數可以接受不同數目的參數。若果你的函數可以接受 2 或 3 個參數,這樣用:

例子 47-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 的類型欄位中。可以是以下任何一種:

表格 47-1. PHP 內定類型

IS_STRING字串
IS_DOUBLE雙精度浮點型
IS_LONG長整型
IS_ARRAY陣列
IS_EMPTYNone
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); /* If the string is "" or "0" it becomes 0, 1 otherwise */
convert_string_to_number(arg1);  /* Converts string to either LONG or DOUBLE depending on string */

這些函數都是即時轉換的,並不返回任何值。

實際上的參數被存放在一個聯合中,成員為:

  • IS_STRING: arg1->value.str.val

  • IS_LONG: arg1->value.lval

  • IS_DOUBLE: arg1->value.dval

函數中的記憶體管理

函數所使用的任何記憶體都應該通過 emalloc() 或是 estrdup() 來申請。它們是記憶體處理抽像函數,看上去就像普通的 malloc() 和 strdup() 函數一樣。記憶體應該用 efree() 來釋放。

程式中有兩種記憶體:作為變量被返回到直譯器中的記憶體,以及內定函數所需要的臨時存儲空間。當把一個字串賦給一個返回直譯器的變量時需要確保首先通過 emalloc() 或是 estrdup() 配置了記憶體。該記憶體決不該由你來釋放,除非你在同一個函數中後來又覆蓋了原來的賦值(這並不是一種好的寫程式實踐)。

任何在你的函數/庫中所需要的臨時/永久記憶體都要通過這三個函數來進行:emalloc(),estrdup() 和 efree()。它們的行為完全和它們相對應的函數相同。任何通過 emalloc() 或 estrdup() 都要用 efree() 在某個環節釋放,除非它們本來就需要存在到程式結束。否則就會出現記憶體洩漏。「它們的行為完全和它們相對應的函數相同」的意思是:若果你用 efree() 釋放了不是由 emalloc() 或 estrdup() 配置的記憶體時可能會導致段錯誤。所以要留意釋放所有不需要的記憶體。

若果編譯時指定了 "-DDEBUG",PHP 將在腳本運行結束後顯示出所有通過 emalloc() 和 estrdup() 配置但是沒有用 efree() 釋放的記憶體清單。

在符號表中設定變量

有一些巨集可以使在符號表中設定變量更容易:

  • SET_VAR_STRING(name,value)

  • SET_VAR_DOUBLE(name,value)

  • SET_VAR_LONG(name,value)

警示

要注意 SET_VAR_STRING。值的部分必須是人工配置出來的,因為記憶體管理代碼會在以後嘗試釋放該指標。不要將靜態配置的記憶體傳遞給 SET_VAR_STRING。

PHP 中的符號表是以散清單來實現的。任何時候 &symbol_table 都是一個指向 'main' 符號表的指標,active_symbol_table points 指向現用的活動的符號表(二者在開始時等同,而在函數內不同)。

以下例子使用了 'active_symbol_table'。若果你指明要工作於 'main' 符號表,應該用 &symbol_table 來替代。此外,同樣的函數也能像下面說明的作用於陣列。

例子 47-3. 檢查 $foo 是否存在於符號表

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

例子 47-4. 在符號表中找到一個變量的字長

hash_find(active_symbol_table,"foo",sizeof("foo"),&pvalue);
check(pvalue.type);
PHP 中的陣列使用了和符號表同一個散清單來實現。這意味著以上兩個函數也可以用來檢查陣列中的變量。

若果要在符號表中定義一個新陣列,應該這樣做。

首先,要用 hash_exists() 或 hash_find() 檢查它是否存在並且正確中止。

接著,起始化陣列:

例子 47-5. 起始化新陣列

pval arr;

if (array_init(&arr) == FAILURE) { failed... };
hash_update(active_symbol_table,"foo",sizeof("foo"),&arr,sizeof(pval),NULL);
這段代碼在活動符號表中定義了一個名為 $foo 的新陣列。這是個空陣列。

這裡展示了怎樣向其中加入新的條目:

例子 47-6. 向陣列中加入新的條目

pval entry;

entry.type = IS_LONG;
entry.value.lval = 5;

/* defines $foo["bar"] = 5 */
hash_update(arr.value.ht,"bar",sizeof("bar"),&entry,sizeof(pval),NULL);

/* defines $foo[7] = 5 */
hash_index_update(arr.value.ht,7,&entry,sizeof(pval),NULL);

/* defines the next free place in $foo[],
 * $foo[8], to be 5 (works like php2)
 */
hash_next_index_insert(arr.value.ht,&entry,sizeof(pval),NULL);
若果要修改一個插入到散清單中的值,必須先將其提取出來。要避免額外開銷你可以給 hash add 函數提供一個 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 指標。

以上任何 hash 函數返回 SUCCESS 或 FAILURE(除了 hash_exists(),它返回一個布爾真值)。

Returning simple values

A number of macros are available to make returning values from a function easier.

The RETURN_* macros all set the return value and return from the function:

  • RETURN

  • RETURN_FALSE

  • RETURN_TRUE

  • RETURN_LONG(l)

  • RETURN_STRING(s,dup) If dup is TRUE, duplicates the string

  • RETURN_STRINGL(s,l,dup) Return string (s) specifying length (l).

  • RETURN_DOUBLE(d)

The RETVAL_* macros set the return value, but do not return.

  • RETVAL_FALSE

  • RETVAL_TRUE

  • RETVAL_LONG(l)

  • RETVAL_STRING(s,dup) If dup is TRUE, duplicates the string

  • RETVAL_STRINGL(s,l,dup) Return string (s) specifying length (l).

  • RETVAL_DOUBLE(d)

The string macros above will all estrdup() the passed 's' argument, so you can safely free the argument after calling the macro, or alternatively use statically allocated memory.

If your function returns boolean success/error responses, always use RETURN_TRUE and RETURN_FALSE respectively.

Returning complex values

Your function can also return a complex data type such as an object or an array.

Returning an object:

  1. Call object_init(return_value).

  2. Fill it up with values. The functions available for this purpose are listed below.

  3. Possibly, register functions for this object. In order to obtain values from the object, the function would have to fetch "this" from the active_symbol_table. Its type should be IS_OBJECT, and it's basically a regular hash table (i.e., you can use regular hash functions on .value.ht). The actual registration of the function can be done using:
    add_method( return_value, function_name, function_ptr );

The functions used to populate an object are:

  • add_property_long( return_value, property_name, l ) - Add a property named 'property_name', of type long, equal to 'l'

  • add_property_double( return_value, property_name, d ) - Same, only adds a double

  • add_property_string( return_value, property_name, str ) - Same, only adds a string

  • add_property_stringl( return_value, property_name, str, l ) - Same, only adds a string of length 'l'

Returning an array:

  1. Call array_init(return_value).

  2. Fill it up with values. The functions available for this purpose are listed below.

The functions used to populate an array are:

  • add_assoc_long(return_value,key,l) - add associative entry with key 'key' and long value '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) specify the string length

  • add_index_long(return_value,index,l) - add entry in index 'index' with long value 'l'

  • add_index_double(return_value,index,d)

  • add_index_string(return_value,index,str)

  • add_index_stringl(return_value,index,str,length) - specify the string length

  • add_next_index_long(return_value,l) - add an array entry in the next free offset with long value 'l'

  • add_next_index_double(return_value,d)

  • add_next_index_string(return_value,str)

  • add_next_index_stringl(return_value,str,length) - specify the string length

Using the resource list

PHP has a standard way of dealing with various types of resources. This replaces all of the local linked lists in PHP 2.0.

Available functions:

  • php3_list_insert(ptr, type) - returns the 'id' of the newly inserted resource

  • php3_list_delete(id) - delete the resource with the specified id

  • php3_list_find(id,*type) - returns the pointer of the resource with the specified id, updates 'type' to the resource's type

Typically, these functions are used for SQL drivers but they can be used for anything else; for instance, maintaining file descriptors.

Typical list code would look like this:

例子 47-7. Adding a new resource

RESOURCE *resource;

/* ...allocate memory for resource and acquire resource... */
/* add a new resource to the list */
return_value->value.lval = php3_list_insert((void *) resource, LE_RESOURCE_TYPE);
return_value->type = IS_LONG;

例子 47-8. Using an existing resource

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;
}
/* ...use resource... */

例子 47-9. Deleting an existing resource

pval *resource_id;
RESOURCE *resource;
int type;

convert_to_long(resource_id);
php3_list_delete(resource_id->value.lval);
The resource types should be registered in php3_list.h, in enum list_entry_type. In addition, one should add shutdown code for any new resource type defined, in list.c's list_entry_destructor() (even if you don't have anything to do on shutdown, you must add an empty case).

Using the persistent resource table

PHP has a standard way of storing persistent resources (i.e., resources that are kept in between hits). The first module to use this feature was the MySQL module, and mSQL followed it, so one can get the general impression of how a persistent resource should be used by reading mysql.c. The functions you should look at are:

php3_mysql_do_connect
php3_mysql_connect()
php3_mysql_pconnect()

The general idea of persistence modules is this:

  1. Code all of your module to work with the regular resource list mentioned in section (9).

  2. Code extra connect functions that check if the resource already exists in the persistent resource list. If it does, register it as in the regular resource list as a pointer to the persistent resource list (because of 1., the rest of the code should work immediately). If it doesn't, then create it, add it to the persistent resource list AND add a pointer to it from the regular resource list, so all of the code would work since it's in the regular resource list, but on the next connect, the resource would be found in the persistent resource list and be used without having to recreate it. You should register these resources with a different type (e.g. LE_MYSQL_LINK for non-persistent link and LE_MYSQL_PLINK for a persistent link).

If you read mysql.c, you'll notice that except for the more complex connect function, nothing in the rest of the module has to be changed.

The very same interface exists for the regular resource list and the persistent resource list, only 'list' is replaced with 'plist':

  • php3_plist_insert(ptr, type) - returns the 'id' of the newly inserted resource

  • php3_plist_delete(id) - delete the resource with the specified id

  • php3_plist_find(id,*type) - returns the pointer of the resource with the specified id, updates 'type' to the resource's type

However, it's more than likely that these functions would prove to be useless for you when trying to implement a persistent module. Typically, one would want to use the fact that the persistent resource list is really a hash table. For instance, in the MySQL/mSQL modules, when there's a pconnect() call (persistent connect), the function builds a string out of the host/user/passwd that were passed to the function, and hashes the SQL link with this string as a key. The next time someone calls a pconnect() with the same host/user/passwd, the same key would be generated, and the function would find the SQL link in the persistent list.

Until further documented, you should look at mysql.c or msql.c to see how one should use the plist's hash table abilities.

One important thing to note: resources going into the persistent resource list must *NOT* be allocated with PHP's memory manager, i.e., they should NOT be created with emalloc(), estrdup(), etc. Rather, one should use the regular malloc(), strdup(), etc. The reason for this is simple - at the end of the request (end of the hit), every memory chunk that was allocated using PHP's memory manager is deleted. Since the persistent list isn't supposed to be erased at the end of a request, one mustn't use PHP's memory manager for allocating resources that go to it.

When you register a resource that's going to be in the persistent list, you should add destructors to it both in the non-persistent list and in the persistent list. The destructor in the non-persistent list destructor shouldn't do anything. The one in the persistent list destructor should properly free any resources obtained by that type (e.g. memory, SQL links, etc). Just like with the non-persistent resources, you *MUST* add destructors for every resource, even it requires no destruction and the destructor would be empty. Remember, since emalloc() and friends aren't to be used in conjunction with the persistent list, you mustn't use efree() here either.

Adding runtime configuration directives

Many of the features of PHP can be configured at runtime. These configuration directives can appear in either the designated php3.ini file, or in the case of the Apache module version in the Apache .conf files. The advantage of having them in the Apache .conf files is that they can be configured on a per-directory basis. This means that one directory may have a certain safemodeexecdir for example, while another directory may have another. This configuration granularity is especially handy when a server supports multiple virtual hosts.

The steps required to add a new directive:

  1. Add directive to php3_ini_structure struct in mod_php3.h.

  2. In main.c, edit the php3_module_startup function and add the appropriate cfg_get_string() or cfg_get_long() call.

  3. Add the directive, restrictions and a comment to the php3_commands structure in mod_php3.c. Note the restrictions part. RSRC_CONF are directives that can only be present in the actual Apache .conf files. Any OR_OPTIONS directives can be present anywhere, include normal .htaccess files.

  4. In either php3take1handler() or php3flaghandler() add the appropriate entry for your directive.

  5. In the configuration section of the _php3_info() function in functions/info.c you need to add your new directive.

  6. And last, you of course have to use your new directive somewhere. It will be addressable as php3_ini.directive.