擷取PHP源碼 為了學習PHP的實現,首先需要下載PHP的原始碼。下載源碼首選是去 PHP官方網站http://php.net/downloads.php下載, 如果你喜歡使用svn/git等版本控制軟體,也可以使用svn/git來擷取最新的原始碼。
# git 官方地址git clone https://git.php.net/repository/php-src.git# 也可以訪問github官方鏡像git clone git://github.com/php/php-src.gitcd php-src && git checkout PHP-5.3 # 簽出5.3分支
PHP源碼目錄結構 俗話講:重劍無鋒,大巧不工。PHP的源碼在結構上非常清晰。下面先簡單介紹一下PHP源碼的目錄結構。 根目錄: / 這個目錄包含的東西比較多,主要包含一些說明檔案以及設計方案。 其實項目中的這些README檔案是非常值得閱讀的例如: /README.PHP4-TO-PHP5-THIN-CHANGES 這個檔案就詳細列舉了PHP4和PHP5的一些差異。 還有有一個比較重要的檔案/CODING_STANDARDS,如果要想寫PHP擴充的話,這個檔案一定要閱讀一下, 不管你個人的代碼風格是什麼樣,怎麼樣使用縮排和花括弧,既然來到了這樣一個團體裡就應該去適應這樣的規範,這樣在閱讀代碼或者別人閱讀你的 代碼是都會更輕鬆。 build 顧名思義,這裡主要放置一些和源碼編譯相關的一些檔案,比如開始構建之前的buildconf指令碼等檔案,還有一些檢查環境的指令碼等。 ext 官方擴充目錄,包括了絕大多數PHP的函數的定義和實現,如array系列,pdo系列,spl系列等函數的實現,都在這個目錄中。個人寫的擴充在測試時也可以放到這個目錄,方便測試和調試。 main 這裡存放的就是PHP最為核心的檔案了,主要實現PHP的基本設施,這裡和Zend引擎不一樣,Zend引擎主要實現語言最核心的語言運行環境。 Zend Zend引擎的實現目錄,比如指令碼的詞法文法解析,opcode的執行以及擴充機制的實現等等。 pear “PHP 擴充與應用倉庫”,包含PEAR的核心檔案。 sapi 包含了各種伺服器抽象層的代碼,例如apache的mod_php,cgi,fastcgi以及fpm等等介面。 TSRM PHP的安全執行緒是構建在TSRM庫之上的,PHP實現中常見的*G宏通常是對TSRM的封裝,TSRM(Thread Safe Resource Manager)安全執行緒資源管理員。 tests PHP的測試指令碼集合,包含PHP各項功能的測試檔案 win32 這個目錄主要包括Windows平台相關的一些實現,比如sokcet的實現在Windows下和*Nix平台就不太一樣,同時也包括了Windows下編譯PHP相關的指令碼。
PHP中的全域變數宏 在PHP的源碼中經常會看到的一些很常見的宏,或者有些對於才開始接觸源碼的讀者比較難懂的代碼。 這些代碼在PHP的源碼中出現的頻率極高,基本在每個模組都會有他們的身影。
在PHP代碼中經常能看到一些類似PG(), EG()之類的函數,他們都是PHP中定義的宏,這系列宏主要的作用是解決安全執行緒所寫的全域變數包裹宏, 如$PHP_SRC/main/php_globals.h檔案中就包含了很多這類的宏。例如PG這個PHP的核心全域變數的宏。 如下所示代碼為其定義。
#ifdef ZTS // 編譯時間開啟了安全執行緒則使用安全執行緒庫# define PG(v) TSRMG(core_globals_id, php_core_globals *, v)extern PHPAPI int core_globals_id;#else# define PG(v) (core_globals.v) // 否則這其實就是一個普通的全域變數extern ZEND_API struct _php_core_globals core_globals;#endif
如上,ZTS是安全執行緒的標記,這個在以後的章節會詳細介紹,這裡就不再說明。下面簡單說說,PHP運行時的一些全域參數, 這個全域變數為如下的一個結構體,各欄位的意義如欄位後的注釋:
struct _php_core_globals { zend_bool magic_quotes_gpc; // 是否對輸入的GET/POST/Cookie資料使用自動字串轉義。 zend_bool magic_quotes_runtime; //是否對運行時從外部資源產生的資料使用自動字串轉義 zend_bool magic_quotes_sybase; // 是否採用Sybase形式的自動字串轉義 zend_bool safe_mode; // 是否啟用安全模式 zend_bool allow_call_time_pass_reference; //是否強迫在函數調用時按引用傳遞參數 zend_bool implicit_flush; //是否要求PHP輸出層在每個輸出塊之後自動重新整理資料 long output_buffering; //輸出緩衝區大小(位元組) char *safe_mode_include_dir; //在安全模式下,該組目錄和其子目錄下的檔案被包含時,將跳過UID/GID檢查。 zend_bool safe_mode_gid; //在安全模式下,預設在訪問檔案時會做UID比較檢查 zend_bool sql_safe_mode; zend_bool enable_dl; //是否允許使用dl()函數。dl()函數僅在將PHP作為apache模組安裝時才有效。 char *output_handler; // 將所有指令碼的輸出重新導向到一個輸出處理函數。 char *unserialize_callback_func; // 如果解序列化處理器需要執行個體化一個未定義的類,這裡指定的回呼函數將以該未定義類的名字作為參數被unserialize()調用, long serialize_precision; //將浮點型和雙精確度型資料序列化儲存時的精度(有效位元)。 char *safe_mode_exec_dir; //在安全模式下,只有該目錄下的可執行程式才允許被執行系統程式的函數執行。 long memory_limit; //一個指令碼所能夠申請到的最大記憶體位元組數(可以使用K和M作為單位)。 long max_input_time; // 每個指令碼解析輸入資料(POST, GET, upload)的最大允許時間(秒)。 zend_bool track_errors; //是否在變數$php_errormsg中儲存最近一個錯誤或警告訊息。 zend_bool display_errors; //是否將錯誤資訊作為輸出的一部分顯示。 zend_bool display_startup_errors; //是否顯示PHP啟動時的錯誤。 zend_bool log_errors; // 是否在記錄檔裡記錄錯誤,具體在哪裡記錄取決於error_log指令 long log_errors_max_len; //設定錯誤記錄檔中附加的與錯誤資訊相關聯的錯誤源的最大長度。 zend_bool ignore_repeated_errors; // 記錄錯誤記錄檔時是否忽略重複的錯誤資訊。 zend_bool ignore_repeated_source; //是否在忽略重複的錯誤資訊時忽略重複的錯誤源。 zend_bool report_memleaks; //是否報告記憶體流失。 char *error_log; //將錯誤記錄檔記錄到哪個檔案中。 char *doc_root; //PHP的”根目錄”。 char *user_dir; //告訴php在使用 /~username 開啟指令碼時到哪個目錄下去找 char *include_path; //指定一組目錄用於require(), include(), fopen_with_path()函數尋找檔案。 char *open_basedir; // 將PHP允許操作的所有檔案(包括檔案自身)都限制在此組目錄列表下。 char *extension_dir; //存放擴充庫(模組)的目錄,也就是PHP用來尋找動態擴充模組的目錄。 char *upload_tmp_dir; // 檔案上傳時存放檔案的臨時目錄 long upload_max_filesize; // 允許上傳的檔案的最大尺寸。 char *error_append_string; // 用於錯誤資訊後輸出的字串 char *error_prepend_string; //用於錯誤資訊前輸出的字串 char *auto_prepend_file; //指定在主檔案之前自動解析的檔案名稱。 char *auto_append_file; //指定在主檔案之後自動解析的檔案名稱。 arg_separators arg_separator; //PHP所產生的URL中用來分隔參數的分隔字元。 char *variables_order; // PHP註冊 Environment, GET, POST, Cookie, Server 變數的順序。 HashTable rfc1867_protected_variables; // RFC1867保護的變數名,在main/rfc1867.c檔案中有用到此變數 short connection_status; // 串連狀態,有三個狀態,正常,中斷,逾時 short ignore_user_abort; // 是否即使在使用者中止請求後也堅持完成整個請求。 unsigned char header_is_being_sent; // 是否頭資訊正在發送 zend_llist tick_functions; // 僅在main目錄下的php_ticks.c檔案中有用到,此處定義的函數在register_tick_function等函數中有用到。 zval *http_globals[6]; // 存放GET、POST、SERVER等資訊 zend_bool expose_php; // 是否展示php的資訊 zend_bool register_globals; // 是否將 E, G, P, C, S 變數註冊為全域變數。 zend_bool register_long_arrays; // 是否啟用舊式的長式數組(HTTP_*_VARS)。 zend_bool register_argc_argv; // 是否聲明$argv和$argc全域變數(包含用GET方法的資訊)。 zend_bool auto_globals_jit; // 是否僅在使用到$_SERVER和$_ENV變數時才建立(而不是在指令碼一啟動時就自動建立)。 zend_bool y2k_compliance; //是否強制開啟2000年適應(可能在非Y2K適應的瀏覽器中導致問題)。 char *docref_root; // 如果開啟了html_errors指令,PHP將會在出錯資訊上顯示超串連, char *docref_ext; //指定檔案的副檔名(必須含有’.')。 zend_bool html_errors; //是否在出錯資訊中使用HTML標記。 zend_bool xmlrpc_errors; long xmlrpc_error_number; zend_bool activated_auto_globals[8]; zend_bool modules_activated; // 是否已經啟用模組 zend_bool file_uploads; //是否允許HTTP檔案上傳。 zend_bool during_request_startup; //是否在請求初始化過程中 zend_bool allow_url_fopen; //是否允許開啟遠程檔案 zend_bool always_populate_raw_post_data; //是否總是產生$HTTP_RAW_POST_DATA變數(原始POST資料)。 zend_bool report_zend_debug; // 是否開啟zend debug,僅在main/main.c檔案中有使用。 int last_error_type; // 最後的錯誤類型 char *last_error_message; // 最後的錯誤資訊 char *last_error_file; // 最後的錯誤檔案 int last_error_lineno; // 最後的錯誤行 char *disable_functions; //該指令接受一個用逗號分隔的函數名列表,以禁用特定的函數。 char *disable_classes; //該指令接受一個用逗號分隔的類名列表,以禁用特定的類。 zend_bool allow_url_include; //是否允許include/require遠程檔案。 zend_bool exit_on_timeout; // 逾時則退出#ifdef PHP_WIN32 zend_bool com_initialized;#endif long max_input_nesting_level; //最大的嵌套層數 zend_bool in_user_include; //是否在使用者包含空間 char *user_ini_filename; // 使用者的ini檔案名稱 long user_ini_cache_ttl; // ini緩衝到期限制 char *request_order; // 優先順序比variables_order高,在request變數產生時用到,個人覺得是曆史遺留問題 zend_bool mail_x_header; // 僅在ext/standard/mail.c檔案中使用, char *mail_log; zend_bool in_error_log;}; 上面的欄位很大一部分是與php.ini檔案中的配置項對應的。 在PHP啟動並讀取php.ini檔案時就會對這些欄位進行賦值, 而使用者空間的ini_get()及ini_set()函數操作的一些配置也是對這個全域變數進行操作的。 在PHP代碼的其他地方也存在很多類似的宏,這些宏和PG宏一樣,都是為了將安全執行緒進行封裝,同時通過約定的 G 命名來表明這是全域的, 一般都是個縮寫,因為這些全域變數在代碼的各處都會使用到,這也算是減少了鍵盤輸入。 我們都應該 儘可能的懶不是麼。 如果你閱讀過一些PHP擴充話應該也見過類似的宏,這也算是一種代碼規範,在編寫擴充時全域變數最好也使用這種方式命名和包裹, 因為我們不能對使用者的PHP編譯條件做任何假設。