我們在寫web應用程式時通常對每個類都建立一個 PHP 源檔案。為了使用這些源檔案,我們就需要在每個指令碼開頭寫大量的的包含語句(include,require)。在 PHP 5 中,不再需要這樣了。我們可__autoload()函數和spl_autoload_register函數實現實現自己的載入源檔案的機制,它們會在試圖使用尚未被定義的類時自動調用。通過調用這些函數,指令碼引擎在 PHP 出錯失敗前有了最後一個機會載入所需的類。本文的主要目標是講述如何在擴充中用C語言實現自動載入源檔案的機制,但是在這之前我們先熟悉一下在PHP指令碼中實現自動載入的方法。
在指令碼中實現自動載入
在 PHP 5 中我們可以定義一個 __autoload() 函數,它會在試圖使用尚未被定義的類時自動調用,這樣我們就可以定義一些自己的載入規則了。
<?php
function __autoload($class_name) {
require_once $class_name . '.php';
}
$obj = new MyClass1();
$obj2 = new MyClass2();
?>
使用spl_autoload_register我們可以一次註冊多個載入函數,PHP會在試圖使用尚未被定義的類時按註冊順序調用。
<?php
function autoload_services($class_name)
{
$file = 'services/' . $class_name. '.php';
if (file_exists($file))
{
require_once($file);
}
}
function autoload_vos($class_name)
{
$file = 'vos/' . $class_name. '.php';
if (file_exists($file))
{
require_once($file);
}
}
spl_autoload_register('autoload_services');
spl_autoload_register('autoload_vos');
?>
在php擴充中實現自動載入
最近在寫一個php擴充,其中一個功能就是實作類別的自動載入,其實也是通過在核心中調用spl_autoload_register函數來實現。使用zend API調用spl_autoload_register函數還是相對簡單的,下面我們主要講一下如何在核心中實現inclue/require/include_once/require_once等指令的功能。其實inclue/require/include_once/require_once等指令主要是讀入檔案編譯並執行,下面的方法就是完成了這些操作,代碼中有詳細的注釋。
/*
* loader_import首先將PHP源檔案編譯成op_array,然後依次執行op_array中的opcode
*/
int loader_import(char *path, int len TSRMLS_DC) {
zend_file_handle file_handle;
zend_op_array *op_array;
char realpath[MAXPATHLEN];
if (!VCWD_REALPATH(path, realpath)) {
return 0;
}
file_handle.filename = path;
file_handle.free_filename = 0;
file_handle.type = ZEND_HANDLE_FILENAME;
file_handle.opened_path = NULL;
file_handle.handle.fp = NULL;
//調用zend API編譯源檔案
op_array = zend_compile_file(&file_handle, ZEND_INCLUDE TSRMLS_CC);
if (op_array && file_handle.handle.stream.handle) {
int dummy = 1;
if (!file_handle.opened_path) {
file_handle.opened_path = path;
}
//將源檔案註冊到執行期間的全域變數(EG)的include_files列表中,這樣就標記了源檔案已經包含過了
zend_hash_add(&EG(included_files), file_handle.opened_path, strlen(file_handle.opened_path)+1, (void *)&dummy,
sizeof(int), NULL);
}
zend_destroy_file_handle(&file_handle TSRMLS_CC);
//開始執行op_array
if (op_array) {
zval *result = NULL;
//儲存原來的執行環境,包括active_op_array,opline_ptr等
zval ** __old_return_value_pp = EG(return_value_ptr_ptr);
zend_op ** __old_opline_ptr = EG(opline_ptr);
zend_op_array * __old_op_array = EG(active_op_array);
//儲存環境完成後,初始化本次執行環境,替換op_array
EG(return_value_ptr_ptr) = &result;
EG(active_op_array) = op_array;
#if ((PHP_MAJOR_VERSION == 5) && (PHP_MINOR_VERSION > 2)) || (PHP_MAJOR_VERSION > 5)
if (!EG(active_symbol_table)) {
zend_rebuild_symbol_table(TSRMLS_C);
}
#endif
//調用zend API執行源檔案的op_array
zend_execute(op_array TSRMLS_CC);
//op_array執行完成後銷毀,要不然就要記憶體泄露了,哈哈
destroy_op_array(op_array TSRMLS_CC);
efree(op_array);
//通過檢查執行期間的全域變數(EG)的exception是否被標記來確定是否有異常
if (!EG(exception)) {
if (EG(return_value_ptr_ptr) && *EG(return_value_ptr_ptr)) {
zval_ptr_dtor(EG(return_value_ptr_ptr));
}
}
//ok,執行到這裡說明源檔案的op_array已經執行完成了,我們要恢複原來的執行環境了
EG(return_value_ptr_ptr) = __old_return_value_pp;
EG(opline_ptr) = __old_opline_ptr;
EG(active_op_array) = __old_op_array;
return 1;
}
return 0;
}