如何执行字符串的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");
|
|