溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

PHP源碼之implode函數(shù)源碼怎么用

發(fā)布時(shí)間:2020-11-16 11:03:33 來源:億速云 閱讀:156 作者:小新 欄目:編程語言

這篇文章將為大家詳細(xì)講解有關(guān)PHP源碼之implode函數(shù)源碼怎么用,小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

PHP 中的 implode

  • 在 PHP 中,implode 的作用是:將一個(gè)一維數(shù)組的值轉(zhuǎn)化為字符串。記住一維數(shù)組,如果是多維的,會(huì)發(fā)生什么呢?在本篇分析中,會(huì)有所探討。
  • 事實(shí)上,通過官方的文檔可以知道,implode 有兩種用法,通過函數(shù)簽名可以看得出來:
// 方法1
implode ( string $glue , array $pieces ) : string
// 方法2
implode ( array $pieces ) : string
  • 因?yàn)?,在不?glue 的時(shí)候,內(nèi)部實(shí)現(xiàn)會(huì)默認(rèn)空字符串。
  • 通過一個(gè)簡單的示例可以看出:
$pieces = [
    123,
    ',是一個(gè)',
    'number!',
];
$str1 = implode($pieces);
$str2 = implode('', $pieces);

var_dump($str1, $str2);
/*
string(20) "123,是一個(gè)number!"
string(20) "123,是一個(gè)number!"
*/

implode 源碼實(shí)現(xiàn)

  • 通過搜索關(guān)鍵字 PHP_FUNCTION(implode) 可以找到,該函數(shù)定義于 \ext\standard\string.c 文件中的 1288 行
  • 一開始的幾行是參數(shù)聲明相關(guān)的信息。其中 *arg2 是用于接收 pieces 參數(shù)的指針。
  • 在下方對 arg2 的判斷中,如果 arg2 為空,則表示沒有傳 pieces 對應(yīng)的值
if (arg2 == NULL) {
    if (Z_TYPE_P(arg1) != IS_ARRAY) {
        php_error_docref(NULL, E_WARNING, "Argument must be an array");
        return;
    }

    glue = ZSTR_EMPTY_ALLOC();
    tmp_glue = NULL;
    pieces = arg1;
} else {
    if (Z_TYPE_P(arg1) == IS_ARRAY) {
        glue = zval_get_tmp_string(arg2, &tmp_glue);
        pieces = arg1;
    } else if (Z_TYPE_P(arg2) == IS_ARRAY) {
        glue = zval_get_tmp_string(arg1, &tmp_glue);
        pieces = arg2;
    } else {
        php_error_docref(NULL, E_WARNING, "Invalid arguments passed");
        return;
    }
}

不傳遞 pieces 參數(shù)

  • 在不傳遞 pieces 參數(shù)的判斷中,即 arg2 == NULL,主要是對參數(shù)的一些處理
  • 將 glue 初始化為空字符串,并將傳進(jìn)來的唯一的參數(shù),賦值給 pieces 變量,接著就調(diào)用 php_implode(glue, pieces, return_value);

十分關(guān)鍵的 php_implode

  • 無論有沒有傳遞 pieces 參數(shù),在處理好參數(shù)后,最終都會(huì)調(diào)用 PHPAPI 的相關(guān)函數(shù) php_implode,可見,關(guān)鍵邏輯都是在這個(gè)函數(shù)中實(shí)現(xiàn)的,那么我們深入其中看一看它
  • 在調(diào)用 php_implode 時(shí),出現(xiàn)了一個(gè)看起來沒有被聲明的變量 return_value。沒錯(cuò),它似乎就是憑空出現(xiàn)的
  • 通過谷歌搜索 PHP源碼中 return_value,找到了答案。
  • 原來,這個(gè)變量是伴隨著宏 PHP_FUNCTION 而出現(xiàn)的,而此處 implode 的實(shí)現(xiàn)就是通過 PHP_FUNCTION(implode) 來聲明的。而 PHP_FUNCTION 的定義是:
#define PHP_FUNCTION            ZEND_FUNCTION
// 對應(yīng)的 ZEND_FUNCTION 定義如下
#define ZEND_FUNCTION(name)                ZEND_NAMED_FUNCTION(ZEND_FN(name))
// 對應(yīng)的 ZEND_NAMED_FUNCTION 定義如下
#define ZEND_NAMED_FUNCTION(name)        void ZEND_FASTCALL name(INTERNAL_FUNCTION_PARAMETERS)
// 對應(yīng)的 ZEND_FN 定義如下
#define ZEND_FN(name) zif_##name
// 對應(yīng)的 ZEND_FASTCALL 定義如下
# define ZEND_FASTCALL __attribute__((fastcall))
  • (關(guān)于雙井號,它起連接符的作用,可以參考這里了解)
  • 在被預(yù)處理后,它的樣子類似于下方所示:
void zif_implode(int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC)
  • 也就是說 return_value 是作為整個(gè) implode 擴(kuò)展函數(shù)定義的一個(gè)形參
  • 在 php_implode 的定義中,一開始,先定義了一些即將用到的變量,隨后使用 ALLOCA_FLAG(use_heap) 進(jìn)行標(biāo)識,如果申請內(nèi)存,則申請的是堆內(nèi)存
  • 通過 numelems = zend_hash_num_elements(Z_ARRVAL_P(pieces)); 獲取 pieces 參數(shù)的單元數(shù)量,如果是空數(shù)組,則直接返回空字符串
  • 此處還有判斷,如果數(shù)組單元數(shù)為 1,則直接將唯一的單元作為字符串返回。
  • 最后是處理多數(shù)組單元的情況,因?yàn)榍懊鏄?biāo)識過,若申請內(nèi)存則申請的是堆內(nèi)存,堆內(nèi)存相對于棧來講,效率比較低,所以只在非用不可的情形下,才會(huì)申請堆內(nèi)存,那此處的情形就是多單元數(shù)組的情況。
  • 隨后,針對 pieces 循環(huán),獲取其值進(jìn)行拼接,在源碼中的 foreach 循環(huán)是固定結(jié)構(gòu),如下:
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(zend_array), tmp) {
    // ...
} ZEND_HASH_FOREACH_END();
  • 這種常用寫法我覺得,在編寫 PHP 擴(kuò)展中是必不可少的吧。雖然我還沒有編寫過任何一個(gè)可用于生產(chǎn)環(huán)境的 PHP 擴(kuò)展。但我正努力朝那個(gè)方向走呢!
  • 在循環(huán)內(nèi),對數(shù)組單元分為三類:

    • 字符串
    • 整形數(shù)據(jù)
    • 其它
  • 事實(shí)上,在循環(huán)開始之前,源碼中,先申請了一塊內(nèi)存,用于存放下面的結(jié)構(gòu)體,并且個(gè)數(shù)恰好是 pieces 數(shù)組單元的個(gè)數(shù)。
struct {
    zend_string *str;
    zend_long    lval;
} *strings, *ptr;
  • 可以看到,結(jié)構(gòu)體成員包含 zend 字符串以及 zend 整形數(shù)據(jù)。這個(gè)結(jié)構(gòu)體的出現(xiàn),恰好是為了存放數(shù)組單元中的 zend 字符串/zend 整形數(shù)據(jù)。
字符串
  • 先假設(shè),pieces 數(shù)組單元中,都是字符串類型,此時(shí)循環(huán)中執(zhí)行的邏輯就是:
// tmp 是循環(huán)中的單元值
ptr->str = Z_STR_P(tmp);
len += ZSTR_LEN(ptr->str);
ptr->lval = 0;
ptr++;
  • 其中,tmp 是循環(huán)中的單元值。每經(jīng)歷一次循環(huán),會(huì)將單元值放入結(jié)構(gòu)體中,隨后進(jìn)行指針 +1 運(yùn)算,指針就指向存儲(chǔ)下一個(gè)結(jié)構(gòu)體數(shù)據(jù)的地址:
  • 并且,在這期間,統(tǒng)計(jì)出了字符串的總長度 len += ZSTR_LEN(ptr->str);
整數(shù)類型
  • 以上,討論了數(shù)組單元中是字符串的情況。接下來看看,如果數(shù)組單元的類型是數(shù)值類型時(shí)會(huì)發(fā)生什么?
  • 判斷一個(gè)變量是否是數(shù)值類型(其實(shí)是 zend_long),通用方法是:Z_TYPE_P(tmp) == IS_LONG。一旦知道當(dāng)前的數(shù)據(jù)類型是 zend_long,則將其賦值給 ptr 的 lval 結(jié)構(gòu)體成員。然后 ptr 指針后移一個(gè)單位長度。
  • 但是,我們知道我們不能像獲取 zend_string 的長度一樣去獲取 zend_long 的字符長度。如果是 zend_string,則可以通過 len += ZSTR_LEN(val); 的方式獲取其字符長度。對于 zend_long,有什么好的方法呢?
  • 在源碼中是通過對 10 做除法運(yùn)算,得出結(jié)果的一部分,再慢慢的累加其長度:
while (val) {
    val /= 10;
    len++;
}
  • 如果是負(fù)數(shù)呢?沒有什么特別的辦法,直接判斷處理:
if (val <= 0) {
    len++;
}

字符串的處理和拷貝

  • 循環(huán)結(jié)束后,ptr 就是指向這段內(nèi)存的尾部的指針。
  • 然后,申請了一段內(nèi)存:str = zend_string_safe_alloc(numelems - 1, ZSTR_LEN(glue), len, 0);,用于存放單元字符串總長度加上連接字符的總長度,即 (n-1)glue + len。因?yàn)?n 個(gè)數(shù)組單元,只需要 n-1 個(gè) glue 字符串。然后,將這段內(nèi)存的尾地址,賦值給 cptr,為什么要指向尾部呢?看下一部分,你就會(huì)明白了。
  • 接下來,需要循環(huán)取出存放在 ptr 中的字符。我們知道,ptr 此時(shí)是所處內(nèi)存區(qū)域的尾部,為了能有序展示連接的字符串,源碼中,是從后向前循環(huán)處理。這也就是為什么需要把 cptr 指向所在內(nèi)存區(qū)域的尾部的原因。
  • 進(jìn)入循環(huán),先進(jìn)行 ptr--;,然后針對 ptr->str 的判斷 if (EXPECTED(ptr->str)),看了一下此處的 EXPECTED 的作用,可以參考這里??梢院唵蔚膶⑵淅斫庖环N匯編層面的優(yōu)化,當(dāng)實(shí)際執(zhí)行的情況更偏向于當(dāng)前條件下的分支而非 else 的分支時(shí),就用 EXPECTED 宏將其包裝起來:EXPECTED(ptr->str)。我敢說,當(dāng)你調(diào)用 implode 傳遞的數(shù)組中都是數(shù)字而非字符串,那么這里的 EXPECTED 作用就會(huì)失效。
  • 接下來的兩行是比較核心的:
cptr -= ZSTR_LEN(ptr->str);
memcpy(cptr, ZSTR_VAL(ptr->str), ZSTR_LEN(ptr->str));
  • cptr 的指針前移一個(gè)數(shù)組單元字符的長度,然后將 ptr->str (某數(shù)組單元的值)通過 c 標(biāo)準(zhǔn)庫函數(shù) memcpy 拷貝到 cptr 內(nèi)存空間中。
  • 當(dāng) ptr == strings 滿足時(shí),意味著 ptr 不再有可被復(fù)制的字符串/數(shù)字。因?yàn)?strings 是 ptr 所在區(qū)域的首地址。
  • 通過上面,已經(jīng)成功將一個(gè)數(shù)組單元的字符串拷貝到 cptr 對應(yīng)的內(nèi)存區(qū)域中,接下來如何處理 glue 呢?
  • 只需要像處理 ptr->str 一樣處理 glue 即可。至少源碼中是這么做的。
  • 代碼中有一段是:*cptr = 0,它的作用相當(dāng)于賦值空字符串。
  • cptr 繼續(xù)前移 glue 的長度,然后,將 glue 字符串拷貝到 cptr 對應(yīng)的內(nèi)存區(qū)域中。沒錯(cuò),還是用 memcpy 函數(shù)。
  • 到這里,第一次循環(huán)結(jié)束了。我應(yīng)該不需要像實(shí)際循環(huán)中那樣描述這里的循環(huán)吧?相信優(yōu)秀的你,是完全可以參考上方的描述腦補(bǔ)出來的 ^^
  • 當(dāng)然,處理返回的兩句還是要提一下:
free_alloca(strings, use_heap);
RETURN_NEW_STR(str);
  • strings 的那一片內(nèi)存空間只是存儲(chǔ)臨時(shí)值的,因此函數(shù)結(jié)束了,就必須跟 strings 說再見。我們知道 c 語言是手動(dòng)管理內(nèi)存的,沒有 GC,你要顯示的釋放內(nèi)存,即 free_alloca(strings, use_heap);
  • 在上面的描述中,我們只講到了 cptr,但這里的返回值卻是 str。
  • 不用懷疑,這里是對的,我們所講的 cptr 那一片內(nèi)存區(qū)域的首地址就是 str。并通過宏 RETURN_NEW_STR 會(huì)將最終的返回值寫入 return_value 中

實(shí)踐

  • 為了可能更加清晰 implode 源碼中代碼運(yùn)行時(shí)的情況,接下來,我們通過 PHP 擴(kuò)展的方式對其進(jìn)行 debug。在這個(gè)過程中的代碼,我都放在 GitHub 的倉庫中,分支名是 debug/implode,可自行下載運(yùn)行,看看效果。
  • 新建 PHP 擴(kuò)展模板的操作,可以參考這里。請確保操作完里面描述的步驟。
  • 接下來,主要針對 su_dd.c 文件修改代碼。為了能通過修改代碼來看效果,將 php_implode 函數(shù)復(fù)制到擴(kuò)展文件中,并將其命名為 su_php_implode:
static void su_php_implode(const zend_string *glue, zval *pieces, zval *return_value)
{
    // 源碼內(nèi)容省略
}
  • 在擴(kuò)展中新增一個(gè)擴(kuò)展函數(shù) su_test:
PHP_FUNCTION(su_test)
{
    zval tmp;
    zend_string *str, *glue, *tmp_glue;
    zval *arg1, *arg2 = NULL, *pieces;

    ZEND_PARSE_PARAMETERS_START(1, 2)
        Z_PARAM_ZVAL(arg1)
        Z_PARAM_OPTIONAL
        Z_PARAM_ZVAL(arg2)
    ZEND_PARSE_PARAMETERS_END();
    glue = zval_get_tmp_string(arg1, &tmp_glue);
    pieces = arg2;
    su_php_implode(glue, pieces, return_value);
}
  • 因?yàn)閿U(kuò)展的編譯以及引入,前面的已經(jīng)提及。因此,此時(shí)只需編寫 PHP 代碼進(jìn)行調(diào)用:
// t1.php
$res = su_test('-', [
    2019, '01', '01',
]);
var_dump($res);
  • PHP 運(yùn)行該腳本,輸出:string(10) "2019-01-01",這意味著,你已經(jīng)成功編寫了一個(gè)擴(kuò)展函數(shù)。別急,這只是邁出了第一步,別忘記我們的目標(biāo):通過調(diào)試來學(xué)習(xí) implode 源碼。
  • 接下來,我們通過 gdb 工具,調(diào)試以上 PHP 代碼在源碼層面的運(yùn)行。為了防止初學(xué)者不會(huì)用 gdb,這里就繁瑣的寫出這個(gè)過程。如果沒有安裝 gdb,請自行谷歌。
  • 先進(jìn)入 PHP 腳本所在路徑。命令行下:
gdb php
b zval_get_tmp_string
r t1.php
  • b 即 break,表示打一個(gè)斷點(diǎn)
  • r 即 run,表示運(yùn)行腳本
  • s 即 step,表示一步一步調(diào)試,遇到方法調(diào)用,會(huì)進(jìn)入方法內(nèi)部單步調(diào)試
  • n 即 next,表示一行一行調(diào)試。遇到方法,則調(diào)試直接略過直接執(zhí)行返回,調(diào)試不會(huì)進(jìn)入其內(nèi)部。
  • p 即 print,表示打印當(dāng)前作用域中的一個(gè)變量
  • 當(dāng)運(yùn)行完 r t1.php,則會(huì)定位到第一個(gè)斷點(diǎn)對應(yīng)的行,顯示如下:
Breakpoint 1, zif_su_test (execute_data=0x7ffff1a1d0c0, 
    return_value=0x7ffff1a1d090)
    at /home/www/clang/php-7.3.3/ext/su_dd/su_dd.c:179
179        glue = zval_get_tmp_string(arg1, &tmp_glue);
  • 此時(shí),按下 n,顯示如下:
184        su_php_implode(glue, pieces, return_value);
  • 此時(shí),當(dāng)前的作用域中存在變量:glue,piecesreturn_value
  • 我們可以通過 gdb 調(diào)試,查看 pieces 的值。先使用命令:p pieces,此時(shí)在終端會(huì)顯示類似于如下內(nèi)容:
$1 = (zval *) 0x7ffff1a1d120
  • 表明 pieces 是一個(gè) zval 類型的指針,0x7ffff1a1d120 是其地址,當(dāng)然,你運(yùn)行的時(shí)候?qū)?yīng)的也是一個(gè)地址,只不過跟我的這個(gè)會(huì)不太一樣。
  • 我們繼續(xù)使用 p 去打印存儲(chǔ)于改地址的變量內(nèi)容:p *$1,$1 可以認(rèn)為是一個(gè)臨時(shí)變量名,* 是取值運(yùn)算符。運(yùn)行完后,此時(shí)顯示如下:
(gdb) p *$1
$2 = {value = {lval = 140737247576960, dval = 6.9533439118030153e-310, 
    counted = 0x7ffff1a60380, str = 0x7ffff1a60380, arr = 0x7ffff1a60380, 
    obj = 0x7ffff1a60380, res = 0x7ffff1a60380, ref = 0x7ffff1a60380, 
    ast = 0x7ffff1a60380, zv = 0x7ffff1a60380, ptr = 0x7ffff1a60380, 
    ce = 0x7ffff1a60380, func = 0x7ffff1a60380, ww = {w1 = 4054188928, 
      w2 = 32767}}, u1 = {v = {type = 7 '\a', type_flags = 1 '\001', u = {
        call_info = 0, extra = 0}}, type_info = 263}, u2 = {next = 0, 
    cache_slot = 0, opline_num = 0, lineno = 0, num_args = 0, fe_pos = 0, 
    fe_iter_idx = 0, access_flags = 0, property_guard = 0, constant_flags = 0, 
    extra = 0}}
  • 打印的內(nèi)容,看起來是一堆亂糟糟的字符,這實(shí)際上是 zval 的結(jié)構(gòu)體,其中的字段剛好是和 zval 的成員一一對應(yīng)的,為了便于讀者閱讀,這里直接貼出 zval 的結(jié)構(gòu)體信息:
struct _zval_struct {
    zend_value        value;            /* value */
    union {
        struct {
            ZEND_ENDIAN_LOHI_3(
                zend_uchar    type,            /* active type */
                zend_uchar    type_flags,
                union {
                    uint16_t  call_info;    /* call info for EX(This) */
                    uint16_t  extra;        /* not further specified */
                } u)
        } v;
        uint32_t type_info;
    } u1;
    union {
        uint32_t     next;                 /* hash collision chain */
        uint32_t     cache_slot;           /* cache slot (for RECV_INIT) */
        uint32_t     opline_num;           /* opline number (for FAST_CALL) */
        uint32_t     lineno;               /* line number (for ast nodes) */
        uint32_t     num_args;             /* arguments number for EX(This) */
        uint32_t     fe_pos;               /* foreach position */
        uint32_t     fe_iter_idx;          /* foreach iterator index */
        uint32_t     access_flags;         /* class constant access flags */
        uint32_t     property_guard;       /* single property guard */
        uint32_t     constant_flags;       /* constant flags */
        uint32_t     extra;                /* not further specified */
    } u2;
};
  • 我們直指要害 —— value,打印一下其中的內(nèi)容。打印結(jié)構(gòu)體成員可以使用 . 運(yùn)算符,例如:p $2.value,運(yùn)行這個(gè)命令,顯示如下:
(gdb) p $2.value
$3 = {lval = 140737247576960, dval = 6.9533439118030153e-310, 
  counted = 0x7ffff1a60380, str = 0x7ffff1a60380, arr = 0x7ffff1a60380, 
  obj = 0x7ffff1a60380, res = 0x7ffff1a60380, ref = 0x7ffff1a60380, 
  ast = 0x7ffff1a60380, zv = 0x7ffff1a60380, ptr = 0x7ffff1a60380, 
  ce = 0x7ffff1a60380, func = 0x7ffff1a60380, ww = {w1 = 4054188928, 
    w2 = 32767}}
  • 通過 zval 結(jié)構(gòu)體,我們知道 value 成員的類型是 zend_value,很不幸,這也是一個(gè)結(jié)構(gòu)體:
typedef union _zend_value {
    zend_long         lval;                /* long value */
    double            dval;                /* double value */
    zend_refcounted  *counted;
    zend_string      *str;
    zend_array       *arr;
    zend_object      *obj;
    zend_resource    *res;
    zend_reference   *ref;
    zend_ast_ref     *ast;
    zval             *zv;
    void             *ptr;
    zend_class_entry *ce;
    zend_function    *func;
    struct {
        uint32_t w1;
        uint32_t w2;
    } ww;
} zend_value;
  • 我們要打印的變量是 pieces,我們知道它是一個(gè)數(shù)組,因而此時(shí)我們直接取 zend_value 結(jié)構(gòu)體的 *arr 成員,它外表看起來就是一個(gè)指針,因此打印其內(nèi)容,需要使用 * 運(yùn)算符
(gdb) p *$3.arr
$4 = {gc = {refcount = 2, u = {type_info = 23}}, u = {v = {flags = 28 '\034', 
      _unused = 0 '\000', nIteratorsCount = 0 '\000', _unused2 = 0 '\000'}, 
    flags = 28}, nTableMask = 4294967294, arData = 0x7ffff1a67648, 
  nNumUsed = 3, nNumOfElements = 3, nTableSize = 8, nInternalPointer = 0, 
  nNextFreeElement = 3, pDestructor = 0x555555b6e200 <zval_ptr_dtor>}
  • 真棒!到目前為止,貌似一切都按照預(yù)定的路線進(jìn)行。通過 zend_value 結(jié)構(gòu)體,可以知道 *arr 的類型是 zend_array:
struct _zend_array {
    zend_refcounted_h gc;
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    flags,
                zend_uchar    _unused,
                zend_uchar    nIteratorsCount,
                zend_uchar    _unused2)
        } v;
        uint32_t flags;
    } u;
    uint32_t          nTableMask;
    Bucket           *arData;
    uint32_t          nNumUsed;
    uint32_t          nNumOfElements;
    uint32_t          nTableSize;
    uint32_t          nInternalPointer;
    zend_long         nNextFreeElement;
    dtor_func_t       pDestructor;
};
  • 了解 PHP 數(shù)組的同學(xué)一定知道它底層是一個(gè) HashTable,感興趣的同學(xué),可以去自行了解一下 HashTable。這里,我們打印 *arData,使用:p *$4.arDaa:
(gdb) p *$4.arData
$5 = {val = {value = {lval = 2019, dval = 9.9751853895347677e-321, 
      counted = 0x7e3, str = 0x7e3, arr = 0x7e3, obj = 0x7e3, res = 0x7e3, 
      ref = 0x7e3, ast = 0x7e3, zv = 0x7e3, ptr = 0x7e3, ce = 0x7e3, 
      func = 0x7e3, ww = {w1 = 2019, w2 = 0}}, u1 = {v = {type = 4 '\004', 
        type_flags = 0 '\000', u = {call_info = 0, extra = 0}}, type_info = 4}, 
    u2 = {next = 0, cache_slot = 0, opline_num = 0, lineno = 0, num_args = 0, 
      fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, 
      constant_flags = 0, extra = 0}}, h = 0, key = 0x0}
  • 到這里,我們已經(jīng)可以看到 pieces 數(shù)組第一個(gè)單元的值 —— 2019,就是那段 lval = 2019
  • 好了,關(guān)于 gdb 的簡單使用就先介紹到這里。文章開篇,我們提到,如果數(shù)組是多維數(shù)組,會(huì)發(fā)生什么?我們實(shí)踐的主要目標(biāo)就是簡單實(shí)現(xiàn)二維數(shù)組的 implode
  • 在 PHP 的 implode 函數(shù)中,如果是多維數(shù)組,則會(huì)直接把里層的數(shù)組顯示為 Array 字符串。
$res = implode('-', [
    2019, '01', '01', [1,2]
]);
var_dump($res);
  • 運(yùn)行這段腳本,會(huì)輸出如下:
PHP Notice:  Array to string conversion in /path/to/t2.php on line 3
PHP Notice:  Array to string conversion in /path/to/t2.php on line 3
string(16) "2019-01-01-Array"
  • 為了能夠支持連接數(shù)組,我們需要改寫 php_implode,因此,先拷貝一下 php_implode 到寫擴(kuò)展代碼的文件中:
PHPAPI void php_implode(const zend_string *glue, zval *pieces, zval *return_value)
{
    zval         *tmp;
    int           numelems;
    zend_string  *str;
    char         *cptr;
    size_t        len = 0;
    struct {
        zend_string *str;
        zend_long    lval;
    } *strings, *ptr;
    ALLOCA_FLAG(use_heap)

    numelems = zend_hash_num_elements(Z_ARRVAL_P(pieces));

    if (numelems == 0) {
        RETURN_EMPTY_STRING();
    } else if (numelems == 1) {
        /* loop to search the first not undefined element... */
        ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pieces), tmp) {
            RETURN_STR(zval_get_string(tmp));
        } ZEND_HASH_FOREACH_END();
    }

    ptr = strings = do_alloca((sizeof(*strings)) * numelems, use_heap);

    ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pieces), tmp) {
        if (EXPECTED(Z_TYPE_P(tmp) == IS_STRING)) {
            ptr->str = Z_STR_P(tmp);
            len += ZSTR_LEN(ptr->str);
            ptr->lval = 0;
            ptr++;
        } else if (UNEXPECTED(Z_TYPE_P(tmp) == IS_LONG)) {
            zend_long val = Z_LVAL_P(tmp);

            ptr->str = NULL;
            ptr->lval = val;
            ptr++;
            if (val <= 0) {
                len++;
            }
            while (val) {
                val /= 10;
                len++;
            }
        } else {
            ptr->str = zval_get_string_func(tmp);
            len += ZSTR_LEN(ptr->str);
            ptr->lval = 1;
            ptr++;
        }
    } ZEND_HASH_FOREACH_END();

    /* numelems can not be 0, we checked above */
    str = zend_string_safe_alloc(numelems - 1, ZSTR_LEN(glue), len, 0);
    cptr = ZSTR_VAL(str) + ZSTR_LEN(str);
    *cptr = 0;

    while (1) {
        ptr--;
        if (EXPECTED(ptr->str)) {
            cptr -= ZSTR_LEN(ptr->str);
            memcpy(cptr, ZSTR_VAL(ptr->str), ZSTR_LEN(ptr->str));
            if (ptr->lval) {
                zend_string_release_ex(ptr->str, 0);
            }
        } else {
            char *oldPtr = cptr;
            char oldVal = *cptr;
            cptr = zend_print_long_to_buf(cptr, ptr->lval);
            *oldPtr = oldVal;
        }

        if (ptr == strings) {
            break;
        }

        cptr -= ZSTR_LEN(glue);
        memcpy(cptr, ZSTR_VAL(glue), ZSTR_LEN(glue));
    }

    free_alloca(strings, use_heap);
    RETURN_NEW_STR(str);
}
  • 先將函數(shù)簽名稍微調(diào)整成 static void su_php_implode(const zend_string *glue, zval *pieces, zval *return_value)
  • 我們可以看到其中有一段循環(huán) pieces 的處理:
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pieces), tmp) {
        if (EXPECTED(Z_TYPE_P(tmp) == IS_STRING)) {
            // ...
        } else if (UNEXPECTED(Z_TYPE_P(tmp) == IS_LONG)) {
            // ...
        } else {
            // ...
        }
    } ZEND_HASH_FOREACH_END();
  • 我們只需將其中的 if 分支新增一個(gè)分支:else if (UNEXPECTED(Z_TYPE_P(tmp) == IS_ARRAY)),其具體內(nèi)容如下:
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pieces), tmp) {
    if (EXPECTED(Z_TYPE_P(tmp) == IS_STRING)) {
        // ...
    } else if (UNEXPECTED(Z_TYPE_P(tmp) == IS_LONG)) {
        // ...
    } else if (UNEXPECTED(Z_TYPE_P(tmp) == IS_ARRAY)) {
        // 如果值是數(shù)組,則調(diào)用 php_implode,將其使用 glue 連接成字符串
        cptr = ZSTR_VAL(ptr->str);
        zend_string* str2 = origin_php_implode(glue, tmp, tmp_val);
        ptr->str = str2;
        // 此時(shí),要拿到拼接后的字符串長度
        len += ZSTR_LEN(str2);
        ptr++;
    } else {
        // ...
    }
} ZEND_HASH_FOREACH_END();
  • 正如注釋中寫的,當(dāng)遇到數(shù)組的單元是數(shù)組類型時(shí),我們會(huì)調(diào)用原先的 php_implode,只不過,這個(gè)“php_implode”會(huì)真的返回一個(gè) zend_string 指針,在此我將其改名為 origin_php_implode
static zend_string* origin_php_implode(const zend_string *glue, zval *pieces, zval *return_value)
{
    zval         *tmp;
    int           numelems;
    zend_string  *str;
    char         *cptr;
    size_t        len = 0;
    struct {
        zend_string *str;
        zend_long    lval;
    } *strings, *ptr;
    ALLOCA_FLAG(use_heap)

    numelems = zend_hash_num_elements(Z_ARRVAL_P(pieces));

    if (numelems == 0) {
        RETURN_EMPTY_STRING();
    } else if (numelems == 1) {
        /* loop to search the first not undefined element... */
        ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pieces), tmp) {
            RETURN_STR(zval_get_string(tmp));
        } ZEND_HASH_FOREACH_END();
    }

    ptr = strings = do_alloca((sizeof(*strings)) * numelems, use_heap);

    ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pieces), tmp) {
        if (EXPECTED(Z_TYPE_P(tmp) == IS_STRING)) {
            ptr->str = Z_STR_P(tmp);
            len += ZSTR_LEN(ptr->str);
            ptr->lval = 0;
            ptr++;
        } else if (UNEXPECTED(Z_TYPE_P(tmp) == IS_LONG)) {
            zend_long val = Z_LVAL_P(tmp);

            ptr->str = NULL;
            ptr->lval = val;
            ptr++;
            if (val <= 0) {
                len++;
            }
            while (val) {
                val /= 10;
                len++;
            }
        } else {
            ptr->str = zval_get_string_func(tmp);
            len += ZSTR_LEN(ptr->str);
            ptr->lval = 1;
            ptr++;
        }
    } ZEND_HASH_FOREACH_END();

    /* numelems can not be 0, we checked above */
    str = zend_string_safe_alloc(numelems - 1, ZSTR_LEN(glue), len, 0);
    cptr = ZSTR_VAL(str) + ZSTR_LEN(str);
    *cptr = 0;

    while (1) {
        ptr--;
        if (EXPECTED(ptr->str)) {
            cptr -= ZSTR_LEN(ptr->str);
            memcpy(cptr, ZSTR_VAL(ptr->str), ZSTR_LEN(ptr->str));
            if (ptr->lval) {
                zend_string_release_ex(ptr->str, 0);
            }
        } else {
            char *oldPtr = cptr;
            char oldVal = *cptr;
            cptr = zend_print_long_to_buf(cptr, ptr->lval);
            *oldPtr = oldVal;
        }

        if (ptr == strings) {
            break;
        }

        cptr -= ZSTR_LEN(glue);
        memcpy(cptr, ZSTR_VAL(glue), ZSTR_LEN(glue));
    }

    free_alloca(strings, use_heap);
    // RETURN_NEW_STR(str);
    return str;
}
  • 內(nèi)容大體不變,只有函數(shù)簽名以及返回值的地方略作調(diào)整了。
  • 配合前面的 PHP_FUNCTION(su_test),功能實(shí)現(xiàn)的差不多了。我們?nèi)ゾ幾g看看:
./configure
sudo make
sudo make install
  • 太棒了,編譯通過。我們?nèi)?zhí)行一下 PHP 腳本:
$res = su_test('-', [
    2019, '01', '01', ['1', '2',],
]);
var_dump($res);
  • 輸出如下:
string(14) "2019-01-01-1-2"
  • 恭喜,我們已經(jīng)大功告成!

關(guān)于PHP源碼之implode函數(shù)源碼怎么用就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI