配置文件的加载

通过前几章的学习,我们知道 CLI 是 PHP SAPI 的一种,也会经历 SAPI startup 的阶段,配置文件的解析也是在这个过程中进行,其调用栈如下:

(gdb) bt
#0  php_init_config () at /home/vagrant/php-7.1.4/main/php_ini.c:714
#1  php_module_startup at /home/vagrant/php-7.1.4/main/main.c:2213
#2  php_cli_startup at /home/vagrant/php-7.1.4/sapi/cli/php_cli.c:427
#3  main at /home/vagrant/php-7.1.4/sapi/cli/php_cli.c:1348

在 PHP module 的启动过程中调用 php_init_config 函数来完成配置文件的解析。

/* this will read in php.ini, set up the configuration parameters,
    load zend extensions and register php function extensions
    to be loaded later */
if (php_init_config() == FAILURE) {
    return FAILURE;
}

PHP 配置文件的解析主要分为文件解析和字符串解析,其主要过程都在这个函数中:

  1. 初始化默认配置,如果当前 SAPI 存在默认配置的初始化函数,则调用该函数来初始化默认配置。例如,CLI 模式的默认配置函数为 sapi_cli_ini_defaults。

  2. 初始化 extension_lists 来保存配置文件中的扩展。

  3. 解析主配置文件。

  4. 解析其他配置文件。

  5. 解析配置字符串。

PHP 有 3 个命令行参数来控制配置项,分别如下。

  • ① -c:指定配置文件,例如 php -c /etc/php7.ini test.php。

  • ② -d:指定配置参数,例如 php -d "memory_limit=256m" -d "post_max_size=30m" test.php。

  • ③ -n:忽略配置文件,例如 php -n test.php。

下面介绍配置文件的加载方式和优先级。

主配置文件

配置解析的第一步是搜索指定路径或者默认路径下的配置文件,配置文件搜索的优先级从高到低如下。

  1. 使用 -c 指定的配置文件,使用该方式指定后不再搜索后边的目录,配置文件名可以自定义。

  2. 使用 PHPRC 环境变量指定的配置文件,配置文件名可以自定义,-n 参数下无效。

  3. 在当前目录下搜索,-n 参数下无效。

  4. 在 PHP 安装目录 bin 和 etc 下搜索,-n 参数下无效。

  5. 第 2、3、4 三种方式指定的目录下,如果 php-${sapi}.ini 存在,使用这个配置文件,如 php-cli.ini,否则使用 php.ini。

其他配置文件

如果有很多配置项但是又不想修改默认的配置文件该如何处理呢?PHP 提供了以下两种方式来扩展配置,通过这两种方式指定的目录下的任何以 .ini 为扩展名的文件(包括隐藏文件和链接文件)都会按照文件名称的字母顺序被加载解析。其配置项会覆盖主配置文件(注意:这两种方式互斥,环境变量的方式优先,-n 参数下无效)。

  1. 在安装 PHP 时由 --with-config-file-scan-dir 参数指定扩展配置文件的目录。

  2. 如果在安装时没有添加 --with-config-file-scan-dir 参数并且环境变量中存在 PHP_INI_SCAN_DIR,则在该路径下寻找。

配置文件在加载完成后开始解析,解析函数如下:

/* {{{ zend_parse_ini_file()
*/ZEND_API  int  zend_parse_ini_file(zend_file_handle  *fh,  zend_bool  unbuffered_
    errors, int scanner_mode, zend_ini_parser_cb_t ini_parser_cb, void *arg){
    // code
}

如果同时存在主配置文件和其他配置文件,PHP 会多次调用这个函数来完成配置文件的解析。其解析过程主要是词法和语法的解析,PHP 将解析后的配置以 key-value 的形式保存到名为 configuration_hash 的全局 hashtable 中。值得注意的是,我们在 php.ini 的配置文件中以符合配置文件语法写的任何配置,都会被解析到 configuration_hash。例如,笔者在自己的配置文件中添加了 test = 1 的配置项,通过 gdb 可以看见 configuration_hash 中已经存在这个配置项了:

(gdb) p *configuration_hash.arData[3].key.val@4
$87 = "test"
(gdb) p configuration_hash.arData[3].val.value.str.val
$95 = "1"

非 PHP 的配置项只会保存在 configuration_hash,通过 ini_get 函数无法获取到。不过,PHP 提供了 get_cfg_var 函数来获取 PHP 配置文件中的自定义配置。

当配置文件解析完成后,还要进行配置字符串的解析。部分 SAPI 有默认的硬编码配置,例如,在 CLI 模式下有如下默认配置:

const char HARDCODED_INI[] =
    "html_errors=0\n"
    "register_argc_argv=1\n"
    "implicit_flush=1\n"
    "output_buffering=0\n"
    "max_execution_time=0\n"
    "max_input_time=-1\n\0";

除此之外,-d 参数可以不通过配置文件来指定一些独立的配置项,例如:

$ php -d "post_max_size=20M" test.php

这两种方式的配置会在合并后以字符串的方式解析,解析函数如下:

/* {{{ zend_parse_ini_string()
*/
ZEND_API  int  zend_parse_ini_string(char  *str,  zend_bool  unbuffered_errors,  int
    scanner_mode, zend_ini_parser_cb_t ini_parser_cb, void *arg)
{
    // code
}

在解析配置的过程中,SAPI 硬编码配置优先级最高,会强制覆盖其他配置;其次是 -d 指定的配置;最后是配置文件指定的配置。

完整的解析顺序如图8-1所示。

image 2024 06 10 11 56 18 781
Figure 1. 图8-1 完整的解析顺序图