PHP 实现字符串表达式计算
什么是字符串表达式?即,将我们常见的表达式文本写到了字符串中,如:
"$age >= 20"
,$age
的值是动态的整型变量。什么是字符串表达式计算?即,我们需要一段程序来执行动态的表达式,如给定一个含表达式的字符串变量并计算其结果,而表达式字符串是动态的,比如为客户A执行的表达式是
$orderCount >= 10
,而为客户B执行的表达式是$orderTotal >= 1000
。场景在哪儿?同一份程序具有完全通用性,但差异就其中一个表达式而已,那么我们需要将其抽象出来,让表达式变成动态的、可配置的、或可生成的。
方案一:eval 函数
eval
函数可能是我们第一个想到的方案,也是最简单直接的方案。我们来试验下:
$a = 10;
var_dump(eval('return $a > 5;'));
// 输出:
// bool(true)
嗯~完全能满足我们的需求,因为 eval
函数执行的 PHP 表达式,只要字符串内表达式符合 PHP 语法就行。
但需注意的是,eval
函数可执行任意 PHP 代码,也就意味着权限大、风险高、不安全。如果你的字符串表达式来自于外部输入,那务必注意了请自行做好安全检查和过滤,并考虑风险。当然,执行的是外部输入表达式,非常不建议使用此函数。
方案二:include 临时文件
如何实现?将字符串表达式写入一个临时文件,然后 include
这个临时文件,执行完成后再删除这个临时文件。
方案依然很简单。需要考虑的有:
- 临时文件会很多,一个请求就有很多个,文件的过期和删除务必考虑在内
- 文件的读写,也就牵扯到了磁盘 IO,那性能必定受到严重影响
那这个方案我们还采用吗?
方案三:assert 断言
其实 assert
做不到字符串表达式的计算,但提出来也算个猜想,因为能实现 PHP 表达式是否合法的校验。
下例演示了如何验证某个字符串表达式是否为合法的 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 语法解析感兴趣,那学习下如何实现是最好不过的了,会解析语法也就意味着可以自己写门语言了呀
PHP 实现字符串表达式计算的更多相关文章
- 使用堆栈结构进行字符串表达式("7*2-5*3-3+6/3")的计算
问题: 给定字符串String str = "7*2-5*3-3+6/3", 求出字符串里面表达式的结果? 像javascript有自带的eval()方法,可以直接计算.但java ...
- .NET平台开源项目速览(8)Expression Evaluator表达式计算组件使用
在文章:这些.NET开源项目你知道吗?让.NET开源来得更加猛烈些吧!(第二辑)中,给大家初步介绍了一下Expression Evaluator验证组件.那里只是概述了一下,并没有对其使用和强大功能做 ...
- 字符串表达式String Expressions
声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...
- [SAP ABAP开发技术总结]字符串表达式String Expressions
声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...
- C# - 二叉树表达式计算
很早以前就写过双栈的表达式计算. 这次因为想深入学一下二叉树,网上都是些老掉牙的关于二叉树的基本操作. 感觉如果就学那些概念,没意思也不好记忆.于是动手写了一个表达式计算的应用例子. 这样学习印象才深 ...
- 转: c# 字符串公式计算
C# 自动计算字符串公式的值(三种方式) 从网络上找到这段源码,重新整理后测试通过. 有三种方式可自动计算字符串公式的值:1. 最简单的方式,由SQL语句计算2. 使用Microsoft.Javasc ...
- C++实现 逆波兰表达式计算问题
C++实现 逆波兰表达式计算问题 #include <iostream> #include <string> using namespace std; class Stack ...
- 第6.4节 Python动态表达式计算:eval函数详述
在Python动态执行的函数中,eval是用于执行表达式计算的函数,这个函数用于执行字符串中包含的一个表达式或其编译后对应的代码,不能适用于执行Python语句和完整的代码. 一. 语法 1. ...
- 第五周PTA笔记 后缀表达式+后缀表达式计算
后缀表达式 所谓后缀表达式是指这样的一个表达式:式中不再引用括号,运算符号放在两个运算对象之后,所有计算按运算符号出现的顺序,严格地由左而右进行(不用考虑运算符的优先级). 如:中缀表达式 3(5–2 ...
随机推荐
- 【kafka】一、消息队列
在高并发的应用场景中,由于来不及同步处理请求,接收到的请求往往会发生阻塞.例如,大量的插入.更新请求同时到达数据库,这会导致行或表被锁住,最后会因为请求堆积过多而触发“连接数过多的异常” 的错误.因此 ...
- Docker系列之烹饪披萨(二)
前言 上一篇我们讲解了虚拟机和容器的区别,本节我们来讲讲Docker中关于Dockerfile.镜像.容器等基本概念.Docker是一个在容器内开发.部署.运行应用程序的平台,Docker本质上是容器 ...
- JVM系列(3)- Java VisualVM使用
前言 Java VisualVM是jdk自带一款工具,可以十分友好的监控java进程相关的应用服务及中间件. 工具位置 jdk的bin目录下,找到jvisualvm.exe,双击打开即可. 功能介绍 ...
- kafka消息的处理机制(五)
这一篇我们不在是探讨kafka的使用,前面几篇基本讲解了工作中的使用方式,基本api的使用还需要更深入的去钻研,多使用才会有提高.今天主要是探讨一下kafka的消息复制以及消息处理机制. 1. bro ...
- java线程池,阿里为什么不允许使用Executors?
带着问题 阿里Java代码规范为什么不允许使用Executors快速创建线程池? 下面的代码输出是什么? ThreadPoolExecutor executor = new ThreadPoolExe ...
- 记一次上线部分docker不打日志的问题排查
一次正常的上线,发了几台docker后,却发现有的机器打了info.log里面有日志,有的没有.排查问题开始: 第一:确认这台docker是否有流量进来,确认有流量进来. 第二:确认这台docker磁 ...
- mysql row size上限
mysql innodb 的 row size上限 背景 在项目使用中,出现了以下报错: Error Code: 1118 - Row size too large (> 8126). Chan ...
- windows下用GCC编译DLL
此程序有3个文件,分别为 export.h .export.c .main.c export.h 文件内容 /*此头很有必要,别人在调用的时候知道有哪些方法*/ #ifdef BUILD_DLL #d ...
- powerdesigner16.6版本resource的重复使用
今天早上遇到想要重复使用resource ,但是发现powerdesigner16.6版本跟16.5版本有关重复使用name的设置已经不一样了,网上找了好久没找到,软件上找了好久也没找到相应的设置. ...
- Selenium Webdriver元素定位的八种常用方式【转】
在使用selenium webdriver进行元素定位时,通常使用findElement或findElements方法结合By类返回的元素句柄来定位元素.其中By类的常用定位方式共八种,现分别介绍如下 ...