配色: 字号:
如何执行字符串的PHP代码
2016-12-12 | 阅:  转:  |  分享 
  
如何执行字符串的PHP代码

最近因项目需要,引出一个议题:如何执行字符串的php代码(php和html混写)。

注:传统情况下,php代码存储在文件中,直接运行文件即可。以下讨论的情况是,如果php代码是从数据库中获取到,那么要如何运行?



最直观的方案

将字符串代码写到临时文件,然后在项目中include该文件,执行完成再删除这个临时文件

systemexec之类的系统函数

php函数eval(会不会有安全问题?)

进一步的瞎想

把字符串代码当做参数,传入到php的cli或者php-fpm中运行

重新定义include,让include可以直接操作字符串

几种思路的验证

1:写临时文件,然后include文件。豪无疑问,可行。那么岂不是每次都要写文件,每个请求都要写一次文件。好吧,总有办法解决,缓存+过期验证之类的,但总感觉这方案不够专业。。



2:systemexec之类的函数。稍微思考下就会明白,这类函数是执行系统命令,不是运行php代码



3:eval函数,手册上写着:



Caution:Theeval()languageconstructisverydangerousbecauseitallowsexecutionofarbitraryPHPcode.Itsusethusisdiscouraged.Ifyouhavecarefullyverifiedthatthereisnootheroptionthantousethisconstruct,payspecialattentionnottopassanyuserprovideddataintoitwithoutproperlyvalidatingitbeforehand.



4:php-fpmcli模式是否有办法解决此类问题

初步设想是把字符串代码传到fpm、cli模式中,等待返回结果。

但有一个硬伤,需要执行的字符串代码是有上下文的。比如字符串代码中使用了一个变量$_GET,如果把这个字符串代码传到fpm中,而$_GET变量并没有传过去,那代码还是没办法正常运行。



5:include能不能直接操作字符串

好吧,前面的4种方法好像都不太满意,那就深挖一下这个思路吧



首先,php中的include是什么原理?

并没有去看过源码,猜一下吧,1:读取文件(fopen,fread之类的)2:解析php语法3:运行代码



那么,如果可以让fopen,fread操作字符串,也许这个问题就解决了?

设想:把字符串转换为一个对象或者流,提供fopen,fread接口。首先想到php的SPL中应该有此类接口,查php官方手册,找到php手册中关于”支持的协议与封装协议“章节(同事也提过使用自定义协议的方式),以下为测试的最简demo:(封装自定义协议,使用include直接操作字符串)




//业务需要:从数据库中读出字符串的php代码

functionmysql_get($id){

return''
echo"contextValue:".$contextName."\n";

echo"hello$i\n";

'';

}

//自定义协议

classVariableStream{

private$string;

private$position;

publicfunctionstream_open($path,$mode,$options,&$opened_path){

$url=parse_url($path);

$id=$url["host"];



//根据ID到数据库中取出php字符串代码

$this->string=mysql_get($id);

$this->position=0;

returntrue;

}

publicfunctionstream_read($count){

$ret=substr($this->string,$this->position,$count);

$this->posiwww.baiyuewang.nettion+=strlen($ret);

return$ret;

}

publicfunctionstream_eof(){}

publicfunctionstream_stat(){}

}



stream_wrapper_register("var","VariableStream");



//上下文变量

$contextName="1000";

//include字符串php代码。(php代码是从数据库中读出来,这里传入的199是数据库的主键ID)

include("var://199");



//修改上下文变量

$contextName="2000";

//引入另一个字符串php代码

include("var://299");

OK,终于找到一种解决思路。再继续思考,既然我们希望最终的展示是include这种方式,include的内部是fopen之类的系统函数,那么fopen除了支持自定义协议之外,还支持哪些呢?

手册中,fopen的第一个参数$filename,可以是文件名,也可以是"scheme://..."的格式,第二种格式就是上面说的自定义协议方式。再继续查看相关的东西,发现SplFileInfo、stream_context_create,不过并不能解决问题。



总结

现在已经有3种方式可以做成这个事情,那么哪种方式更好

1:写临时文件,加缓存,直接include

2:eval,官方手册上说这个函数有安全问题

3:自定义协议,直接include



首先排除方法1,原因1:缓存文件会增加硬盘I/O。原因2:不够专业(这不是小问题)

至于eval提到的安全问题,仔细阅读手册上写的那段话后,发现他只是提示你现在正在运行一段项目代码以外的代码,请多小心。这样看来,方法2并没有比方法3更危险。



选取标准,如果项目中只有一个很小的功能需要执行php字符串,那直接使用eval即可

如果项目中有大量的此类需求,封装一个自定义协议会很方便。项目中的引用会是这样的:include("protocolName://param");

献花(0)
+1
(本文系thedust79首藏)