搜尋
首頁php教程php手册php中動態修改ini配置_php基礎

1,運行時改變配置
在前一篇文章中曾經談到,ini_set函數可以在php執行的過程中,動態修改php的部分配置。請注意,只是部分,並非所有的配置都可以動態修改。關於ini配置的可修改性,請參閱:http://php.net/manual/zh/configuration. changes.modes.php

我們直接進入ini_set的實現,函數雖然有點長,但是邏輯很清晰:

複製程式碼 程式碼如下:

PHP_FUNCTION(ini_set)
{
char *varname, *new_value;
int varname_len, new_value_len;
字元*舊值;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &varname, &varname_len, &new_value, &new_value_len) == 失敗) {
返回;
}

    // 去EG(ini_directives)中取得設定的值
old_value = zend_ini_string(varname, varname_len 1, 0);

    /* 複製返回此處,因為 alter 可能會釋放它! */
如果(舊值){
RETVAL_STRING(舊值, 1);
} 否則{
RETVAL_FALSE;
}

    // 如果開啟了安全模式,那麼以下這些ini配置可能涉及檔案操作,需要要輔助檢查uid
#define _CHECK_PATH(var, var_len, ini) php_ini_check_path(var, var_len, ini, sizeof(ini))
    /* safe_mode & basedir check */
    if (PG(safe_mode) || PG(open_basedir)) {
        if (_CHECK_PATH(varname, varname_len, "error_log") ||
            _CHECK_PATH(varname, varname_len, "java.class.path") ||
            _CHECK_PATH(varname, varname_len, "java.home") ||
            _CHECK_PATH(varname, varname_len, "mail.log") ||
            _CHECK_PATH(varname, varname_len, "java.library.path") ||
            _CHECK_PATH(varname, varname_len, "vpopmail.directory")) {
            if (PG(safe_mode) && (!php_checkuid(new_value, NULL, CHECKUID_CHECK_FILE_AND_DIR))) {
                zval_dtor(return_value);
                RETURN_FALSE;
            }
            if (php_check_open_basedir(new_value TSRMLS_CC)) {
                zval_dtor(return_value);
                RETURN_FALSE;
            }
        }
    }

    // 在安全模式下,如下這些ini受到保護,不會被動態修改
    if (PG(safe_mode)) {
        if (!strncmp("max_execution_time", varname, sizeof("max_execution_time")) ||
            !strncmp("memory_limit", varname, sizeof("memory_limit")) ||
            !strncmp("child_terminate", varname, sizeof("child_terminate"))
        ) {
            zval_dtor(return_value);
            RETURN_FALSE;
        }
    }

    // 呼叫zend_alter_ini_entry_ex去動態修改ini設定
    if (zend_alter_ini_entry_ex(varname, varname_len 1, new_value, new_value_len, PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0 TSRMLS_CC) == FAILURE) {
        zval_dtor(return_value);
        RETURN_FALSE;
    }
}

可以看到,除了一些必要的驗證工作,主要是呼叫zend_alter_ini_entry_ex。

我們繼續跟進到zend_alter_ini_entry_ex函數:

複製程式碼 程式碼如下:

ZEND_API int zend_alter_ini_entry_ex(char *name, uint name_length, char *new_value, uint new_value_length, int modify_type, int stage, int force_change TSRMLS_DC) /* {{{ */
{
    zend_ini_entry *ini_entry;
    char *duplicate;
    zend_bool modifiable;
    zend_bool modified;

    // 求出EG(ini_directives)對應的ini_entry
    if (zend_hash_find(EG(ini_directives), name, name_length, (void **) &ini_entry) == FAILURE) {
        return FAILURE;
    }

    // 是否被修改及可修改性
    modifiable = ini_entry->modifiable;
    modified = ini_entry->modified;

    if (stage == ZEND_INI_STAGE_ACTIVATE && modify_type == ZEND_INI_SYSTEM) {
        ini_entry->modifiable = ZEND_INI_SYSTEM;
    }

    // 是否強制修改
    if (!force_change) {
        if (!(ini_entry->modifiable & modify_type)) {
            return FAILURE;
        }
    }

    // EG(modified_ini_directives)用來存放已修改過的ini_entry
    // 主要用做恢復
    if (!EG(modified_ini_directives)) {
        ALLOC_HASHTABLE(EG(modified_ini_directives));
        zend_hash_init(EG(modified_ini_directives), 8, NULL, NULL, 0);
    }
   
    // 將ini_entry中的值,值的長度,可修改範圍,保留到orig_xxx中去
    // 以便在請求結束的時候,可以對ini_entry做恢復
    if (!modified) {
        ini_entry->orig_value = ini_entry->value;
        ini_entry->orig_value_length = ini_entry->value_length;
        ini_entry->orig_modifiable = modifiable;
        ini_entry->modified = 1;
        zend_hash_add(EG(modified_ini_directives), name, name_length, &ini_entry, sizeof(zend_ini_entry*), NULL);
    }

    duplicate = estrndup(new_value, new_value_length);

    // 呼叫modify來更新XXX_G中對應的ini配置
    if (!ini_entry->on_modify || ini_entry->on_modify(ini_entry, duplicate, new_value_length, ini_entry-phpcnphpphpcnmh_argini, ini_value_length, ini_entry-php當== SUCCESS) {
        // 同上面,若有多次修改,則需要釋放前一次修改的數值
        if (modified && ini_entry->orig_value != ini_entry->value) {
            efree(ini_entry->value);
        }
        ini_entry->value = duplicate;
        ini_entry->value_length = new_value_length;
    } else {
        efree(duplicate);
        return FAILURE;
    }

    return SUCCESS;
}

有3處邏輯需要我們仔細體會:

1)ini_entry中的modified欄位用來表示該配置是否被動態修改過。一旦該ini配置發生修改,modified就會被置為1。上述程式碼中有一段很關鍵:

複製程式碼 程式碼如下:

// 如果多次呼叫ini_set,則orig_value等始終保持最原始的值
if (!modified) {
    ini_entry->orig_value = ini_entry->value;
    ini_entry->orig_value_length = ini_entry->value_length;
    ini_entry->orig_modifiable = modifiable;
    ini_entry->modified = 1;
    zend_hash_add(EG(modified_ini_directives), name, name_length, &ini_entry, sizeof(zend_ini_entry*), NULL);
}

這段程式碼表示,不管我們先後在php程式碼中呼叫幾次ini_set,只有第一次ini_set時才會進入這段邏輯,設定好orig_value。從第二次呼叫ini_set開始,便不會再執行這段分支,因為此時的modified已經被置為1了。因此,ini_entry->orig_value始終保存的是第一次修改之前的配置值(即最原始的配置)。

2)為了能讓ini_set修改的配置立即生效,需要on_modify回呼函數。

如前一篇所述,呼叫on_modify是為了能夠更新模組的全域變數。再次回憶下,首先,模組全域變數中的配置已經不是字串型了,該用bool用bool、該用int用int。其次,每一個ini_entry中都儲存了該模組全域變數的位址以及對應的偏移量,使得on_modify可以很迅速的進行記憶體修改。另外別忘記,on_modify調用完了之後,仍需進一步更新ini_entry->value,這樣EG(ini_directives)中的配置值就是最新的了。

3)這裡出現了一張新的hash表,EG(modified_ini_directives)。

EG(modified_ini_directives)只用於存放被動態修改過的ini配置,如果一個ini配置被動態修改過,那麼它既存在於EG(ini_directives)中,又存在於EG(modified_ini_directives)。既然每個ini_entry都有modified字段做標記,那豈不是可以遍歷EG(ini_directives)來獲得所有被修改過的配置呢?

答案是肯定的。個人覺得,這裡的EG(modified_ini_directives)主要還是為了提升性能,醬直接遍歷EG(modified_ini_directives)就足夠了。此外,將EG(modified_ini_directives)的初始化延後到zend_alter_ini_entry_ex中,也可以看出php在細節上的效能最佳化點。

2,恢復配置
ini_set的作用時間和php.ini檔案的作用時間是不一樣的,一旦請求執行結束,則ini_set會失效。此外,當我們程式碼中呼叫了ini_restore函數,則先前透過ini_set設定的配置也會失效。

每一個php請求執行完畢之後,會觸發php_request_shutdown,它和php_request_startup是兩個相對應過程。如果php是掛接在apache/nginx下,每處理完一個http請求,就會呼叫php_request_shutdown;如果php以CLI模式來執行,則腳本執行完畢之後,也會呼叫php_request_shutdown。

在php_request_shutdown中,我們可以看到針對ini的復原處理:

複製程式碼 程式碼如下:

/* 7. Shutdown scanner/executor/compiler and restore ini entries */
zend_deactivate(TSRMLS_C);

進入zend_deactivate,可以進一步看到呼叫了zend_ini_deactivate函數,由zend_ini_deactivate來負責將php的配置進行復原。

複製程式碼 程式碼如下:

zend_try {
    zend_ini_deactivate(TSRMLS_C);
} zend_end_try();

具體來看看zend_ini_deactivate的實作:

複製程式碼 程式碼如下:

ZEND_API int zend_ini_deactivate(TSRMLS_D) /* {{{ */
{
    if (EG(modified_ini_directives)) {
        // 遍歷EG(modified_ini_directives)中這張表
        // 對每一個ini_entry呼叫zend_restore_ini_entry_wrapper
        zend_hash_apply(EG(modified_ini_directives), (apply_func_t) zend_restore_ini_entry_wrapper TSRMLS_CC);
       
        // 回收作業
        zend_hash_destroy(EG(modified_ini_directives));
        FREE_HASHTABLE(EG(modified_ini_directives));
        EG(modified_ini_directives) = NULL;
    }
    return SUCCESS;
}

從zend_hash_apply來看,真正恢復ini的任務最終落地到了zend_restore_ini_entry_wrapper回呼函數。

複製程式碼 程式碼如下:

static int zend_restore_ini_entry_wrapper(zend_ini_entry **ini_entry TSRMLS_DC)
{
    // zend_restore_ini_entry_wrapper就是zend_restore_ini_entry_cb的封裝
    zend_restore_ini_entry_cb(*ini_entry, ZEND_INI_STAGE_DEACTIVATE TSRMLS_CC);
    return 1;
}

static int zend_restore_ini_entry_cb(zend_ini_entry *ini_entry, int stage TSRMLS_DC)
{
    int result = FAILURE;

    // 只看修改過的ini項
    if (ini_entry->modified) {
        if (ini_entry->on_modify) {
            // 使用orig_value,且對XXX_G內的相關欄位重新設定
            zend_try {
                result = ini_entry->on_modify(ini_entry, ini_entry->orig_value, ini_entry-php-phpd _arg2, ini_entry->mh_arg3, stage TSRMLS_CC);
            } zend_end_try();
        }
        if (stage == ZEND_INI_STAGE_RUNTIME && result == FAILURE) {
            /* runtime failure is OK */
            return 1;
        }
        if (ini_entry->value != ini_entry->orig_value) {
            efree(ini_entry->value);
        }
       
        // ini_entry本身恢復至最原始的數值
        ini_entry->value = ini_entry->orig_value;
        ini_entry->value_length = ini_entry->orig_value_length;
        ini_entry->modifiable = ini_entry->orig_modifiable;
        ini_entry->modified = 0;
        ini_entry->orig_value = NULL;
        ini_entry->orig_value_length = 0;
        ini_entry->orig_modifiable = 0;
    }
    return 0;
}

邏輯都蠻清晰的,相信讀者可以看懂。總結一下關於ini配置的復原流程:

複製程式碼 程式碼如下:

php_request_shutdown--->zend_deactivate--->zend_ini_deactivate--->zend_restore_ini_entry_wrapper--->zend_restore_ini_entry_cb

3,配置的銷毀
在sapi生命週期結束的時候,例如apache關閉,cli程式執行完畢等等。一旦進入到這個階段,之前所說的configuration_hash,EG(ini_directives)等都需要被銷毀,其用到的記憶體空間需要被釋放。

1,php會依序結束所有的模組,在每個模組的PHP_MSHUTDOWN_FUNCTION中呼叫UNREGISTER_INI_ENTRIES。 UNREGISTER_INI_ENTRIES和REGISTER_INI_ENTRIES對應,但UNREGISTER_INI_ENTRIES並不負責模組全域空間的釋放,XXX_globals這塊記憶體放在靜態資料區上,無須人為回收。

UNREGISTER_INI_ENTRIES主要做的事情,是將某個模組的ini_entry配置從EG(ini_directives)表中刪除。刪除之後,ini_entry本身的空間會被回收,但ini_entry->value不一定會被回收。

當所有模組的PHP_MSHUTDOWN_FUNCTION都呼叫UNREGISTER_INI_ENTRIES一遍之後,EG(ini_directives)中只剩下了Core模組的ini配置。此時,就需要手動呼叫UNREGISTER_INI_ENTRIES,來完成對Core模組配置的刪除工作。

複製程式碼 程式碼如下:

void php_module_shutdown(TSRMLS_D)
{
    ...
   
    // zend_shutdown會依序關閉除了Core之外的所有php模組
    // 關閉時會呼叫各模組的PHP_MSHUTDOWN_FUNCTION
    zend_shutdown(TSRMLS_C);
   
    ...

    // 至此,EG(ini_directives)只剩下了Core模組的設定
    // 這裡手動清理一下
    UNREGISTER_INI_ENTRIES();
   
    // 回收configuration_hash
    php_shutdown_config();

    // 回收EG(ini_directives)
    zend_ini_shutdown(TSRMLS_C);

    ...
}

當手動呼叫UNREGISTER_INI_ENTRIES完成之後,EG(ini_directives)已經不包含任何的元素,理論上講,此時的EG(ini_directives)是一張空的hash表。

2,configuration_hash的回收發生在EG(ini_directives)之後,上面貼出的程式碼中有關於php_shutdown_config的函數呼叫。 php_shutdown_config主要負責回收configuration_hash。

複製程式碼 程式碼如下:

int php_shutdown_config(void)
{
    // 回收configuration_hash
    zend_hash_destroy(&configuration_hash);
   
    ...
   
    return SUCCESS;
}

注意zend_hash_destroy並不會釋放configuration_hash本身的空間,同XXX_G存取的模組全域空間一樣,configuration_hash也是全域變量,無需手動回收。

3,當php_shutdown_config完成時,只剩下EG(ini_directives)的自身空間還沒被釋放。因此最後一步呼叫zend_ini_shutdown。 zend_ini_shutdown用來釋放EG(ini_directives)。在前文已經提到,此時的EG(ini_directives)理論上是一張空的hash表,因此該HashTable本身所佔用的空間需要被釋放。

複製程式碼 程式碼如下:

ZEND_API int zend_ini_shutdown(TSRMLS_D)
{
    // EG(ini_directives)是動態分配的空間,需要回收
    zend_hash_destroy(EG(ini_directives));
    free(EG(ini_directives));
    return SUCCESS;
}

4,總結
用一張圖大致描述一下和ini配置相關的流程:

php中動態修改ini配置_php基礎

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
php怎么把负数转为正整数php怎么把负数转为正整数Apr 19, 2022 pm 08:59 PM

php把负数转为正整数的方法:1、使用abs()函数将负数转为正数,使用intval()函数对正数取整,转为正整数,语法“intval(abs($number))”;2、利用“~”位运算符将负数取反加一,语法“~$number + 1”。

php怎么实现几秒后执行一个函数php怎么实现几秒后执行一个函数Apr 24, 2022 pm 01:12 PM

实现方法:1、使用“sleep(延迟秒数)”语句,可延迟执行函数若干秒;2、使用“time_nanosleep(延迟秒数,延迟纳秒数)”语句,可延迟执行函数若干秒和纳秒;3、使用“time_sleep_until(time()+7)”语句。

php怎么除以100保留两位小数php怎么除以100保留两位小数Apr 22, 2022 pm 06:23 PM

php除以100保留两位小数的方法:1、利用“/”运算符进行除法运算,语法“数值 / 100”;2、使用“number_format(除法结果, 2)”或“sprintf("%.2f",除法结果)”语句进行四舍五入的处理值,并保留两位小数。

php字符串有没有下标php字符串有没有下标Apr 24, 2022 am 11:49 AM

php字符串有下标。在PHP中,下标不仅可以应用于数组和对象,还可应用于字符串,利用字符串的下标和中括号“[]”可以访问指定索引位置的字符,并对该字符进行读写,语法“字符串名[下标值]”;字符串的下标值(索引值)只能是整数类型,起始值为0。

php怎么根据年月日判断是一年的第几天php怎么根据年月日判断是一年的第几天Apr 22, 2022 pm 05:02 PM

判断方法:1、使用“strtotime("年-月-日")”语句将给定的年月日转换为时间戳格式;2、用“date("z",时间戳)+1”语句计算指定时间戳是一年的第几天。date()返回的天数是从0开始计算的,因此真实天数需要在此基础上加1。

php怎么读取字符串后几个字符php怎么读取字符串后几个字符Apr 22, 2022 pm 08:31 PM

在php中,可以使用substr()函数来读取字符串后几个字符,只需要将该函数的第二个参数设置为负值,第三个参数省略即可;语法为“substr(字符串,-n)”,表示读取从字符串结尾处向前数第n个字符开始,直到字符串结尾的全部字符。

php怎么替换nbsp空格符php怎么替换nbsp空格符Apr 24, 2022 pm 02:55 PM

方法:1、用“str_replace(" ","其他字符",$str)”语句,可将nbsp符替换为其他字符;2、用“preg_replace("/(\s|\&nbsp\;||\xc2\xa0)/","其他字符",$str)”语句。

php怎么查找字符串是第几位php怎么查找字符串是第几位Apr 22, 2022 pm 06:48 PM

查找方法:1、用strpos(),语法“strpos("字符串值","查找子串")+1”;2、用stripos(),语法“strpos("字符串值","查找子串")+1”。因为字符串是从0开始计数的,因此两个函数获取的位置需要进行加1处理。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱工具

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用