Pelajar yang menggunakan PHP tahu bahawa konfigurasi php.ini akan berkuat kuasa sepanjang keseluruhan kitaran hayat SAPI. Semasa pelaksanaan skrip php, jika anda mengubah suai konfigurasi ini secara manual, ia tidak akan berkuat kuasa. Jika anda tidak boleh memulakan semula apache atau nginx pada masa ini, anda hanya boleh memanggil antara muka ini_set secara eksplisit dalam kod php. ini_set ialah fungsi yang disediakan oleh PHP untuk mengubah suai konfigurasi secara dinamik Perlu diingat bahawa konfigurasi yang ditetapkan oleh ini_set dan set konfigurasi dalam fail ini mempunyai julat masa berkesan yang berbeza. Selepas skrip php dilaksanakan, tetapan ini_set akan menjadi tidak sah serta-merta.
Oleh itu, artikel ini dibahagikan kepada dua bahagian Bahagian pertama menerangkan prinsip konfigurasi php.ini, dan bahagian kedua bercakap tentang mengubah suai konfigurasi php secara dinamik.
Konfigurasi php.ini kira-kira akan melibatkan tiga keping data, configuration_hash, EG (ini_directives) dan PG, BG, PCRE_G, JSON_G, XXX_G, dsb. Tidak mengapa jika anda tidak mengetahui maksud ketiga-tiga jenis data ini, ia akan diterangkan secara terperinci di bawah.
1, huraikan fail konfigurasi INI
Memandangkan php.ini perlu berkuat kuasa semasa proses SAPI, kerja menghuraikan fail ini dan membina konfigurasi php sewajarnya mestilah permulaan SAPI. Dalam erti kata lain, ia mesti berlaku semasa proses permulaan PHP. PHP memerlukan konfigurasi ini dijana secara dalaman sebelum sebarang permintaan sebenar tiba.
Dicerminkan ke dalam teras php, iaitu fungsi php_module_startup.
php_module_startup bertanggungjawab terutamanya untuk memulakan php. Ia biasanya dipanggil apabila SAPI bermula. btw, satu lagi fungsi biasa ialah php_request_startup, yang bertanggungjawab untuk memulakan setiap permintaan apabila ia tiba php_module_startup dan php_request_startup adalah dua tindakan ikonik, tetapi analisis mereka di luar skop artikel ini.
Sebagai contoh, apabila php disambungkan ke modul di bawah apache, maka apabila apache bermula, semua modul ini akan diaktifkan, termasuk modul php. Apabila mengaktifkan modul php, php_module_startup akan dipanggil. Fungsi php_module_startup menyelesaikan banyak kerja Setelah panggilan php_module_startup tamat, ini bermakna, OK, php telah dimulakan dan kini boleh menerima permintaan dan bertindak balas.
Dalam fungsi php_module_startup, pelaksanaan yang berkaitan dengan menghurai fail ini ialah:
Seperti yang anda lihat, fungsi php_init_config sebenarnya dipanggil untuk melengkapkan penghuraian fail ini. Kerja penghuraian terutamanya melaksanakan analisis lex&grammar, dan mengekstrak serta menyimpan pasangan kunci dan nilai dalam fail ini. Format php.ini sangat mudah, dengan kunci di sebelah kiri tanda sama dan nilai di sebelah kanan. Setiap kali sepasang kv diekstrak, di manakah php menyimpannya? Jawapannya ialah configuration_hash yang disebutkan tadi.
konfigurasi_hash Jadual Hash statik;
configuration_hash diisytiharkan dalam php_ini.c, iaitu struktur data jenis HashTable. Seperti namanya, ia sebenarnya adalah jadual hash. Sebagai tambahan, configuration_hash tidak boleh diperolehi dalam versi sebelum php5.3 kerana ia adalah pembolehubah statik dalam fail php_ini.c. Kemudian, php5.3 menambah antara muka php_ini_get_configuration_hash, yang mengembalikan &configuration_hash secara langsung, supaya pelbagai sambungan PHP dapat melihat dengan mudah konfigurasi_hash... Alangkah baiknya...
Perhatikan empat perkara:
Pertama, php_init_config tidak akan melakukan sebarang pengesahan selain daripada leksikal dan sintaks. Dalam erti kata lain, jika kita menambah baris hello=world pada fail ini, selagi ini adalah item konfigurasi yang diformat dengan betul, maka configuration_hash akhir akan mengandungi elemen dengan hello kunci dan dunia nilai, dan configuration_hash akan mencerminkan ia ke tahap maksimum ini.
Kedua, fail ini membolehkan kami mengkonfigurasi dalam bentuk tatasusunan. Sebagai contoh, tulis tiga baris berikut dalam fail ini:
那么最终生成的configuration_hash表中,就会存在一个key为drift.arr的元素,其value为一个包含的1,2,3三个数字的数组。这是一种极为罕见的配置方法。
第三,php还允许我们除了默认的php.ini文件(准确说是php-%s.ini)之外,另外构建一些ini文件。这些ini文件会被放入一个额外的目录。该目录由环境变量PHP_INI_SCAN_DIR来指定,当php_init_config解析完了php.ini之后,会再次扫描此目录,然后找出目录中所有.ini文件来分析。这些额外的ini文件中产生的kv键值对,也会被加入到configuration_hash中去。
这是一个偶尔有用的特性,假设我们自己开发php的扩展,却又不想将配置混入php.ini,便可以另外写一份ini,并通过PHP_INI_SCAN_DIR告诉php该去哪儿找到它。当然,其缺点也显而易见,其需要设置额外的环境变量来支持。更好的解决办法是,开发者在扩展中自己调用php_parse_user_ini_file或zend_parse_ini_file去解析对应的ini文件。
第四,在configuration_hash中,key是字符串,那么值的类型是什么?答案也是字符串(除了上述很特殊的数组)。具体来说,比如下面的配置:
那么最后configuration_hash中实际存放的键值对为:
key: "log_errors"
val : ""
key: "log_errors_max_len"
val : "1024"
注意log_errors,其存放的值连"0"都不是,就是一个实实在在地空字符串。另外,log_errors_max_len也并非数字,而是字符串1024。
分析至此,基本上解析ini文件相关的内容都说清楚了。简单总结一下:
1,解析ini发生在php_module_startup阶段
2,解析结果存放在configuration_hash里。
2,配置作用到模块
php的大致结构可以看成是最下层有一个zend引擎,它负责与OS进行交互、编译php代码、提供内存托管等等,在zend引擎的上层,排列着很多很多的模块。其中最核心的就一个Core模块,其他还有比如Standard,PCRE,Date,Session等等...这些模块还有另一个名字叫php扩展。我们可以简单理解为,每个模块都会提供一组功能接口给开发者来调用,举例来说,常用的诸如explode,trim,array等内置函数,便是由Standard模块提供的。
为什么需要谈到这些,是因为在php.ini里除了针对php自身,也就是针对Core模块的一些配置(例如safe_mode,display_errors,max_execution_time等),还有相当多的配置是针对其他不同模块的。
例如,date模块,它提供了常见的date, time,strtotime等函数。在php.ini中,它的相关配置形如:
除了这些模块拥有独立的配置,zend引擎也是可配的,只不过zend引擎的可配项非常少,只有error_reporting,zend.enable_gc和detect_unicode三项。
在上一小节中我们已经谈到,php_module_startup会调用php_init_config,其目的是解析ini文件并生成configuration_hash。那么接下来在php_module_startup中还会做什么事情呢?很显然,就是会将configuration_hash中的配置作用于Zend,Core,Standard,SPL等不同模块。当然这并非一个一蹴而就的过程,因为php通常会包含有很多模块,php启动的过程中这些模块也会依次进行启动。那么,对模块A进行配置的过程,便是发生在模块A的启动过程中。
有扩展开发经验的同学会直接指出,模块A的启动不就是在PHP_MINIT_FUNCTION(A)中么?
是的,如果模块A需要配置,那么在PHP_MINIT_FUNCTION中,可以调用REGISTER_INI_ENTRIES()来完成。REGISTER_INI_ENTRIES会根据当前模块所需要的配置项名称,去configuration_hash查找用户设置的配置值,并更新到模块自己的全局空间中。
2.1,模块的全局空间
要理解如何将ini配置从configuration_hash作用到各个模块之前,有必要先了解一下php模块的全局空间。对于不同的php模块,均可以开辟一块属于自己的存储空间,并且这块空间对于该模块来说,是全局可见的。一般而言,它会被用来存放该模块所需的ini配置。也就是说,configuration_hash中的配置项,最终会被存放到该全局空间中。在模块的执行过程中,只需要直接访问这块全局空间,就可以拿到用户针对该模块进行的设置。当然,它也经常被用来记录模块在执行过程中的中间数据。
我们以bcmath模块来举例说明,bcmath是一个提供数学计算方面接口的php模块,首先我们来看看它有哪些ini配置:
bcmath只有一个配置项,我们可以在php.ini中用bcmath.scale来配置bcmath模块。
接下来继续看看bcmatch模块的全局空间定义。在php_bcmath.h中有如下声明:
宏展开之后,即为:
其实,zend_bcmath_globals类型就是bcmath模块中的全局空间类型。这里仅仅声明了zend_bcmath_globals结构体,在bcmath.c中还有具体的实例化定义:
// 展开后即为zend_bcmath_globals bcmath_globals;
ZEND_DECLARE_MODULE_GLOBALS(bcmath)
可以看出,用ZEND_DECLARE_MODULE_GLOBALS完成了对变量bcmath_globals的定义。
bcmath_globals是一块真正的全局空间,它包含有四个字段。其最后一个字段bc_precision,对应于ini配置中的bcmath.scale。我们在php.ini中设置了bcmath.scale的值,随后在启动bcmath模块的时候,bcmath.scale的值被更新到bcmath_globals.bc_precision中去。
把configuration_hash中的值,更新到各个模块自己定义的xxx_globals变量中,就是所谓的将ini配置作用到模块。一旦模块启动完成,那么这些配置也都作用到位。所以在随后的执行阶段,php模块无需再次访问configuration_hash,模块仅需要访问自己的XXX_globals,就可以拿到用户设定的配置。
bcmath_globals,除了有一个字段为ini配置项,其他还有三个字段为何意?这就是模块全局空间的第二个作用,它除了用于ini配置,还可以存储模块执行过程中的一些数据。
再例如json模块,也是php中一个很常用的模块:
可以看到json模块并不需要ini配置,它的全局空间只有一个字段error_code。error_code记录了上一次执行json_decode或者json_encode中发生的错误。json_last_error函数便是返回这个error_code,来帮助用户定位错误原因。
为了能够很便捷的访问模块全局空间变量,php约定俗成的提出了一些宏。比如我们想访问json_globals中的error_code,当然可以直接写做json_globals.error_code(多线程环境下不行),不过更通用的写法是定义JSON_G宏:
我们使用JSON_G(error_code)来访问json_globals.error_code。本文刚开始的时候,曾提到PG、BG、JSON_G、PCRE_G,XXX_G等等,这些宏在php源代码中也是很常见的。现在我们可以很轻松的理解它们,PG宏可以访问Core模块的全局变量,BG访问Standard模块的全局变量,PCRE_G则访问PCRE模块的全局变量。
2.2,如何确定一个模块需要哪些配置呢?
模块需要什么样的INI配置,都是在各个模块中自己定义的。举例来说,对于Core模块,有如下的配置项定义:
可以在php-srcmainmain.c文件大概450+行找到上述代码。其中涉及的宏比较多,有ZEND_INI_BEGIN 、ZEND_INI_END、PHP_INI_ENTRY_EX、STD_PHP_INI_BOOLEAN等等,本文不一一赘述,感兴趣的读者可自行分析。
上述代码进行宏展开后得到:
我们看到,配置项的定义,其本质上就是定义了一个zend_ini_entry类型的数组。zend_ini_entry结构体的字段具体含义为:
char *value; // 配置项的值
uint value_length;
char *orig_value; // 配置项的原始值
uint orig_value_length;
int orig_modifiable; // 配置项的原始modifiable
int modified; // 是否发生过修改,如果有修改,则orig_value会保存修改前的值
void (*displayer)(zend_ini_entry *ini_entry, int type);
};
2.3,将配置作用到模块——REGISTER_INI_ENTRIES
经常能够在不同扩展的PHP_MINIT_FUNCTION里看到REGISTER_INI_ENTRIES。REGISTER_INI_ENTRIES主要负责完成两件事情,第一,对模块的全局空间XXX_G进行填充,同步configuration_hash中的值到XXX_G中去。其次,它还生成了EG(ini_directives)。
REGISTER_INI_ENTRIES也是一个宏,展开之后实则为zend_register_ini_entries方法。具体来看下zend_register_ini_entries的实现:
// 如果configuration_hash中没有找到,则采用默认值
if (!config_directive_success && hashed_ini_entry->on_modify) {
hashed_ini_entry->on_modify(hashed_ini_entry, hashed_ini_entry->value, hashed_ini_entry->value_length, hashed_ini_entry->mh_arg1, hashed_ini_entry->mh_arg2, hashed_ini_entry->mh_arg3, ZEND_INI_STAGE_STARTUP TSRMLS_CC);
}
p++;
}
return SUCCESS;
}
Ringkasnya, logik kod di atas boleh dinyatakan sebagai:
1. Tambahkan item konfigurasi ini yang diisytiharkan oleh modul kepada EG (ini_directives). Ambil perhatian bahawa nilai item konfigurasi ini boleh diubah suai kemudian.
2. Cuba cari ini yang diperlukan oleh setiap modul dalam konfigurasi_hash.
Jika ia boleh ditemui, ini bermakna nilai dikonfigurasikan dalam fail ini pengguna dan konfigurasi pengguna digunakan.
Jika ia tidak dijumpai, OK, tidak mengapa, kerana modul akan membawa nilai lalai apabila mengisytiharkan ini.
3. Segerakkan nilai ini kepada XX_G. Lagipun, semasa pelaksanaan php, XXX_globals ini masih memainkan peranan. Proses khusus adalah untuk memanggil kaedah on_modify yang sepadan dengan setiap konfigurasi ini on_modify ditentukan oleh modul semasa mengisytiharkan ini.
Mari kita lihat dengan lebih dekat on_modify, yang sebenarnya adalah penunjuk fungsi. Mari lihat penyataan konfigurasi dua modul Teras tertentu:
Untuk log_errors, on_modifynya ditetapkan kepada OnUpdateBool dan untuk log_errors_max_len, on_modifynya ditetapkan kepada OnUpdateLong.
Selanjutnya andaikan bahawa konfigurasi kami dalam php.ini ialah:
Mari kita lihat lebih dekat pada fungsi OnUpdateBool:
// p mewakili alamat core_globals ditambah dengan offset medan log_errors
//Alamat yang diperoleh ialah alamat medan log_errors
p = (zend_bool *) (asas+(saiz_t) mh_arg1);
*p = (zend_bool) 1;
}
else if (new_value_length == 3 && strcasecmp("yes", new_value) == 0) {
*p = (zend_bool) 1;
}
else if (new_value_length == 4 && strcasecmp("true", new_value) == 0) {
*p = (zend_bool) 1;
}
lain {
//Nilai yang disimpan dalam configuration_hash ialah rentetan "1", bukan "Hidup"
// Jadi di sini kita gunakan atoi untuk menukarnya kepada nombor 1
*p = (zend_bool) atoi(new_value);
}
Kembali BERJAYA;
}
// Dapatkan alamat log_errors_max_len
p = (panjang *) (asas+(saiz_t) mh_arg1);
// Tukar "1024" kepada jenis panjang dan tetapkan kepada core_globals.log_errors_max_len
*p = zend_atol(nilai_baru, panjang_nilai_baru);
Kembali BERJAYA;
}
Perkara terakhir yang perlu diambil perhatian ialah dalam fungsi zend_register_ini_entry, jika terdapat konfigurasi dalam configuration_hash, nilai dan panjang_nilai dalam hash_ini_entry akan dikemas kini apabila on_modify dipanggil. Dalam erti kata lain, jika pengguna telah mengkonfigurasinya dalam php.ini, EG (ini_directives) menyimpan nilai yang dikonfigurasikan sebenar. Jika pengguna tidak dikonfigurasikan, EG (ini_directives) menyimpan nilai lalai yang diberikan semasa mengisytiharkan zend_ini_entry.
Pembolehubah default_value dalam zend_register_ini_enries dinamakan dengan buruk dan boleh menyebabkan salah faham dengan mudah. Malah, default_value tidak mewakili nilai lalai, tetapi nilai sebenarnya dikonfigurasikan oleh pengguna.
3, Ringkasan
Pada ketika ini, tiga keping data configuration_hash, EG (ini_directives) dan PG, BG, PCRE_G, JSON_G, XXX_G... semuanya telah dijelaskan dengan jelas.
Untuk meringkaskan:
1, configuration_hash, menyimpan konfigurasi dalam fail php.ini, tidak melakukan pengesahan dan nilainya ialah rentetan.
2. EG (ini_directives) menyimpan zend_ini_entry yang ditakrifkan dalam setiap modul Jika pengguna telah mengkonfigurasinya dalam php.ini (sedia ada dalam configuration_hash), nilai digantikan dengan nilai dalam configuration_hash, dan jenisnya masih rentetan.
3. XXX_G, makro ini digunakan untuk mengakses ruang global modul ini boleh digunakan untuk menyimpan konfigurasi ini dan dikemas kini melalui fungsi yang ditentukan oleh on_modify. Jenis datanya ditentukan oleh pengisytiharan medan dalam XXX_G.