如何找到函數的定義
作為開始,讓我們試著找出strpos函數的定義。
嘗試的第一步,就是去PHP 5.4根目錄然後在頁面頂部的搜尋框輸入strpos。搜尋的結果是一個很大的列表,展示了strpos在PHP原始碼中出現的位置。
因為這個結果對我們並沒有太大的幫助,我們使用一個小技巧:我們搜尋」PHP_FUNCTION strpos」(不要漏了雙引號,它們很重要),而不是strpos.
現在我們得到兩個入口連結:
/PHP_5_4/ext/standard/
php_string.h 48 PHP_FUNCTION(strpos);
## strpos)第一個要注意的事情是,兩個位置都是在ext/standard資料夾。這就是我們希望找到的,因為strpos函數(跟大部分string,array和檔案函數一樣)是standard擴充的一部分。 現在,在新標籤頁打開兩個鏈接,然後看看它們背後藏了什麼程式碼。 你會看到第一個連結帶你到了php_string.h文件,它包含了下面的程式碼:// ... PHP_FUNCTION(strpos); PHP_FUNCTION(stripos); PHP_FUNCTION(strrpos); PHP_FUNCTION(strripos); PHP_FUNCTION(strrchr); PHP_FUNCTION(substr); // ...這就是一個典型的頭檔(以.h後綴結尾的文件)的樣子:單純的函數列表,函數在其他地方定義。事實上,我們對這些並不感興趣,因為我們已經知道我們要找的是什麼。 第二個連結更有趣:它帶我們到string.c文件,這個文件包含了函數真正的原始碼。 在我帶你一步一步地查閱這個函數之前,我推薦你自己嘗試理解這個函數。這是一個很簡單的函數,儘管你不知道真正的細節,但大多數程式碼看起來都很清晰。
PHP函數的骨架
所有的PHP函數都使用同一個基本結構。在函數頂部定義了各個變量,然後呼叫zend_parse_parameters函數,然後到了主要的邏輯,當中有RETURN_***和php_error_docref的呼叫。 那麼,讓我們以函數的定義開始:zval *needle;char *haystack;char *found = NULL;char needle_char[2];long offset = 0;int haystack_len;#第一行定義了一個指向zval的指標needle。 zval是在PHP內部代表任一個PHP變數的定義。它真正是怎麼樣的會在下一篇文章中重點談論。 第二行定義了指向單一字元的指標haystack。這時候,你需要記住,在C語言裡面,陣列代表指向它們第一個元素的指標。比如說,haystack變數會指向你所傳遞的$haystack字串變數的第一個字元。 haystack + 1會指向第二個字符,haystack + 2指向第三個,以此類推。因此,透過逐個遞增指針,可以讀取整個字串。
那麼問題來了,PHP需要知道字串在哪裡結束。不然的話,它會一直遞增指針而不會停止。為了解決這個問題,PHP也保存了明確的長度,這就是haystack_len變數。 現在,在上面的定義中,我們感興趣的是offset變量,這個變數用來保存函數的第三個參數:開始搜尋的偏移量。它使用long來定義,跟int一樣,也是整數資料型別。現在這兩者的差異並不重要,但你需要知道的是在PHP中,整數值使用long來存儲,字串的長度使用int來存儲。
現在來看看下面的三行:if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|l", &haystack, &haystack_len, &needle, &offset) == FAILURE) { return; }這三行程式碼做的事情就是,取得傳遞到
函數的參數,然後把它們儲存到上面聲明的變數中。
傳遞給函數的第一個參數是傳遞參數的數量。這個數字透過ZEND_NUM_ARGS()巨集提供。 下一個函數是TSRMLS_CC宏,這是PHP的特性。你會發現這個奇怪的宏分散在PHP程式碼庫的很多地方。是執行緒安全資源管理器(TSRM)的一部分,它保證PHP不會在多執行緒之間混亂變數。這對我們來說不是很重要,當你在程式碼中看到TSRMLS_CC(或TSRMLS_DC)的時候,忽略它就行。 (有一個奇怪的地方你需要注意的是,在”argument”之前沒有逗號。這是因為不管你是否使用線程安全創建函數,該巨集會被解釋為空或者, trsm_ls。因此,逗號是巨集的一部分。 :s // 第一个参数是字符串 z // 第二个参数是一个zval结构体,任意的变量 | // 标识接下来的参数是可选的 l // 第三个参数是long类型(整型)除了s,z,l之外,還有更多的標識類型,但是大部分都能從字元中清楚其意思。例如b是boolean,d是double(浮點型數字),a是array,f是回呼(function),o是object。
接下来的参数&haystack;,&haystack;_len,&needle;,&offset;指定了需要赋值的参数的变量。你可以看到,它们都是使用引用(&)传递的,意味着它们传递的不是变量本身,而是指向它们的指针。
这个函数调用之后,haystack会包含haystack字符串,haystack_len是字符串的长度,needle是needle的值,offset是开始的偏移量。
而且,这个函数使用FAILURE(当你尝试传递无效参数到函数时会发生,比如传递一个数组赋值到字符串)来检查。这种情况下zend_parse_parameters函数会抛出警告,而此函数马上返回(会返回null给PHP的用户层代码)。
在参数解析完毕以后,主函数体开始:
if (offset < 0 || offset > haystack_len) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Offset not contained in string"); RETURN_FALSE; }
这段代码做的事情很明显,如果offset超出了边界,一个E_WARNING级别的错误会通过php_error_docref函数抛出,然后函数使用RETURN_FALSE宏返回false。
php_error_docref是一个错误函数,你可以在扩展目录找到它(比如,ext文件夹)。它的名字根据它在错误页面中返回文档参考(就是那些不会正常工作的函数)定义。还有一个zend_error函数,它主要被Zend Engine使用,但也经常出现在扩展代码中。
两个函数都使用sprintf函数,比如格式化信息,因此错误信息可以包含占位符,那些占位符会被后面的参数填充。下面有一个例子:
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to write %d bytes to %s", Z_STRLEN_PP(tmp), filename); // %d is filled with Z_STRLEN_PP(tmp) // %s is filled with filename
让我们继续解析代码:
if (Z_TYPE_P(needle) == IS_STRING) { if (!Z_STRLEN_P(needle)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Empty delimiter"); RETURN_FALSE; } found = php_memnstr(haystack + offset, Z_STRVAL_P(needle), Z_STRLEN_P(needle), haystack + haystack_len); }
前面的5行非常清晰:这个分支只会在needle为字符串的情况下执行,而且如果它是空的话会抛出错误。然后到了比较有趣的一部分:php_memnstr被调用了,这个函数做了主要的工作。跟往常一样,你可以点击该函数名然后查看它的源码。
php_memnstr返回指向needle在haystack第一次出现的位置的指针(这就是为什么found变量要定义为char *,例如,指向字符的指针)。从这里可以知道,偏移量(offset)可以通过减法被简单地计算,可以在函数的最后看到:
RETURN_LONG(found - haystack);
最后,让我们来看看当needle作为非字符串的时候的分支:
else { if (php_needle_char(needle, needle_char TSRMLS_CC) != SUCCESS) { RETURN_FALSE; } needle_char[1] = 0; found = php_memnstr(haystack + offset, needle_char, 1, haystack + haystack_len); }
我只引用在手册上写的”如果 needle 不是一个字符串,那么它将被转换为整型并被视为字符顺序值。”这基本上说明,除了写strpos($str, 'A'),你还可以写strpos($str, 65),因为A字符的编码是65。
如果你再查看变量定义,你可以看到needle_char被定义为char needle_char[2],即有两个字符的字符串,php_needle_char会将真正的字符(在这里是’A’)到needle_char[0]。然后strpos函数会设置needle_char[1]为0。这背后的原因是因为,在C里面,字符串是使用’’结尾,就是说,最后一个字符被设置为NUL(编码为0的字符)。在PHP的语法环境里,这样的情况不存在,因为PHP存储了所有字符串的长度(因此它不需要0来帮助找到字符串的结尾),但是为了保证与C函数的兼容性,还是在PHP的内部实现了。
Zend functions
我对strpos这个函数感觉好累,让我们找另一个函数吧:strlen。我们使用之前的方法:
从PHP5.4源码根目录开始搜索strlen。
你会看到一堆无关的函数的使用,因此,搜索“PHP_FUNCTION strlen”。当你这么搜索的时候,你会发现一些奇怪的事情发生了:没有任何的结果。
原因是,strlen是少数通过Zend Engine而不是PHP扩展定义的函数。这种情况下,函数不是使用PHP_FUNCTION(strlen)定义,而是ZEND_FUNCTION(strlen)。因此,我们也要搜索“ZEND_FUNCTION strlen”。
我们都知道,我们需要点击没有分号结尾的链接跳到源码的定义。这个链接带我们到下面的函数定义:
ZEND_FUNCTION(strlen) { char *s1; int s1_len; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &s1, &s1_len) == FAILURE) { return; } RETVAL_LONG(s1_len); }
这个函数实现太简单了,我不觉得我还需要进一步的解释。
方法
我们会谈论类和对象如何工作的更多细节在其他文章里,但作为一个小小的剧透:你可以通过在搜索框搜索ClassName::methodName来搜索对象方法。例如,尝试搜索SplFixedArray::getSize。
以上是如何使用php內部函數的詳細內容。更多資訊請關注PHP中文網其他相關文章!