什么是字符串表达式?即,将咱们常见的表达式文本写到了字符串中,如:
"$age >= 20"
,$age
的值是动态的整型变量。php什么是字符串表达式计算?即,咱们须要一段程序来执行动态的表达式,如给定一个含表达式的字符串变量并计算其结果,而表达式字符串是动态的,好比为客户A执行的表达式是
$orderCount >= 10
,而为客户B执行的表达式是$orderTotal >= 1000
。html场景在哪儿?同一份程序具备彻底通用性,但差别就其中一个表达式而已,那么咱们须要将其抽象出来,让表达式变成动态的、可配置的、或可生成的。git
方案一:eval 函数
eval
函数多是咱们第一个想到的方案,也是最简单直接的方案。咱们来试验下:github
$a = 10; var_dump(eval('return $a > 5;')); // 输出: // bool(true)
嗯~彻底能知足咱们的需求,由于 eval
函数执行的 PHP 表达式,只要字符串内表达式符合 PHP 语法就行。shell
但需注意的是,eval
函数可执行任意 PHP 代码,也就意味着权限大、风险高、不安全。若是你的字符串表达式来自于外部输入,那务必注意了请自行作好安全检查和过滤,并考虑风险。固然,执行的是外部输入表达式,很是不建议使用此函数。express
方案二:include 临时文件
如何实现?将字符串表达式写入一个临时文件,而后 include
这个临时文件,执行完成后再删除这个临时文件。安全
方案依然很简单。须要考虑的有:app
- 临时文件会不少,一个请求就有不少个,文件的过时和删除务必考虑在内
- 文件的读写,也就牵扯到了磁盘 IO,那性能一定受到严重影响
那这个方案咱们还采用吗?函数
方案三:assert 断言
其实 assert
作不到字符串表达式的计算,但提出来也算个猜测,由于能实现 PHP 表达式是否合法的校验。post
下例演示了如何验证某个字符串表达式是否为合法的 PHP 表达式:
try { assert('a +== 1'); } catch (Throwable $e) { echo $e->getMessage(), "\n"; }
运行结果:
Failure evaluating code: a +== 1
可依然面临一个问题,那就是安全性,由于与 eval
同样能执行任意代码。因此,从 PHP 7.2 开始就不能够再执行字符串类型的表达式了。关于 PHP assert 断言,可参考 你所不知的 PHP 断言(assert)
方案四:system/exec 函数
system、exec、proc_open、shell_exec、passthru 等系列函数,本质上都是执行外部命令或脚本,以达到执行 PHP 代码的效果,与 include 实现相似,虽能实现但不安全。
system('php -r "echo 1 + 2;"'); echo exec('php -r "echo 1 + 2;"');
方案五:create_function 函数
create_function
函数是匿名函数的前生临时替代品,虽然现今还未废弃。做用是什么呢?容许用字符串建立一个 lambda
风格的匿名函数。
函数语法定义:
create_function ( string $args , string $code ) : string
使用示例:
$newfunc = create_function('$a, $b', 'var_dump($a, $b); return $a === $b;'); var_dump($newfunc(1, 2));
示例输出:
int(1) int(2) bool(false)
发现彻底能实现咱们的场景需求~可是又来了,这个函数不安全。为何呢?看下手册中的 Caution:
This function internally performs an eval() and as such has the same security issues as eval(). Additionally it has bad performance and memory usage characteristics.
If you are using PHP 5.3.0 or newer a native anonymous function should be used instead.
create_function
函数底层走的是 eval
函数,因此面临着与 eval
同样的安全问题。而且,create_function
函数性能低下、占用内存高。而这函数最初就是为了匿名函数而生的,从 PHP 5.3.0 开始就内置实现了匿名函数,因此经过 create_function
去建立 lambda
风格自定义函数就毫无存在的必要了。
方案六:include 文件流
为什么又是 include ?
咱们从官方手册中了解到,include
语句用于包含并运行指定文件,而且支持远程文件,好比 include 'http://www.example.com/file.php?foo=1&bar=2';
。
咱们还从手册中能找到这句话:
若是“URL include wrappers”在 PHP 中被激活,能够用 URL(经过 HTTP 或者其它支持的封装协议——见支持的协议和封装协议)而不是本地文件来指定要被包含的文件。
此时,咱们是否想起了熟悉的 php://input
等 scheme://...
风格内置或自定义的URL封装协议。而这些协议都有个特色,便可用于相似 fopen()
、file_exists()
和 file_get_contents()
的文件系统函数打开。include
读取文件其实与这些函数是一致的。
那咱们就可使用 stream_wrapper_register()
来注册一个用 PHP 类实现的 URL 封装协议。该函数容许用户实现自定义的协议处理器和流,用于全部其它的文件系统函数中(例如 fopen()
,fread()
等)。关于如何实现并注册一个 Stream Wrapper,可参考官方手册,本文仅提供个最简单的示例,来实现字符串表达式的计算。
class VarStream { private $string; private $position; public function stream_open($path, $mode, $options, &$opened_path) { $path = explode('://', $path, 2)[1]; // 此处可对传入的参数进行自定义解析,并做进一步的操做 $this->string = $path; $this->position = 0; return true; } public function stream_read($count) { $ret = substr($this->string, $this->position, $count); $this->position += strlen($ret); return $ret; } public function stream_eof() {} public function stream_stat() {} } stream_wrapper_register("var", "VarStream"); try { $params = ['count' => 1]; $expression = '($count += 111) - 8'; $result = include 'var://<?php extract($params); return ' . $expression . ';'; var_dump($result); } catch (Throwable $t) { echo $t->getMessage(); }
输出结果:
int(104)
方案七:语法解析
这个方案就比较高大上许多,固然实现方式也难了太多。具体就是本身写个语法解析器,将代码字符串解析成 AST 语法树,而后再把语法树的内容计算成最终的值。
怎么实现呢?不用咱们本身再去写了,已经有大佬写好了。固然,若是对 AST 语法解析感兴趣,那学习下如何实现是最好不过的了,会解析语法也就意味着能够本身写门语言了呀 😆
GitHub 中比较有名的 PHP 实现以下 2 个,不少代码静态分析器都是基于这 2 个库开发的。
- PHP-Parser - 最流行
- Tolerant PHP Parser - 微软开源
咱们来看个 nikic/php-parser
的例子:
<?php use PhpParser\Error; use PhpParser\NodeDumper; use PhpParser\ParserFactory; $code = <<<'CODE' <?php function test($foo) { var_dump($foo); } CODE; $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); try { $ast = $parser->parse($code); } catch (Error $error) { echo "Parse error: {$error->getMessage()}\n"; return; } $dumper = new NodeDumper; echo $dumper->dump($ast) . "\n";
示例输出:
array( 0: Stmt_Function( byRef: false name: Identifier( name: test ) params: array( 0: Param( type: null byRef: false variadic: false var: Expr_Variable( name: foo ) default: null ) ) returnType: null stmts: array( 0: Stmt_Expression( expr: Expr_FuncCall( name: Name( parts: array( 0: var_dump ) ) args: array( 0: Arg( value: Expr_Variable( name: foo ) byRef: false unpack: false ) ) ) ) ) ) )
由此,咱们能够任意的实现咱们所需的,也不用担忧安全性问题。
最后,总结下。咱们尝试了不少种方法,都能解决咱们或多或少的场景需求,但哪一个最适合须要咱们本身去考量,但思路值得咱们去深刻探讨。
感谢您的阅读,以为内容不错,点个赞吧 😆
原文地址: https://shockerli.net/post/php-expression-string?source=cnblogs
原文出处:https://www.cnblogs.com/shockerli/p/php-expression-string.html