怎么样获取PHP变量的变量名之扩展实现
很长时间没有更新博客了. 一来最近工作比较忙,没有时间好好研究问题, 二是觉得没有很好的材料可以写. 也有一些没有彻底研究透的问题,写着写着没有了头绪,都扔在了草稿箱里了. 这次顺带也要更新一下博客的模版了, 现在的这个模版主体有点窄,不适合阅读. 我这个博客现在,以后主要还是写一些技术的东西.还是换一个眼睛友好的主题吧.
本文要解决的是从去年就一直在考虑的一个PHP的问题: 怎么样获取PHP变量的变量名. 一直以来都没有好好的研究.最近断断续续的开始看PHP源代码.并尝试解决. 直到两星期前把问题都解决了才开始把这些东西都记下来.
如果有兴趣先看看这个功能是怎么实现的. 可以先点击这里下载代码.
1.问题:能在PHP中获取php变量本身的名字么?
一年多前做一个模版引擎的什么时候有了这样一个需求: 获取变量的变量名. 比如:
1
2 3 4 5
|
$some_variable_name = "blahblah"; //... echo get_var_name($some_variable_name); // 这里期望输出"some_variable_name"; ?> |
如果你也有这样的需求. 你对需求的理解绝对有问题. 不过后来想想这需求虽然不合理. 但是如果我偏有这样不合理的需求, 我有办法真的能满足么?
2.有哪些解决方法
在遇到这个问题之前,没有太系统的去看过PHP的C实现. 从问题提出到目前为止,我想到了如下几种方法:
1
2 3 4
|
function get_var_name($var) {
// 但是... 我怎么的到变量的名字呢... // echo ? How To? } |
用过$GLOBALS变量的人应该知道可以通过 $GLOBALS[\'var\']的方式来获取变量$var的值. 这样的话,我应该就能这样实现了
1
2 3 4 5 6 7
|
function get_var_name($var) {
foreach($GLOBALS as $var_name => $var_value) { if($var === $var_value) { return $var_name; } } } |
这个是不可行的. 首先, 这个方法只能返回全局作用域内的变量. 如果在函数体内调用这个函数会有问题. 并且通过值比较也完全不可靠.
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
|
struct _zend_executor_globals {
zval **return_value_ptr_ptr;
zval uninitialized_zval; zval *uninitialized_zval_ptr;
zval error_zval; zval *error_zval_ptr;
zend_ptr_stack arg_types_stack;
/* symbol table cache */ HashTable *symtable_cache[SYMTABLE_CACHE_SIZE]; HashTable **symtable_cache_limit; HashTable **symtable_cache_ptr;
zend_op **opline_ptr;
HashTable *active_symbol_table; // 当前作用域的变量符号表 HashTable symbol_table; /* main symbol table */ // 全局符号表
HashTable included_files; /* files already included */
//.. }; |
比如模块提供一个叫做get_var_name()的函数来获取变量名字. 如果大家有写过PHP扩展的经验的话,应该看过类似如下的函数实现(取自php json扩展$PHP_SRC/ext/json/json.c):
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
/* {{{ proto string json_encode(mixed data [, int options])
Returns the JSON representation of a value */ static PHP_FUNCTION(json_encode) { zval *parameter; smart_str buf = {0}; long options = 0;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|l", ¶meter, &options) == FAILURE) { // 这里将传入的参数取出来. 参考文档 http://www.php.net/manual/en/internals2.funcs.php return; }
php_json_encode(&buf, parameter, options TSRMLS_CC);
ZVAL_STRINGL(return_value, buf.c, buf.len, 1);
smart_str_free(&buf); } /* }}} */ |
这中实现在函数体内可以通过zend_parse_parameter的方式来获取传递进来的变量, 但这样只能获取到变量的值. 却无法得到其他更多的信息.,我们往低层看看在PHP中函数是怎么调用,参数是怎么传递的.
3.1 PHP中函数的调用
在研究函数怎么调用之前, 我们需要看看PHP代码是怎么执行的.
大致可以分为2个步骤:
- 词法分析,语法分析然后编译成opcode
- 执行opcode
PHP函数的执行也只能在opcode执行阶段执行.
这里之前要介绍一个查看OPCODE的绝佳工具 vld(http://pecl.php.net/package/vld)
装好这扩展。可以在命令行下查看php脚本编译后的opcode
我们看看下面这个php脚本被编译后opcode是什么样的.
1
2 3 4 5 6 7 8 9 10
|
也可以再增加一个参数 -dvld.verbosity=3, 这样将会显示更多的信息.
它被编译为上面的10条opcode命令.
op的名称一看也能看出什么意思. .. 其中以 “!”开头的数字表示编译后的变量,, 以”~”开头的变量表示零时变量.
上面可可以看出如果函数调用存在参数的话,在DO_FCALL之前会执行SEND_VAR 或者 SEND_VAR_NO_REF指令。并且这些指令后面操作的是编译过变量或者一个临时变量.
在PHP中调用时我们是可以访问到DO_FCALL这个操作的opcode信息 。可以通过 EG(active_opline_ptr) 获取到当前指令
PHP中存在一系列*G宏, EG 则为在执行opcode时的全局变量。
见文件: $PHP_SRC/Zend/zend_globals.h
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
|
struct _zend_executor_globals {
// ... zend_op **opline_ptr; // 指向当前正在执行的zend_op对象 HashTable *active_symbol_table; HashTable symbol_table; /* main symbol table */ HashTable included_files; /* files already included */ jmp_buf *bailout; int error_reporting; int orig_error_reporting; int exit_status; zend_op_array *active_op_array; // ... };
struct _zend_op_array { /* Common elements */ zend_uchar type; char *function_name; zend_class_entry *scope; zend_uint fn_flags; union _zend_function *prototype; zend_uint num_args; zend_uint required_num_args; zend_arg_info *arg_info; zend_bool pass_rest_by_reference; unsigned char return_reference; /* END of common elements */
zend_bool done_pass_two;
zend_uint *refcount;
zend_op *opcodes; // zend_op数组. zend_uint last, size;
zend_compiled_variable *vars; // 所有编译后的变量信息Since PHP5.1 这是一个数组 int last_var, size_var; // last_var 最后一个编译变量的索引
// ... }; |
当前执行的op_array中保存所有编译变量的信息, 再看看zend_compiled_variable的结构吧。
1
2 3 4 5
|
typedef struct _zend_compiled_variable {
char *name; int name_len; ulong hash_value; } zend_compiled_variable; |
这正是我想获取的变量名称.
我们可以通过全局变量EG(opline_ptr)指针获取到当前执行的zend_op, zend_op的结构如下:
1
2 3 4 5 6 7 8 9
|
struct _zend_op {
opcode_handler_t handler; // 处理该OPCODE的处理函数 znode result; // 该opcode执行的结果 znode op1; // 有的opcode需要1个,有的需要两个操作数。 znode op2; ulong extended_value; uint lineno; zend_uchar opcode; // 该opcode的值 见$PHP_SRC/Zend/zend_vm_opcodes.h }; |
这也就是我们函数调用时执行的opcode.我们现在可以获取到DO_FCALL时的opcode, 通过VLD察看opcode工具很容易就知道函数调用之前,如果函数有参数的话,在DO_FCALL之前一定有SEND_VAR或者SEND_VAR_NO_REF指令, 指针后退一个则一定是指向SEND_VAR或SEND_VAR_NO_REF指令的。 这样的话我们根据DO_FCALL获取到的zend_op指令后退不久可以获取SEND_VAR指令了么. SEND_VAR指令会操作compiled_var,这样我们就能得到变量的信息了..
看看znode都有哪些信息:
1
2 3 4 5 6 7 8 9 10 11 12 13 14
|
typedef struct _znode {
int op_type; union { zval constant; zend_uint var; // 这个var就是当前变量在zend_op_array.vars 中的compiled_variable数组中的索引.不过这个索要并不是字面上的. 详情请看最后的代码实现. zend_uint opline_num; /* Needs to be signed */ zend_op_array *op_array; zend_op *jmp_addr; struct { zend_uint var; /* dummy */ zend_uint type; } EA; } u; } znode; |
如在在上面的注释. 通过获取znode.u.var的值就可以获取到变量的信息了.
这样的话.程序的实现也就简单了.
下面是实现:
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
|
/* {{{ get_var_name
* * 这个扩展要求PHP >= 5.1 * 因为依赖PHP 5.1引入的compiled variable * * 在PHP空间导出一个get_var_name函数. * echo get_var_name($var_name); // expect: var_name * echo get_var_name($lineno=100); // expect: lineno */ PHP_FUNCTION(get_var_name) { int len; char *strg = "";
if(ZEND_NUM_ARGS() < 1) { return; }
/* 显示所有的编译变量 int i; zend_compiled_variable *vars = EG(active_op_array)->vars; for(i=0; i < EG(active_op_array)->last_var; ++i) { // last_var 最后一个编译变量的索引 spprintf(&strg, 0, "%s\\nVar:%s\\n", strg, EG(active_op_array)->vars[i].name); ++vars; } */
zend_op *pre_opline_ptr = *EG(opline_ptr); pre_opline_ptr--;
// 支持这类的调用: get_var_name($a="VALUE"); // expect: a // 这里增加在赋值的情况下也能正确返回变量的名字的处理方法, 如果方法参数是赋值的的话, 编译的OPCODE 中SEND_VAR之前将会 // 有一个ZEND_ASSIGN 操作, 并且ZEND_ASSIGN操作的返回值被使用.比如: $c = $d + 1; $d + 1的返回值就被使用了. 就可以确认 // 是前面的调用方式 zend_op *pre_pre_online_ptr = pre_opline_ptr - 1; if(pre_pre_online_ptr && pre_pre_online_ptr->opcode == ZEND_ASSIGN && !(pre_pre_online_ptr->result.u.EA.type & EXT_TYPE_UNUSED)) { // 通过赋值之前的zend_op来获取变量信息 pre_opline_ptr = pre_pre_online_ptr; }
int index; // 比如get_var_name($name); 这时SEND_VAR OPCODE的op1操作数类型就是IS_CV 也就是IS Compiled Variable // 只有compiled variable才是直接存储索引的. PHP >= 5.1 if(pre_opline_ptr->op1.op_type == IS_CV) { index = pre_opline_ptr->op1.u.var;
} else { // 请参考VLD的源代码 $VLD_SRC/srm_oparray.c LINE:320 vld_dump_znode函数 index = pre_opline_ptr->op1.u.var / sizeof(temp_variable); }
zend_compiled_variable var = EG(active_op_array)->vars[index]; len = spprintf(&strg, 0, "%s", strg, var.name);
RETURN_STRINGL(strg, len, 0); } /* }}} */ |
建议继续学习:
- 怎样获取PHP变量的变量名之PHP实现 (阅读:2726)
- PHP的可变变量名 (阅读:2261)
- 定制PHP语法获取PHP变量的变量名 (阅读:2253)
- 让 Lua 支持中文变量名 (阅读:1549)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:reeze 来源: Zen Space [睿]
- 标签: 变量名
- 发布时间:2011-02-23 22:27:26
- [46] 界面设计速成
- [40] 视觉调整-设计师 vs. 逻辑
- [40] Oracle MTS模式下 进程地址与会话信
- [38] android 开发入门
- [38] IOS安全–浅谈关于IOS加固的几种方法
- [36] 程序员技术练级攻略
- [36] 如何拿下简短的域名
- [35] 图书馆的世界纪录
- [35] 【社会化设计】自我(self)部分――欢迎区
- [32] 读书笔记-壹百度:百度十年千倍的29条法则