【PHP源码】实现简单自动加载函数扩展

前言:

没有写过PHP相关的文章,今天是第一篇。写的不好的地方请见谅,多多批评。

 

PHP版本: php-7.1.23

 

文章涉及源码:

https://github.com/Diamonds-ZhaoYu/php_auto_function

 

php源码:

https://github.com/php/php-src

 

开篇:

本章内容实现的方法为4个,如图:

zhaoyu_set_autofunction: 设置自动加载方法;

zhaoyu_get_all_global_vars:获得运行时所有的全局变量,返回数组。

zhaoyu_get_all_classes: 获得运行时所有的类,返回数组。

zhaoyu_get_all_functions: 获得运行时所有的方法名,返回数组。

基础知识:

一.生成一个扩展:

首先我们要下载对应的php源码,先编译好对应的php。然后我们找到源码中的ext目录,ext目录下有一个ext_skel,这个文件是我们用于生成php扩展的脚骨架文件。

我们可以输入:

#./ext_skel -h

看一下对应的ext_skel的命令参数信息,我们使用--extname=module名就可以构建一个自己的扩展。

接下来我们输入:

#./ext_skel --extname=zhaoyu

我们来一下生成扩展的具体信息:

 

config.m4 和 config.w32用于编译使用。

zhaoyu.c是我们生成的php扩展的入口文件,内部包含了扩展的生命周期。

php_zhaoyu.h头文件主要用于我们的一些结构体定义和宏定义,骨架框架生成也包含了一些。

 

二.编译一个扩展:

我的php安装的目录:

/data/soft/php7.1.23/

 

执行phpize

#/data/soft/php7.1.23/bin/phpize

phpize是一个运行脚本,主要作用是检测php的环境还有就是在特定的目录生成相应的configure文件,这样make install之后,生产.so文件才会自动加载到php扩展下。

 

初始化编译配置

# cd /Users/tal/Desktop/code-source/php-7.1.23/ext/zhaoyu/
#./configure --with-php-config=/data/soft/php7.1.23/bin/php-config 

初始化配置时,可能会看到一些初始化检查错误,这时可以根据对应数据的检测信息安装对应的支持库。

接下来编译扩展就可以:

#make
#make install

三.配置一个扩展:

复制一个php.ini文件:

#cp /Users/tal/Desktop/code-source/php-7.1.23/php.ini-development  /data/soft/php7.1.23/lib/php.ini

/data/soft/php7.1.23/lib/目录在哪里找,可以通过

 #/data/soft/php7.1.23/bin/php -ini |grep '(php.ini) Path'

查看php.ini的目录在什么地方。

 

然后就可以用vim打开/data/soft/php7.1.23/lib/php.ini在最底行加入:

extension=zhaoyu.so

四.EG和CG结构介绍:

 

EG宏对应的executor_globals,Zend执行器相关的全局变量。

Zend引擎在执行opcode的过程中,需要记录一些状态。如当前执行的类,加载了哪些文件。

 

CG宏对应的compiler_globals,Zend编译器相关的全局变量。

php在转换opcode的过程汇总需要保存一些信息,这些信息就保存在CG全局变量中。

/* Compiler */
#ifdef ZTS
# define CG(v) ZEND_TSRMG(compiler_globals_id, zend_compiler_globals *, v)
#else
# define CG(v) (compiler_globals.v)
extern ZEND_API struct _zend_compiler_globals compiler_globals;
#endif
ZEND_API int zendparse(void);


/* Executor */
#ifdef ZTS
# define EG(v) ZEND_TSRMG(executor_globals_id, zend_executor_globals *, v)
#else
# define EG(v) (executor_globals.v)
extern ZEND_API zend_executor_globals executor_globals;
#endif

zend_executor_globals 结构体

struct _zend_executor_globals {
	zval uninitialized_zval;
	zval error_zval;

	/* 全局变量缓存 */
	zend_array *symtable_cache[SYMTABLE_CACHE_SIZE];
	zend_array **symtable_cache_limit;
	zend_array **symtable_cache_ptr;

	zend_array symbol_table;		/* 全局变量hashtable */

	HashTable included_files;	/* 已经加载的文件 */

	JMP_BUF *bailout;

	int error_reporting;
	int exit_status;

	HashTable *function_table;	/* 方法hashtable */
	HashTable *class_table;		/* 类hashtable */
	HashTable *zend_constants;	/* 常量hashtable */
   ...
	void *reserved[ZEND_MAX_RESERVED_RESOURCES];
};

zend_compiler_globals 结构体

struct _zend_compiler_globals {
	zend_stack loop_var_stack;

	zend_class_entry *active_class_entry;

	zend_string *compiled_filename;

	int zend_lineno;

	zend_op_array *active_op_array;

	HashTable *function_table;	/* function symbol table */
	HashTable *class_table;		/* class table */

	HashTable filenames_table;

	HashTable *auto_globals;

	zend_bool parse_error;
	zend_bool in_compilation;
	zend_bool short_tags;

	zend_bool unclean_shutdown;

	zend_bool ini_parser_unbuffered_errors;

	zend_llist open_files;

	struct _zend_ini_parser_param *ini_parser_param;

	uint32_t start_lineno;
	zend_bool increment_lineno;

	zend_string *doc_comment;
	uint32_t extra_fn_flags;

	uint32_t compiler_options; /* set of ZEND_COMPILE_* constants */

	HashTable const_filenames;

	zend_oparray_context context;
	zend_file_context file_context;

	zend_arena *arena;

	zend_string *empty_string;
	zend_string *one_char_string[256];
	zend_string **known_strings;
	uint32_t    known_strings_count;

	HashTable interned_strings;

	const zend_encoding **script_encoding_list;
	size_t script_encoding_list_size;
	zend_bool multibyte;
	zend_bool detect_unicode;
	zend_bool encoding_declared;

	zend_ast *ast;
	zend_arena *ast_arena;

	zend_stack delayed_oplines_stack;

#ifdef ZTS
	zval **static_members_table;
	int last_static_member;
#endif
};

源码解析:

 

zhaoyu_get_all_classes方法实现:

PHP_FUNCTION(zhaoyu_get_all_classes)
{

	array_init(return_value);

	int num = EG(class_table)->nNumOfElements;

	for (int i = 0; i < num; i++)
	{
		Bucket *arr = (EG(class_table)->arData + i);
		add_next_index_string(return_value,arr->key->val);
	}
}

在这个方法是不是发现这个方法内没有返回值,其实return_value就是返回值。

我们来看看方法体返回值的宏,在zend_API.h

...
#define RETVAL_BOOL(b)					ZVAL_BOOL(return_value, b)
#define RETVAL_NULL() 					ZVAL_NULL(return_value)
#define RETVAL_LONG(l) 					ZVAL_LONG(return_value, l)
#define RETVAL_DOUBLE(d) 				ZVAL_DOUBLE(return_value, d)
#define RETVAL_STR(s)			 		ZVAL_STR(return_value, s)
#define RETVAL_INTERNED_STR(s)	 		ZVAL_INTERNED_STR(return_value, s)
#define RETVAL_NEW_STR(s)		 		ZVAL_NEW_STR(return_value, s)
#define RETVAL_STR_COPY(s)		 		ZVAL_STR_COPY(return_value, s)
#define RETVAL_STRING(s)		 		ZVAL_STRING(return_value, s)
#define RETVAL_STRINGL(s, l)		 	ZVAL_STRINGL(return_value, s, l)
#define RETVAL_EMPTY_STRING() 			ZVAL_EMPTY_STRING(return_value)
#define RETVAL_RES(r)			 		ZVAL_RES(return_value, r)
#define RETVAL_ARR(r)			 		ZVAL_ARR(return_value, r)
#define RETVAL_OBJ(r)			 		ZVAL_OBJ(return_value, r)
#define RETVAL_ZVAL(zv, copy, dtor)		ZVAL_ZVAL(return_value, zv, copy, dtor)
#define RETVAL_FALSE  					ZVAL_FALSE(return_value)
#define RETVAL_TRUE   					ZVAL_TRUE(return_value)

#define RETURN_BOOL(b) 					{ RETVAL_BOOL(b); return; }
#define RETURN_NULL() 					{ RETVAL_NULL(); return;}
#define RETURN_LONG(l) 					{ RETVAL_LONG(l); return; }
#define RETURN_DOUBLE(d) 				{ RETVAL_DOUBLE(d); return; }
#define RETURN_STR(s) 					{ RETVAL_STR(s); return; }
#define RETURN_INTERNED_STR(s)			{ RETVAL_INTERNED_STR(s); return; }
#define RETURN_NEW_STR(s)				{ RETVAL_NEW_STR(s); return; }
#define RETURN_STR_COPY(s)				{ RETVAL_STR_COPY(s); return; }
#define RETURN_STRING(s) 				{ RETVAL_STRING(s); return; }
#define RETURN_STRINGL(s, l) 			{ RETVAL_STRINGL(s, l); return; }
#define RETURN_EMPTY_STRING() 			{ RETVAL_EMPTY_STRING(); return; }
#define RETURN_RES(r) 					{ RETVAL_RES(r); return; }
#define RETURN_ARR(r) 					{ RETVAL_ARR(r); return; }
#define RETURN_OBJ(r) 					{ RETVAL_OBJ(r); return; }
#define RETURN_ZVAL(zv, copy, dtor)		{ RETVAL_ZVAL(zv, copy, dtor); return; }
#define RETURN_FALSE  					{ RETVAL_FALSE; return; }
#define RETURN_TRUE   					{ RETVAL_TRUE; return; }
...

可以看到我们的返回值其实是在设置return_value。那么我们看看return_value是从哪里来的。

可以看看PHP_FUNCTION(zhaoyu_get_all_classes)的实现:

对应宏解析出来为

void zif_zhaoyu_get_all_classes(zend_execute_data *execute_data, zval *return_value)

PHP_FUNCTION(zhaoyu_get_all_classes)对应的宏:

php.h

#define PHP_FN					ZEND_FN
#define PHP_MN					ZEND_MN
#define PHP_NAMED_FUNCTION		ZEND_NAMED_FUNCTION
#define PHP_FUNCTION			ZEND_FUNCTION
#define PHP_METHOD  			ZEND_METHOD

 

zend_API.h

#define ZEND_FN(name) zif_##name
#define ZEND_MN(name) zim_##name
#define ZEND_NAMED_FUNCTION(name)		void name(INTERNAL_FUNCTION_PARAMETERS)
#define ZEND_FUNCTION(name)				ZEND_NAMED_FUNCTION(ZEND_FN(name))
#define ZEND_METHOD(classname, name)	ZEND_NAMED_FUNCTION(ZEND_MN(classname##_##name))
#define ZEND_FN(name) zif_##name
#define 

zend.h

#define INTERNAL_FUNCTION_PARAMETERS zend_execute_data *execute_data, zval *return_value

接下来我们继续看int num = EG(class_table)->nNumOfElements; 这段代码其实是获取当前hash表已有的元素个数。

EG(class_table)对应的是一个HashTable结构,HashTable对应的是zend_array结构。

 

zend_types.h

typedef struct _Bucket {
    zval              val;
    zend_ulong        h;                /* hash value (or numeric index)   */
    zend_string      *key;              /* string key or NULL for numerics */
} Bucket;

struct _zend_array {
    zend_refcounted_h gc;
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                    zend_uchar    flags,
                    zend_uchar    nApplyCount,
                    zend_uchar    nIteratorsCount,
                    zend_uchar    reserve)
        } v;
        uint32_t flags;
    } u;   //主要还是起辅助作用,比如flags用来设置散列表的一些属性是否持久化,是否初始化等
    uint32_t          nTableMask;      //哈希值计算掩码,等于nTableSize的负值(nTableMask = ~nTableSize + 1)
    Bucket           *arData;          //存储元素数组,指向第一个Bucket
    uint32_t          nNumUsed;        //已使用Bucket个数
    uint32_t          nNumOfElements;  //哈希表已有元素个数
    uint32_t          nTableSize;      //哈希表总大小,为2的n次方
    uint32_t          nInternalPointer; //当前遍历指针
    zend_long         nNextFreeElement; //下一个可用的数值索引,如:$arr[] = 1;$arr["a"] = 2;$arr[] = 3;  则nNextFreeElement = 2;
    dtor_func_t       pDestructor;      //西沟函数,在删除或覆盖某个元素时,调用该函数,可以对旧元素进行清理
};

(EG(class_table)->arData + i); 是为了遍历数组中的数据,arData是用于存储元素数组,因为它在存取类信息时是顺序存储,所以我们通过指针+1就等于下一个数组的Bucket结构的数据。

而add_next_index_string(return_value,arr->key->val); 是将数组中的键值名存储在返回变量return_value中。

操作数组的函数:

zend_API.h

/** 通过key键值添加一个数组数据 */
ZEND_API int add_assoc_long_ex(zval *arg, const char *key, size_t key_len, zend_long n);
ZEND_API int add_assoc_null_ex(zval *arg, const char *key, size_t key_len);
ZEND_API int add_assoc_bool_ex(zval *arg, const char *key, size_t key_len, int b);
ZEND_API int add_assoc_resource_ex(zval *arg, const char *key, size_t key_len, zend_resource *r);
ZEND_API int add_assoc_double_ex(zval *arg, const char *key, size_t key_len, double d);
ZEND_API int add_assoc_str_ex(zval *arg, const char *key, size_t key_len, zend_string *str);
ZEND_API int add_assoc_string_ex(zval *arg, const char *key, size_t key_len, char *str);
ZEND_API int add_assoc_stringl_ex(zval *arg, const char *key, size_t key_len, char *str, size_t length);
ZEND_API int add_assoc_zval_ex(zval *arg, const char *key, size_t key_len, zval *value);

#define add_assoc_long(__arg, __key, __n) add_assoc_long_ex(__arg, __key, strlen(__key), __n)
#define add_assoc_null(__arg, __key) add_assoc_null_ex(__arg, __key, strlen(__key))
#define add_assoc_bool(__arg, __key, __b) add_assoc_bool_ex(__arg, __key, strlen(__key), __b)
#define add_assoc_resource(__arg, __key, __r) add_assoc_resource_ex(__arg, __key, strlen(__key), __r)
#define add_assoc_double(__arg, __key, __d) add_assoc_double_ex(__arg, __key, strlen(__key), __d)
#define add_assoc_str(__arg, __key, __str) add_assoc_str_ex(__arg, __key, strlen(__key), __str)
#define add_assoc_string(__arg, __key, __str) add_assoc_string_ex(__arg, __key, strlen(__key), __str)
#define add_assoc_stringl(__arg, __key, __str, __length) add_assoc_stringl_ex(__arg, __key, strlen(__key), __str, __length)
#define add_assoc_zval(__arg, __key, __value) add_assoc_zval_ex(__arg, __key, strlen(__key), __value)

/** 通过数组索引添加一个类型数组数据 */
ZEND_API int add_index_long(zval *arg, zend_ulong idx, zend_long n); 
ZEND_API int add_index_null(zval *arg, zend_ulong idx);  
ZEND_API int add_index_bool(zval *arg, zend_ulong idx, int b);
ZEND_API int add_index_resource(zval *arg, zend_ulong idx, zend_resource *r); 
ZEND_API int add_index_double(zval *arg, zend_ulong idx, double d);   
ZEND_API int add_index_str(zval *arg, zend_ulong idx, zend_string *str); 
ZEND_API int add_index_string(zval *arg, zend_ulong idx, const char *str); 
ZEND_API int add_index_stringl(zval *arg, zend_ulong idx, const char *str, size_t length);  
ZEND_API int add_index_zval(zval *arg, zend_ulong index, zval *value); 

/** 顺序添加一个类型数组数据 */
ZEND_API int add_next_index_long(zval *arg, zend_long n);  
ZEND_API int add_next_index_null(zval *arg); 
ZEND_API int add_next_index_bool(zval *arg, int b); 
ZEND_API int add_next_index_resource(zval *arg, zend_resource *r); 
ZEND_API int add_next_index_double(zval *arg, double d);  
ZEND_API int add_next_index_str(zval *arg, zend_string *str); 
ZEND_API int add_next_index_string(zval *arg, const char *str); 
ZEND_API int add_next_index_stringl(zval *arg, const char *str, size_t length);
ZEND_API int add_next_index_zval(zval *arg, zval *value);

zhaoyu_set_autofunction方法实现:

PHP_FUNCTION(zhaoyu_set_autofunction)
{
	zend_string *name;
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"S", &name) == FAILURE) {
		RETURN_NULL();
	}

	zend_function *spl_func_ptr = zend_hash_find_ptr(EG(function_table), name);
	if (spl_func_ptr == NULL)
	{
		php_printf("not found %s function! \n" ,name);
		zend_string_release(name);
		RETURN_NULL();
	}

	EG(autoload_func) =	spl_func_ptr;
	zend_string_release(name);
	RETURN_TRUE;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值