譯者注: 文中的操做都是基於 PHP5.6 進行的修改,翻譯這篇文章的時候 PHP7 都已經出了,有不少方法已經被遺棄,但願各位注意不要踩坑。php
原文連接html
最近有好多人問我怎麼給 PHP 添加新語法特性。我仔細想了想,確實沒有這方面的教程,接下來我會闡述整個流程。同時這篇文章也是對 Zend 引擎的一個簡介。node
我提早爲這篇過長的文章道歉。git
這篇文章假設你已經掌握了一些 C 的基本知識,而且瞭解 PHP 的一些基本概念(像 zvals 結構體)。若是你不具有這些條件,建議先去了解一下。github
我將使用你可能從其餘語言獲知的 in
運算符做爲一個例子。它表現以下:api
$words = ['hello', 'world', 'foo', 'bar'];
var_dump('hello' in $words); // true
var_dump('foo' in $words); // true
var_dump('blub' in $words); // false
$string = 'PHP is fun!';
var_dump('PHP' in $string); // true
var_dump('Python' in $string); // false
複製代碼
基本上來講,in
操做符和 in_array
函數在數組中的使用同樣(可是沒有 needle/haystack 問題),和字符函數 false != strpos($str2, $str1)
也相似。數組
在開始以前,你必須檢出並編譯 PHP。因此接下來咱們須要安裝一些工具。大部分可能都預先在系統上安裝好了,可是你必須使用本身選擇的包管理工具安裝 "re2c" 和 「bison」。若是你用的是 Ubuntu:緩存
$ sudo apt-get install re2c
$ sudo apt-get install bison
複製代碼
接下來,從 git 上克隆 php-src 並進行編譯:安全
// 獲取源碼
$ git clone http://git.php.net/repository/php-src.git
$ cd php-src
// 建立新分支
$ git checkout -b addInOperator
// 構建 ./configure (預編譯)腳本
$ ./buildconf
// 使用 debug 模式和 線程安全模式 預編譯
$ ./configure --disable-all --enable-debug --enable-maintainer-zts
// 編譯 (4 是你擁有的核心數)
$ make -j4
複製代碼
PHP 二進制包應該在 sapi/cli/php
。你能夠嘗試如下操做:bash
$ sapi/cli/php -v
$ sapi/cli/php -r 'echo "Hallo World!";'
複製代碼
如今你可能已經有了一個編譯過的 PHP,接下來咱們看下 PHP 在運行一個腳本的時候都作了哪些事。
運行一個 PHP 腳本有三個主要階段:
接下來我會詳細解釋每一個階段都在作什麼,如何實現以及咱們須要修改什麼地方纔能讓 in
操做符運行。
第一階段 PHP 讀取源代碼,把源碼切分紅更小的 「token」 單元。舉個例子 <?php echo "Hello World!";
會被拆解成下面的 token:
T_OPEN_TAG (<?php )
T_ECHO (echo)
T_WHITESPACE ( )
T_CONSTANT_ENCAPSED_STRING ("Hello World!")
';'
複製代碼
(譯者注: 這裏是官方的 token表)
如你所見原始代碼被切分紅具備語義的 token。處理過程被稱爲符號化,掃描和詞法解析的實如今 Zend
目錄下的 zend_language_scanner.l
文件。
若是你打開文件向下滾動到差很少 1000 行(譯者注: php 8.0.0 在 1261 行),你會發現大量的 token 定義語句像下面這樣:
<ST_IN_SCRIPTING>"exit" {
return T_EXIT;
}
複製代碼
上述代碼的意思很明顯是: 若是在源代碼中遇到了 exit
,lexer 應該標記它爲 T_EXIT
。<
和 >
中間的內容是文本應該被匹配的狀態。
ST_IN_SCRIPTING
是對 PHP 源碼來講是正常狀態。還有一些其餘的狀態像 ST_DOUBLE_QUOTE
(在雙引號中間),ST_HEREDOC
(在 heredoc 字符串中間),等等。
另外一個能夠在掃描期間作的是指定一個「語義」值(也能夠稱爲"lower case" 或者簡稱"lval")。下面是例子:
<ST_IN_SCRIPTING,ST_VAR_OFFSET>{LABEL} {
zend_copy_value(zendlval, yytext, yyleng);
zendlval->type = IS_STRING;
複製代碼
{LABEL}
匹配一個 PHP 標識(能夠被定義爲[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*
),代碼返回 token T_STRING
。另外它複製 token 的文本到 zendlval
。因此若是 lexer 遇到一個標識像 FooBarClass
,它將設置 FooBarClass
做爲lval。字符串,數字和變量名稱也同樣。
幸運的是 in
操做符並不須要深層次的 lexer 知識。咱們只須要添加如下代碼段到文件中(與上面的 exit
相似):
<ST_IN_SCRIPTING>"in" {
return T_IN;
}
複製代碼
(譯者注: 新版已經不是上面的寫法了)
除此以外咱們須要讓引擎知道咱們添加了一個新的 token。打開 zend_language_parser.y
加入下面的行在它的相似代碼中(在定義操做符的代碼段中):
%token T_IN "in (T_IN)"
複製代碼
如今你應該用 make -j4
從新編譯下 PHP (必須在頂級目錄 php-src
中執行,不是 Zend/
)。這會產生一個新由 re2c 生成的 lexer 並編譯它。爲了測試咱們的修改是否生效。須要執行如下命令:
$ sapi/cli/php -r 'in'
複製代碼
這將會給出一個解析錯誤:
Parse error: syntax error, unexpected 'in' (T_IN) in Command line code on line 1
複製代碼
咱們須要作的最後一件事就是使用 Tokenizer 擴展 從新生成數據,你須要使用 cd
進入 ext/tokenizer
目錄而且執行 ./tokenizer_data_gen.sh
。
若是你運行 git diff --stat,你會看見下面的信息:
Zend/zend_language_parser.y | 1 +
Zend/zend_language_scanner.c | 1765 +++++++++++++++++++------------------
Zend/zend_language_scanner.l | 4 +
Zend/zend_language_scanner_defs.h | 2 +-
ext/tokenizer/tokenizer_data.c | 4 +-
5 files changed, 904 insertions(+), 872 deletions(-)
複製代碼
zend_language_scanner.c
內容的變動是 re2C 從新生成的 lexer。由於它包含了行號信息,每一個對 lexer 的改變都會產生巨大的不一樣。因此不用擔憂;)
目前爲止源碼已經被分解成有含義的 token,PHP已經能夠識別更大的結構像"this is an if
block"或者"you are defining function here"。這個過程被稱爲解析,規則被定義在 zend_language_parser.y
文件中。這只是一個定義文件,真正的解析器仍是由 bison 生成的。
爲了瞭解解析器的定義是如何運行的,咱們來看個例子:
class_statement:
variable_modifiers { CG(access_type) = Z_LVAL($1.u.constant); } class_variable_declaration ';'
| class_constant_declaration ';'
| trait_use_statement
| method_modifiers function is_reference T_STRING { zend_do_begin_function_declaration(&$2, &$4, 1, $3.op_type, &$1 TSRMLS_CC); } '('
parameter_list ')' method_body { zend_do_abstract_method(&$4, &$1, &$9 TSRMLS_CC); zend_do_end_function_declaration(&$2 TSRMLS_CC); }
;
複製代碼
咱們把花括號中的內容去掉,剩下的內容以下:
class_statement:
variable_modifiers class_variable_declaration ';'
| class_constant_declaration ';'
| trait_use_statement
| method_modifiers function is_reference T_STRING '(' parameter_list ')' method_body
;
複製代碼
你能夠這樣解讀:
A class statement is
a variable declaration (with access modifier)
or a class constant declaration
or a trait use statement
or a method (with method modifier, optional return-by-ref, method name, parameter list and method body)
複製代碼
想知道什麼是「methid modifer」,你須要去看 method_modifier
的定義。這就至關直白了。
爲了讓解析器支持 in
,咱們須要把 expr T_IN expr
規則加到 expr_without_variable
裏面:
expr_without_variable:
...
| expr T_IN expr
...
;
複製代碼
若是你運行 make -j4
,bison 會嘗試從新構建解析器,可是會報如下的錯誤:
conflicts: 87 shift/reduce
/some/path/php-src/Zend/zend_language_parser.y: expected 3 shift/reduce conflicts
make: *** [/some/path/php-src/Zend/zend_language_parser.c] Error 1
複製代碼
shift/reduce 意思是解析器在某些狀況下不知道怎麼去作。PHP 語法有 3 個 shift/reduce 自相矛盾的衝突(意料之中,由於相似 elseif/else 的歧義)。其他的 84 個衝突是由於新規則形成的。
緣由是咱們沒有規定 in
如何在其餘運算符之間運行。舉個例子:
// if you write
$foo in $bar && $someOtherCond
// should PHP interpret this as
($foo in $bar) && $someOtherCond
// or as
$foo in ($bar && $someOtherCond)
複製代碼
上述被成爲」運算符的優先級「。還有一個相關的概念是」運算符的關聯性「,它決定了你寫$foo in $bar in $baz
時會發生什麼。
爲了解決 shift/reduce 的衝突,你須要在解析器的開始處找到下面的行並把 T_IN
追加在這行後面
%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
複製代碼
這意味着 in
和 <
比較運算符有相同的優先級,並且沒有關聯性。下面是 in
如何運行的一些示例:
$foo in $bar && $someOtherCond
// 被解釋爲
($foo in $bar) && $someOtherCond
// because `&&` has lower precedence than `in`
$foo in ['abc', 'def'] + ['ghi', 'jkl']
// 被解釋爲
$foo in (['abc', 'def'] + ['ghi', 'jkl'])
// 由於 `+` 的優先級比 `in` 高
$foo in $bar in $baz
// 會拋出解析異常,由於 `in` 是無關聯性的
複製代碼
若是運行 make -j4
,會發現報錯沒了。而後你能夠嘗試運行 sapi/cli/php -r '"foo" in "bar";'
。這什麼也不會作,除了打印除一個內存泄漏信息:
[Thu Jul 26 22:33:14 2012] Script: '-'
Zend/zend_language_scanner.l(876) : Freeing 0xB777E7AC (4 bytes), script=-
=== Total 1 memory leaks detected ===
複製代碼
預料之中,由於到目前爲止咱們尚未告訴解析器匹配到 in
的時候該怎麼作。這就是花括號裏的內容的做用(譯者注: 還記得上面講解析器定義的時候簡化的花括號嗎),接下來咱們用下面的內容替換掉 expr T_IN expr
:
expr T_IN expr { zend_do_binary_op(ZEND_IN, &$$, &$1, &$3 TSRMLS_CC); }
複製代碼
花括號裏的內容被成爲語義動做,在解析器匹配到固定規則的時候運行。$$
,$1
和 $3
這些看起來奇奇怪怪的東西是節點。$1
關聯第一個 expr
,$3
關聯第二個 expr
($3
是規則裏的第三個元素),$$
是存儲結果的節點。
zend_do_binary_op
是一個編譯器指令。它告訴編譯器發行 ZEND_IN
操做指令,指令將會把 $1
和 $3
做爲操做數,將計算結果存入 $$
中。
編譯指令在 zend_compole.c
中定義(裏面帶有 zend_compile.h 頭文件)。 zend_do_binary_op
定義以下:
void zend_do_binary_op(zend_uchar op, znode *result, const znode *op1, const znode *op2 TSRMLS_DC)
{
zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);
opline->opcode = op;
opline->result_type = IS_TMP_VAR;
opline->result.var = get_temporary_variable(CG(active_op_array));
SET_NODE(opline->op1, op1);
SET_NODE(opline->op2, op2);
GET_NODE(result, opline->result);
}
複製代碼
代碼應該比較好理解,下節咱們會把它放到一個有上下文的環境中。最後提醒一件事,在大多數狀況下當你想要添加本身的語法的時候,你必須添加本身的 _do_*
方法。添加一個二進制操做符是爲數很少的狀況中的一個。若是你必需要加一個新的 _do_*
函數,先看看現存的函數能不能知足你的需求。它們中的大部分都挺簡單的。
在上節我提到了編譯器在發行操做碼。接下來咱們近距離看下這些操做碼(看 zend_compile.h):
struct _zend_op {
opcode_handler_t handler;
znode_op op1;
znode_op op2;
znode_op result;
ulong extended_value;
uint lineno;
zend_uchar opcode;
zend_uchar op1_type;
zend_uchar op2_type;
zend_uchar result_type;
};
複製代碼
對上述結構一個簡短的介紹:
opcode
: 這是一個真正被執行的操做。能夠用 ZEND_ADD
或者 ZEND_SUB
當例子。
op1
,op2
, result
: 每一個操做最多能夠擁有兩個操做數(它能夠只選擇其中用一個或者一會也不用)和一個結果節點。op1_type
,op2_type
和 result_type
決定了節點的類型。稍後咱們會去了解節點和節點的類型。
extended_value
: 擴展值用來存儲標記和一些別的整型值。好比說變量獲取指定用它存儲變量的類型(像 ZEND_FETCH_LOCAL
或者 ZEND_FETCH_GLOBAL
)
handler
: 用來優化操做碼的執行,它存儲處理函數與操做碼和操做數類型相關。 這是自動肯定的,所以沒必要在編譯代碼中設置。
lineno
: 這就很少說了..
這裏有五種基本的類型能夠詳細解釋 *_type
屬性:
IS_TMP_VAR
: 臨時變量,一般用在一些表達式的結果像 $foor + $bar
上。臨時變量不能共享,因此不能使用引用計數。它們的生命週期很短,因此在使用完成後立刻被銷燬。臨時變量一般被寫成 ~n
,~0
表示第一個臨時變量,~1
表示第二個,以此類推。
IS_CV
: 編譯變量。用來存儲哈希表查詢結果,PHP 緩存簡單變量的位置像 $foo
在數組中的地址(C 數組)。此外,編譯變量容許 PHP 徹底優化哈希表。編譯變量使用 !n
表示(n
表示編譯變量數組的偏移量)
IS_VAR
: 只是一些簡單的變量能夠被轉換爲編譯變量。 全部其餘類型的變量訪問,如 $foo['bar']
或 $foo->bar
返回一個 IS_VAR
變量。它基本上就是一個正常的 zval (有引用計數和其餘的全部屬性)。Vars 這樣 $n
表示。
IS_CONST
: 常量在代碼中的表示比較隨意。舉個例子,"foo"
或者 3.141
都是 IS_CONST
類型。常量容許更近一步的優化,像複用 zvals,預先計算哈希值。
id_UNUSED
: 操做數沒有被使用。
與此相關的 znode_op
的結構:
typedef union _znode_op {
zend_uint constant;
zend_uint var;
zend_uint num;
zend_ulong hash;
zend_uint opline_num;
zend_op *jmp_addr;
zval *zv;
zend_literal *literal;
void *ptr;
} znode_op;
複製代碼
咱們能夠看到節點就是一個聯合體。它能夠包含上述元素中的一個(只有一個),具體哪一個取決於上下文。好比 zv
用來存儲 IS_CONST
zvals,var
用來存儲 IS_CV
,IS_VAR
和 IS_TMP_VAR
變量。剩下的使用在不一樣的特殊環境下。例如 jmp_addr
和 JMP*
指令結合使用(在循環和條件判斷中使用)。其他都只在編譯期間使用,不是在執行期間(像 constant
)。
如今咱們瞭解了單個操做碼的結構,惟一剩下的問題就是這些操做碼存在什麼地方: PHP 爲每一個函數(和文件)建立一個 zend_op_array
,裏面存儲了操做碼和不少其餘的信息。我不想深刻去講每一個部分都是幹什麼的,你只須要了解這個結構體存在就好了。
接下來咱們回到 in
操做符的實現!咱們已經指示編譯器去發行一個 ZEND_IN
操做碼。如今咱們須要定義這個操做碼能夠幹什麼。
這部分在 zend_vm_def.h
中實現。若是你看過這個文件,你會發現裏面全是下面這樣的定義:
ZEND_VM_HANDLER(1, ZEND_ADD, CONST|TMP|VAR|CV, CONST|TMP|VAR|CV)
{
USE_OPLINE
zend_free_op free_op1, free_op2;
SAVE_OPLINE();
fast_add_function(&EX_T(opline->result.var).tmp_var,
GET_OP1_ZVAL_PTR(BP_VAR_R),
GET_OP2_ZVAL_PTR(BP_VAR_R) TSRMLS_CC);
FREE_OP1();
FREE_OP2();
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
複製代碼
ZEND_IN
操做碼的定義和這個基本同樣,因此咱們來了解下這個定在在幹什麼。我會逐行解釋:
// 頭部定義個四個事情:
// 1. 這是一個 ID 爲 1 的操做碼
// 2. 這個操做碼叫 ZEND_ADD
// 3. 這個操做碼接受 CONST, TMP, VAR 和 CV 做爲第一個操做數
// 4. 這個操做碼接受 CONST, TMP, VAR 和 CV 做爲第二個操做數
ZEND_VM_HANDLER(1, ZEND_ADD, CONST|TMP|VAR|CV, CONST|TMP|VAR|CV)
{
// USE_OPLINE 意味着咱們想像 `opline` 同樣操做 zend_op.
// 這個對全部存取操做數或者設置返回值的操做碼都是必須的
USE_OPLINE
// For every operand that is accessed a free_op* variable has to be defined.
// 這個用來判斷操做數是否須要釋放.
zend_free_op free_op1, free_op2;
// SAVE_OPLINE() 加載 zend_op 到 `opline`。
// USE_OPLINE 只是聲明。
SAVE_OPLINE();
// 調用 fast add 函數
fast_add_function(
// 告訴函數把結果放在 tmp_var 裏
// EX_T 使用 ID opline->result.var 來操做臨時變量
&EX_T(opline->result.var).tmp_var,
// 以讀取模式獲取第一個操做數 ( R 在 BP_VAR_R 的含義是讀取,read 的縮寫)
GET_OP1_ZVAL_PTR(BP_VAR_R),
// 以讀取模式獲取第二個操做數
GET_OP2_ZVAL_PTR(BP_VAR_R) TSRMLS_CC);
// 釋放兩個操做數 (必須的狀況下)
FREE_OP1();
FREE_OP2();
// 檢查異常。異常可能發生在任何地方,因此必須在全部操做碼中檢查異常。
// 若是有疑問,加上異常檢測。
CHECK_EXCEPTION();
// 處理下一個操做碼
ZEND_VM_NEXT_OPCODE();
}
複製代碼
你可能會注意到這個文件裏的東西大部分都是 大寫
的。由於 zend_vm_def.h
只是一個定義文件。真正的 ZEND VM 根據它生成,最終存儲在 zend_vm_execute.h
(巨...大的一個文件)。PHP 有三個不一樣的虛擬機類型,CALL
(默認) GOTO
SWITCH
。由於他們有不一樣的實現細節,定義文件使用了大量的僞宏(像 USE_OPLINE
),它們最終會被具體實現替代掉。
此外,生成的 VM 爲全部可能的操做數類型的組合建立專門的實現。因此最後不會只有一個 ZEND_ADD 函數,會有不一樣的函數實現,像 ZEND_ADD_CONST_CONST
,ZEND_ADD_CONST_TMP
,ZEND_ADD_CONST_VAR
…
如今爲了實現 ZEND_IN
操做碼,你應該在 zend_vm_def.h
文件結尾處新增一個操做碼定義框架:
// 159 是我這裏下個沒有被使用的操做碼編號。 或許你須要選擇一個更大的數字。
ZEND_VM_HANDLER(159, ZEND_IN, CONST|TMP|VAR|CV, CONST|TMP|VAR|CV)
{
USE_OPLINE
zend_free_op free_op1, free_op2;
zval *op1, *op2;
SAVE_OPLINE();
op1 = GET_OP1_ZVAL_PTR(BP_VAR_R);
op2 = GET_OP2_ZVAL_PTR(BP_VAR_R);
/* TODO */
FREE_OP1();
FREE_OP2();
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
複製代碼
上面的代碼只會獲取操做數而後丟棄。
爲了生成一個新的 VM ,你須要在 Zend/
目錄內運行 php_zend_vm_gen.php
。(若是它給了你一堆 /e modifier being deprecated 警告,忽略掉就好了)。運行完之後,去頂級目錄運行 make -j4
從新編譯。
終於,咱們能實現真正的邏輯了。咱們開始寫字符串類型的狀況吧:
if (Z_TYPE_P(op2) == IS_STRING) {
zval op1_copy;
int use_copy;
// 把要 needle(要找的數據) 轉換爲 string
zend_make_printable_zval(op1, &op1_copy, &use_copy);
if (Z_STRLEN_P(op1) == 0) {
/* 空的 needle 直接返回 true */
ZVAL_TRUE(&EX_T(opline->result.var).tmp_var);
} else {
char *found = zend_memnstr(
Z_STRVAL_P(op2), /* haystack */
Z_STRVAL_P(op1), /* needle */
Z_STRLEN_P(op1), /* needle length */
Z_STRVAL_P(op2) + Z_STRLEN_P(op2) /* haystack end ptr */
);
ZVAL_BOOL(&EX_T(opline->result.var).tmp_var, found != NULL);
}
/* Free copy */
if (use_copy) {
zval_dtor(&op1_copy);
}
}
複製代碼
最難的部分是把 needle 轉換成字符串,這裏使用了 zend_make_printable_zval
。這個函數也許會建立一個新的 zval。這就是咱們傳 op1_copy
和 use_copy
的緣由。 若是函數複製了值,咱們只需將它放入op1變量中(因此咱們沒必要處處處理兩個不一樣的變量)。此外,必須在最後釋放複製的值(最後三行的內容)。
若是你添加了上面的代碼到/* TODO */
所在的位置,再運行 zend_vm_gen.php
而後從新編譯 make -j4
,你已經完成了 in
操做符一半的工做:
$ sapi/cli/php -r 'var_dump("foo" in "bar");'
bool(false)
$ sapi/cli/php -r 'var_dump("foo" in "foobar");'
bool(true)
$ sapi/cli/php -r 'var_dump("foo" in "hallo foo world");'
bool(true)
$ sapi/cli/php -r 'var_dump(2 in "123");'
bool(true)
$ sapi/cli/php -r 'var_dump(5 in "123");'
bool(false)
$ sapi/cli/php -r 'var_dump("" in "test");'
bool(true)
複製代碼
接下來咱們進行實現數組的部分:
else if (Z_TYPE_P(op2) == IS_ARRAY) {
HashPosition pos;
zval **value;
/* Start under the assumption that the value isn't contained */ ZVAL_FALSE(&EX_T(opline->result.var).tmp_var); /* Iterate through the array */ zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(op2), &pos); while (zend_hash_get_current_data_ex(Z_ARRVAL_P(op2), (void **) &value, &pos) == SUCCESS) { zval result; /* Compare values using == */ if (is_equal_function(&result, op1, *value TSRMLS_CC) == SUCCESS && Z_LVAL(result)) { ZVAL_TRUE(&EX_T(opline->result.var).tmp_var); break; } zend_hash_move_forward_ex(Z_ARRVAL_P(op2), &pos); } } 複製代碼
這裏咱們簡單的遍歷了 haystack 中的每一個值,並檢查是否和 needle 相等。咱們在這裏使用 ==
對比,要使用 ==
對比的話,必須使用 is_identical_function
代替 is_equal_function
。
再次運行完 zend_vm_gen.php
和 make -j4
後,in
操做符號就支持數組類型的操做了:
$ sapi/cli/php -r 'var_dump("test" in []);'
bool(false)
$ sapi/cli/php -r 'var_dump("test" in ["foo", "bar"]);'
bool(false)
$ sapi/cli/php -r 'var_dump("test" in ["foo", "test", "bar"]);'
bool(true)
$ sapi/cli/php -r 'var_dump(0 in ["foo"]);'
bool(true) // because we're comparing using == 複製代碼
最後一件須要考慮的事情是,若是第二個參數既不是數組又不是字符串咱們該如何處理。這裏我選擇最簡單的辦法: 拋出一個警告並返回 false:
else {
zend_error(E_WARNING, "Right operand of in has to be either string or array");
ZVAL_FALSE(&EX_T(opline->result.var).tmp_var);
}
複製代碼
從新生成 VM,再編譯後:
$ sapi/cli/php -r 'var_dump("foo" in new stdClass);'
Warning: Right operand of in has to be either string or array in Command line code on line 1
bool(false)
複製代碼
我但願這篇文章能夠幫你理解如何給 PHP 添加新特性,理解 Zend 引擎 如何運行 php 腳本。儘管這篇文章很長,可是我只覆蓋到了整個系統的一小部分。當你想對 ZE 作出一些修改的時候,工做量最大的部分就是閱讀已經存在的代碼。交叉引用工具在閱讀代碼的時候會提供很大幫助。除此之外,也能夠在 efnet 的 #php.pecl 房間問問題。
當你添加完你想加的特性後,下一步就是把它放到內部郵件列表。人們會查看你加的特性並決定是否應該把它加進項目中。
對了,還有最後一件事: in
操做符只是一個示例。我並不打算提議包含這個特性 ;)
若是你有任何問題或意見,請在下方留言。