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 ...
随机推荐
- java在src/test/resourse下读取properties文件
package com.jiepu; import java.io.File; import java.net.URISyntaxException; import java.util.Map; im ...
- 算法与数据结构基础 - 哈希表(Hash Table)
Hash Table基础 哈希表(Hash Table)是常用的数据结构,其运用哈希函数(hash function)实现映射,内部使用开放定址.拉链法等方式解决哈希冲突,使得读写时间复杂度平均为O( ...
- JAVA基础知识(七)存根类
存根类是一个类,它实现了一个接口,它的作用是:如果一个接口有很多方法,如果要实现这个接口,就要实现所有的方法.但是一个类从业务来说,可能只需要其中一两个方法.如果直接去实现这个接口,除了实现所需的方法 ...
- 用 bat 文件实现 excel 周报复制
又要写周报???? 写周报就算了每次都要改这一大堆的日期,什么鬼嘛,最骚的我还总是有的忘记改.... 作为一个正儿八经的程序员,固定每周某天干重复的一件事,哦~~ 这是机器人 程序应 ...
- viewpager_轮播
public class MainActivity extends Activity { private ViewPager pager; private int[] id={R.layout.lay ...
- SoapSerialization——手机号码归属地
public class MainActivity extends AppCompatActivity { private EditText etNumber; private TextView tv ...
- Mysql优化-mysql分区
背景:由于我负责i西科教务处系统,i西科用户量达到20000人左右,那么假设每人每星期10门讲课,数据库记录信息将是20万条,如果不将课程表进行分区或分表,就会造成爆表的情况,如此看来,分区是必须要做 ...
- 7.17 正则表达式 re模块
在介绍正则表达式和re模块之前,先简要介绍一下 正则表达式与re模块的关系 1.正则表达式是一门独立的技术,任何语言均可使用 2.python中要想使用正则表达式需要通过re模块 正则表达式 元字符 ...
- python语言特点简介 以及在Windows以及Mac中安装以及配置的注意事项
正如前一篇随笔所提到的,python属于解释型语言 python语言有两个特点: 1.胶水语言(历史遗留问题,原来Perl语言作为Unix内置标准件,获得极大追捧,作为竞争者的python一开始是作为 ...
- zabbix自发现item监控
在zabbix监控中,我们可以通过自带item的可以和自定义key进行监控,但是当所需要的监控项不确定,比如key会根据时间进行变化时,这时候我们就不能把item的key定义死,要通过自发现这个高级功 ...