标签归档:PHP源码

PHP源码阅读笔记六:stream_get_wrappers函数

PHP源码阅读笔记stream_get_wrappers函数
stream_get_wrappers

(PHP 5)
stream_get_wrappers — 返回注册的数据流列表
Description

array stream_get_wrappers ( void )

Returns an indexed array containing the name of all stream wrappers available on the running system.

在某次其他函数的实现过程中需要知道url_stream_wrappers_hash变量的来源,
从而发现此函数也是从url_stream_wrappers_hash变量直接读取数据,
于是有了对于此函数和url_stream_wrappers_hash变量的跟踪过程。
首先在standard文件夹下的streamsfuncs.c文件中包含了此扩展的实现
其路径如下:

1
2
3
4
==>PHP_FUNCTION(stream_get_wrappers)        //    streamsfuncs.c 548行
==>#define php_stream_get_url_stream_wrappers_hash() _php_stream_get_url_stream_wrappers_hash(TSRMLS_C)    //    php_stream.h    552行
==>PHPAPI HashTable *_php_stream_get_url_stream_wrappers_hash(TSRMLS_D)        //    streams/streams.c    58行
==>static HashTable url_stream_wrappers_hash;    //    全局静态变量,

从此函数的代码中我们可以看出它是直接遍历php_stream_get_url_stream_wrappers_hash()函数的返回值,生成一个字符串数组
php_stream_get_url_stream_wrappers_hash()函数

而函数直接调用全局变量中的数据,此变量初始化和注册过程跟踪如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
url_stream_wrappers_hash初始化位置:
==>int php_init_stream_wrappers(int module_number TSRMLS_DC)    //    streams.c     1395行  初始化数据流
引用位置:
==> if (php_init_stream_wrappers(module_number TSRMLS_CC) == FAILURE)     //    main.c 1765行,初始化,注册数据流
 
添加默认注册的流程如下:
==> zend_startup_modules(TSRMLS_C);    //    main.c 1843行,添加注册数据流
==>zend_hash_apply(&module_registry, (apply_func_t)zend_startup_module_ex TSRMLS_CC);    //    zend_API.c    1519行
==>ZEND_API int zend_startup_module_ex(zend_module_entry *module TSRMLS_DC)    //    zend_API.c 1424行    
==>if (module->module_startup_func) {    //    zend_API.c    1470行
==>PHP_MINIT_FUNCTION(basic)    //    basic_functions.c     3973行
==> php_register_url_stream_wrapper("php", &php_stream_php_wrapper TSRMLS_CC);
 php_register_url_stream_wrapper("file", &php_plain_files_wrapper TSRMLS_CC);
 php_register_url_stream_wrapper("data", &php_stream_rfc2397_wrapper TSRMLS_CC);
#ifndef PHP_CURL_URL_WRAPPERS
 php_register_url_stream_wrapper("http", &php_stream_http_wrapper TSRMLS_CC);
 php_register_url_stream_wrapper("ftp", &php_stream_ftp_wrapper TSRMLS_CC);
#endif 
     //     basic_functions.c    4073行,添加过程
==>php_register_url_stream_wrapper    //    main/streams/streams.c    1450行

此次跟踪的时间跨度为一周,也算是经历几多磨难。但是最后总算是弄清楚其来源。

PHP源码阅读笔记五:array 数组的创建

PHP源码阅读笔记五:array 数组的创建

在PHP的调试环境中使用cli执行PHP文件
一步一步跟踪代码,由于对C和词法分析不熟悉
所使用PHP文件如下:

1
2
3
 <?PHP
$a = array(1, 2, 3);
var_dump($a);

代码简单跟踪如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
 
===>php_execute_script(&file_handle TSRMLS_CC);     //php_cli.c line1130
==> retval = (zend_execute_scripts(ZEND_REQUIRE TSRMLS_CC, NULL, 3, prepend_file_p, primary_file, append_file_p) == SUCCESS);    //    main.c 2023行
==> EG(active_op_array) = zend_compile_file(file_handle, type TSRMLS_CC);      //    zend.c 1126行
==> compiler_result = zendparse(TSRMLS_C);   //    zend_language_scanner.c  3420行
==> zend-language_parse.c     2773==>case 382:     { zend_do_init_array(&(yyval), &(yyvsp[(1) - (1)]), NULL, 0 TSRMLS_CC); }    //    zend_language_parser.c  4689行
    ==>void zend_do_init_array(znode *result, znode *expr, znode *offset, zend_bool is_ref TSRMLS_DC)    //    zend_compile.c 3348行
 
 
 
==>case 380:    { zend_do_add_array_element(&(yyval), &(yyvsp[(3) - (3)]), NULL, 0 TSRMLS_CC); }//    zend_language_parser.c  4684行
    ==>void zend_do_add_array_element(znode *result, znode *expr, znode *offset, zend_bool is_ref TSRMLS_DC)    //    zend_compile.c 3371行

在代码跟踪过程中,发现当PHP的源码生成语法树时,遇到array和第一个数字时是没有任何有关数组的操作的,当解析到1后面的逗号时才会调用zend_do_init_array函数,创建数组

以上只是一个跟踪的过程,对于代码没有解释,由于自身水平有限,对于词法分析和语法分析了解不多,
另:如果PHP程序的最后一行代码后面没有回车换行,在读取源码文件时会看到一堆乱码

待续…

PHP源码阅读笔记四:count函数

PHP源码阅读笔记之count函数

在一些面试或考试中经常会看到count函数的身影,于是一探其究竟

对于非数组的count处理
在其代码中可以看到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
PHP_FUNCTION(count)
{
    zval *array;
    long mode = COUNT_NORMAL;
    if (zend_parse_parameters (ZEND_NUM_ARGS() TSRMLS_CC, "z|l", &array, &mode) == FAILURE)
        return;
    switch (Z_TYPE_P(array)) {
        case IS_NULL:    //    空值处理
            RETURN_LONG(0);
            break;
        case IS_ARRAY:    //    处理数组,包括其是递归
            RETURN_LONG (php_count_recursive (array, mode TSRMLS_CC));
            break;
        case IS_OBJECT: {    
    #ifdef HAVE_SPL
        /* it the object implements Countable we call its count() method */
            zval *retval;
 
            if (Z_OBJ_HT_P(array)->get_class_entry && instanceof_function(Z_OBJCE_P(array), spl_ce_Countable TSRMLS_CC)) {
                zend_call_method_with_0_params(&array, NULL, NULL, "count", &retval);
                if (retval) {
                    convert_to_long_ex(&retval);
                    RETVAL_LONG(Z_LVAL_P(retval));
                    zval_ptr_dtor(&retval);
                }
            return;
            }
    #endif
        /* if not we return the number of properties (not taking visibility into account) */
            if (Z_OBJ_HT_P(array)->count_elements) {
                RETVAL_LONG(1);
                if (SUCCESS == Z_OBJ_HT(*array)->count_elements(array, &Z_LVAL_P(return_value) TSRMLS_CC)) {
                    return;    
              }
            }
        }
    default:    //    其它情况,如考试中常见的字符串等
        RETURN_LONG(1);
        break;
    }
}

在手册中有说明:
此函数对于对象,如果安装了 SPL,可以通过实现 Countable 接口来调用 count()。该接口只有一个方法 count(),此方法返回 count() 函数的返回值。

对于数组长度的统计,如果mode使用默认值(0),则仅显示第一维数组的长度
如下所示代码

1
2
3
4
5
6
7
8
$arr = array(1, 2, 3);
$arr2 = array($arr, $arr);
echo count($arr2), '<br />';
echo count($arr2, 1);
/*   输出结果:
2
8
*/

如果只是显示第一维数组,则直接返回保存数组的HashTable的nNumOfElements属性即可
其实现的代码为:

1
2
3
4
5
6
7
8
9
//    php_count_recursive函数 array.c 251行
cnt = zend_hash_num_elements(Z_ARRVAL_P(array));
 
ZEND_API int zend_hash_num_elements(HashTable *ht)
{
    IS_CONSISTENT(ht);
 
    return ht->nNumOfElements;
}

如果想直接运行HashTable的一些简单操作,猛击PHP源码中HashTable的简单示例
如果想查看了解数组存储或遍历的方式,猛击鸟哥的深入理解PHP之数组(遍历顺序)

如果启动了递归,即使用count($arr, 1)
则程序会递归调用php_count_recursive函数
针对HashTable,程序会遍历包含这一维数组的双向链表,然后再递归遍历每个节点存储的pData,真到遍历完所有的节点

【感受】
HashTable很强大,如果要计算数组的长度还是调用count函数,重复发明轮子会是一种吃力不讨好的事情!