首頁  >  文章  >  後端開發  >  [翻譯][php擴充開發與嵌入式]第13章-php的INI設置

[翻譯][php擴充開發與嵌入式]第13章-php的INI設置

黄舟
黄舟原創
2017-02-10 10:17:041059瀏覽


INI設定

和上一章你看到的超級全域變數以及持久化常數一樣, php.iniini值必須在執行的MINIT變數和其他特性不同的是, INI選項的定義僅由簡單的啟動/終止線組成.

PHP_MINIT_FUNCTION(sample4)
{
    REGISTER_INI_ENTRIES();
    return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(sample4)
{
    UNREGISTER_INI_ENTRIES();
    return SUCCESS;
}

定義並訪問INI設置

INI指令本身是在源碼文件中MINIT函數上面下面的巨集完全獨立的定義的, 在這兩個巨集之間可以定義一個或多個INI指令:

PHP_INI_BEIGN()
PHP_INI_END()

這兩個宏函數和ZEND_BEGIN_MODULE_GLGLGLMOm/ENDEND不過這裡不是typdef一個結構體, 而是對靜態資料實例定義的框架組織:

static zend_ini_entry ini_entries[] = {
{0,0,NULL,0,NULL,NULL,NULL,NULL,NULL,0,NULL,0,0,NULL} };

如你所見, 它定義了一個zend_ini_entry值的向量, 以空的向量記錄. 這和你在前面看到的靜態向量function_entry的定義一致.

簡單的INI設定

現在, 你已經有一個INI結構體用於定義INI指令, 以及引擎註冊指令INI設定的機制, 因此我們可以真正的去為你的擴展定義一些INI指令了. 假設你的擴展暴露了一個打招呼的函數, 就像第5章"你的第一個擴展"中一樣, 不過,你想要讓打招呼的話可以自訂:

PHP_FUNCTION(sample4_hello_world)
{
    php_printf("Hello World!\n");
}

最簡單最直接的方式就是定義一個INI指令, 並給它一個預設值"Hello world!":

rrrereee你

可能已經猜到了, 這個宏的前兩個參數表示INI指令的名字和它的默認值. 第三個參數用來確定引擎是否允許這個INI指令被修改(這將涉及到本章後面要介紹的訪問級別問題). 最後一個參數是一個回調函數, 它將在每次INI指令的值發生變化時被調用. 你將在修改事件一節看到這個參數的細節.

譯註: 如果你和譯者一樣遇到結果和原著結果預期不一致時, 請在測試時, 在你的MINIT()函數中增加一句"REGISTER_INI_ENTRIES();"調用, 並確保該調用在你的MINIT中分配全局空間之後執行.

現在你的INI設定已經定義, 只需要在你的打招呼函數中使用就可以了.

#include "php_ini.h"
PHP_INI_BEGIN()
    PHP_INI_ENTRY("sample4.greeting", "Hello World",
                                    PHP_INI_ALL, NULL)
PHP_INI_END()
一定要注意, char *的值是引擎所有的, 一定不要修改. 正因為這樣, 所以將你本地用來臨時儲存INI設定值的變數定義為const修飾. 當然, 並不是所有的INI值都是字串; 還有其他的宏用來取得整數, 浮點型以及布爾型的值:

PHP_FUNCTION(sample4_hello_world)
{
    const char *greeting = INI_STR("sample4.greeting");
    php_printf("%s\n", greeting);
}

通常你想要知道的是INI設定的當前值; 不過, 作為補充, 存在幾個宏可以用來讀取未經修改的INI設定值:

long lval = INI_INT("sample4.intval");
double dval = INI_FLT("sample4.fltval");
zend_bool bval = INI_BOOL("sample4.boolval");

這個例子中, INI指令的名字"sample4.greeting"增加了擴展名作為前綴, 這樣來保證不會和其他擴展暴露的INI指令名字衝突. 對於私有的擴展來說, 這個前綴不是必須的, 但是對於商業化或開源發布的公開擴展還是鼓勵這樣做的.

訪問級別

對於INI默認指令值值不變; 然而, 對於某些特殊的環境或腳本內特定的動作, 這些值可能需要被修改. 如下表所示, INI指令的值可能在下面3個點被修改:


位於,去修改INI

訪問等級

意義

php.ini

apachehttpd.conf設定檔指令可以影響外部引擎和認為是 INI設定的"全域".

位於Apachehttpd.conf設定檔中 或請求腳本所在目錄或虛擬主機下的.htaccess檔案以及其他apache在處理請求之前其他地方設定的INI指令. 一旦腳本開始執行,就只能透過呼叫使用者空間函數ini_set()

設定了.

INI


某些设置如果可以在任何地方被修改就没有多大意义了, 比如safe_mode, 如果可以在任何地方去修改, 那么恶意脚本的作者就可以很简单的去禁用safe_mode, 接着去读或修改本不允许操作的文件.

类似的, 某些非安全相关的指令比如register_globals或magic_quotes_gpc, 在脚本中不能被修改, 因为, 在脚本执行时, 它所影响的事情已经发生过了.

这些指令的访问控制是通过PHP_INI_ENTRY()的第三个参数完成的. 在你前面例子中, 使用了PHP_INI_ALL, 它的定义是一个位域操作: PHP_INI_SYSTEM | PHP_INI_PERDIR | PHP_INI_USER.

对于register_globals和magic_quotes_gpc这样的指令, 定义的访问级别为PHP_INI_SYSTEM | PHP_INI_PERDIR. 排除了PHP_INI_USER将导致以这个名字调用ini_set()时最终会失败.

现在, 你可能已经猜到, safe_mode和open_basedir这样的指令应该仅被定义为PHP_INI_SYSTEM. 这样的设置就确保了只有系统管理员可以修改这些值, 因为只有它们可以访问修改php.ini或httpd.conf文件中的配置.

修改事件

当INI指令被修改时, 无论是通过ini_set()函数还是某个perdir指令的处理, 引擎都会为其测试OnModify回调. 修改处理器可以使用ZEND_INI_MH()宏定义, 并通过在OnModify参数上传递函数名附加到INI指令上:

ZEND_INI_MH(php_sample4_modify_greeting)
{
    if (new_value_length == 0) {
        return FAILURE;
    }
    return SUCCESS;
}
PHP_INI_BEGIN()
    PHP_INI_ENTRY("sample4.greeting", "Hello World",
            PHP_INI_ALL, php_sample4_modify_greeting)
PHP_INI_END()

通过在new_value_length为0时返回FAILURE, 这个修改处理器禁止将greeting设置为空字符串. ZEND_INI_MH()宏产生的整个原型如下:

int php_sample4_modify_greeting(zend_ini_entry *entry,
    char *new_value, uint new_value_length,
    void *mh_arg1, void *mh_arg2, void *mh_arg3,
    int stage TSRMLS_DC);

各个参数的含义见下表:


這個值將被設定到entry->value,5個值之一MINIT, MSHUTDOWN, RINIT, RSHUTDOWN,

参数名

含义

entry

指向引擎真實儲存的INI指令項.這個結構體提供了當前值以及其他一些下面程式碼(zend_ini_entry結構體結構)列出的資訊 .如果處理器回傳 SUCCESS,

同時如果entry->orig_value

,並設定,並設定entry->modified標記.這個字串的長度透過 mh_arg1, 2 , 3

3個指針對應INI指令定義時給出的資料指標(zend_entry指令定義時給出的資料指標(zend_entry 實際上,這幾個值是引擎內部處理使用的,你不需要關心它們. STAGE_系列的

: STARTUP, SHUTDOWN, ACTIVATE, DEACTIVATE, RUNTIME. 這些常數對應於

以及活性腳本執行🜎🜎🎟


核心结构体: zend_ini_entry

struct _zend_ini_entry {
    int module_number;
    int modifiable;
    char *name;
    uint name_length;
    ZEND_INI_MH((*on_modify));
    void *mh_arg1;
    void *mh_arg2;
    void *mh_arg3;

    char *value;
    uint value_length;

    char *orig_value;
    uint orig_value_length;
    int modified;

    void ZEND_INI_DISP(*displayer);
};

展示INI设置

在上一章, 你看到了MINFO函数以及相关的指令用于展示扩展的信息. 由于扩展暴露INI指令是很常见的, 因此引擎提供了一个公共的宏可以放置到PHP_MINFO_FUNCTION()中用于展示INI指令信息.

PHP_MINFO_FUNCTION(sample4)
{
    DISPLAY_INI_ENTRIES();
}

这个宏将迭代PHP_INI_BEGIN()和PHP_INI_END()宏之间定义的INI指令集和, 在一个3列的表格中展示它们的INI指令名, 原始值(全局的), 以及当前值(经过PERDIR指令或ini_set()调用修改后)

默认情况下, 所有的指令都直接以其字符串形式输出. 对于某些指令, 比如布尔值以及用于语法高亮的颜色值, 则在展示处理时应用了其他格式. 这些格式是通过每个INI设置的显示处理器处理的, 它和你看到的OnModify一样是一个动态的回调函数指针.

显示处理器可以使用PHP_INI_ENTRY()宏的扩展版指定, 它接受一个额外的参数. 如果设置为NULL, 则使用展示字符串值的处理器作为默认处理器:

PHP_INI_ENTRY_EX("sample4.greeting", "Hello World", PHP_INI_ALL,
    php_sample4_modify_greeting, php_sample4_display_greeting)

显然, 需要在INI设置定义之前声明这个函数. 和OnModify回调函数一样, 这可以通过一个包装宏以及少量编码完成:

#include "SAPI.h" /* needed for sapi_module */
PHP_INI_DISP(php_sample4_display_greeting)
{
    const char *value = ini_entry->value;

    /* 选择合适的当前值或原始值 */
    if (type == ZEND_INI_DISPLAY_ORIG &&
        ini_entry->modified) {
        value = ini_entry->orig_value;
    }

    /* 使得打招呼的字符串粗体显示(当以HTML方式输出时) */
    if (sapi_module.phpinfo_as_text) {
        php_printf("%s", value);
    } else {
        php_printf("<b>%s</b>", value);
    }
}

绑定到扩展的全局空间

所有的INI指令都在Zend引擎内有一块存储空间, 可以用以跟踪脚本内的变更并进行请求外部的全局设置维护. 在这块存储空间中, 所有的INI指令都以字符串值存储. 你已经知道了, 这些值可以使用INI_INT(), INI_FLT(), INI_BOOL()等宏函数, 很简单的翻译成其他的标量类型.

这个查找和转换过程由于两个原因非常低效: 首先, 每次一个INI的值在获取时, 它必须通过名字在一个HashTable中进行定位. 这种查找方式对于仅在运行时编译的用户空间脚本而言是没有问题的, 但是对于已编译的机器代码源, 运行时做这个工作就毫无意义.

每次请求标量值的时候都需要将底层的字符串值转换到标量值是非常低效的. 因此我们使用你已经学习过的线程安全全局空间作为存储媒介, 每次INI指令值变更时更新它即可. 这样, 所有访问INI指令的代码都只需要查找你的线程安全全局空间结构体中的某个指针即可, 这样就获得了编译期优化的优点.

在你的php_sample4.h文件MODULE_GLOBALS结构体中增加const char *greeting; 接着更新sample4.c中的下面两个方法:

ZEND_INI_MH(php_sample4_modify_greeting)
{
    /* Disallow empty greetings */
    if (new_value_length == 0) {
        return FAILURE;
    }
    SAMPLE4_G(greeting) = new_value;
    return SUCCESS;
}
PHP_FUNCTION(sample4_hello_world)
{
    php_printf("%s\n", SAMPLE4_G(greeting));
}

由于这是对INI访问的一种非常常见的优化方式, 因此引擎暴露了一组专门处理INI指令到全局变量的绑定宏:

STD_PHP_INI_ENTRY_EX("sample4.greeting", "Hello World",
    PHP_INI_ALL, OnUpdateStringUnempty, greeting,
    zend_sample4_globals, sample4_globals,
    php_sample4_display_greeting)


这个宏执行和上面你自己的php_sample4_modify_greeting相同的工作, 但它不需要OnModify回调. 取而代之的是, 它使用了一个泛化的修改回调OnUpdateStringUnempty, 以及信息应该存储的空间. 如果要允许空的greeting指令值, 你可以直接指定OnUpdateString替代OnUpdateStringUnempty.

类似的, INI指令也可以绑定long, double, zend_bool的标量值. 在你的php_sample4.h中MODULE_GLOBALS结构体上增加几个字段:

long mylong;
double mydouble;
zend_bool mybool;

现在在你的PHP_INI_BEGIN()/PHP_INI_END()代码块中使用STD_PHP_INI_ENTRY()宏创建新的INI指令, 它和对应的_EX版本的宏的区别只是显示处理器以及绑定到的值不同.

STD_PHP_INI_ENTRY("sample4.longval", "123",
    PHP_INI_ALL, OnUpdateLong, mylong,
    zend_sample4_globals, sample4_globals)
STD_PHP_INI_ENTRY("sample4.doubleval", "123.456",
    PHP_INI_ALL, OnUpdateDouble, mydouble,
    zend_sample4_globals, sample4_globals)
STD_PHP_INI_ENTRY("sample4.boolval", "1",
    PHP_INI_ALL, OnUpdateBool, mybool,
    zend_sample4_globals, sample4_globals)

这里要注意, 如果调用了DISPLAY_INI_ENTRIES(), 布尔类型的INI指令"sample4.boolval"将和其他设置一样, 被显示为它的字符串值; 然而, 首选的布尔值指令应该被显示为"on"或"off". 要使用这些更加表意的显示, 你可以使用STD_PHP_INI_ENTRY_EX()宏并创建显示处理器, 或者使用另外一个宏:

STD_PHP_INI_BOOLEAN("sample4.boolval", "1",
    PHP_INI_ALL, OnUpdateBool, mybool,
    zend_sample4_globals *, sample4_globals)

这个特定类型的宏是布尔类型特有的, 它提供的是将布尔值转换为"on"/"off"值的显示处理器.

小结

在本章, 你了解了php语言中最古老的特性之一的实现, 它也是阻碍php可移植的罪魁. 对于每个新的INI设置, 都会使得编写可移植代码变得更加复杂. 使用这些特性要非常慎重, 因为扩展以后时钟都要使用它了. 并且, 在使用时要注意不同系统间的行为一致性, 以免在维护时出现不可预期的状况.

接下来的三张, 我们将深入到流API, 开始使用流的实现层和包装操作, 上下文, 过滤器等.

以上就是[翻译][php扩展开发和嵌入式]第13章-php的INI设置 的内容,更多相关内容请关注PHP中文网(www.php.cn)!


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn