什么是字符串表达式?即,将我们常见的表达式文本写到了字符串中,如:"$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 函数

systemexecproc_openshell_execpassthru 等系列函数,本质上都是执行外部命令或脚本,以达到执行 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://inputscheme://... 风格内置或自定义的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 实现字符串表达式计算的更多相关文章

  1. 使用堆栈结构进行字符串表达式("7*2-5*3-3+6/3")的计算

    问题: 给定字符串String str = "7*2-5*3-3+6/3", 求出字符串里面表达式的结果? 像javascript有自带的eval()方法,可以直接计算.但java ...

  2. .NET平台开源项目速览(8)Expression Evaluator表达式计算组件使用

    在文章:这些.NET开源项目你知道吗?让.NET开源来得更加猛烈些吧!(第二辑)中,给大家初步介绍了一下Expression Evaluator验证组件.那里只是概述了一下,并没有对其使用和强大功能做 ...

  3. 字符串表达式String Expressions

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  4. [SAP ABAP开发技术总结]字符串表达式String Expressions

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  5. C# - 二叉树表达式计算

    很早以前就写过双栈的表达式计算. 这次因为想深入学一下二叉树,网上都是些老掉牙的关于二叉树的基本操作. 感觉如果就学那些概念,没意思也不好记忆.于是动手写了一个表达式计算的应用例子. 这样学习印象才深 ...

  6. 转: c# 字符串公式计算

    C# 自动计算字符串公式的值(三种方式) 从网络上找到这段源码,重新整理后测试通过. 有三种方式可自动计算字符串公式的值:1. 最简单的方式,由SQL语句计算2. 使用Microsoft.Javasc ...

  7. C++实现 逆波兰表达式计算问题

    C++实现 逆波兰表达式计算问题 #include <iostream> #include <string> using namespace std; class Stack ...

  8. 第6.4节 Python动态表达式计算:eval函数详述

    在Python动态执行的函数中,eval是用于执行表达式计算的函数,这个函数用于执行字符串中包含的一个表达式或其编译后对应的代码,不能适用于执行Python语句和完整的代码. 一.    语法 1.  ...

  9. 第五周PTA笔记 后缀表达式+后缀表达式计算

    后缀表达式 所谓后缀表达式是指这样的一个表达式:式中不再引用括号,运算符号放在两个运算对象之后,所有计算按运算符号出现的顺序,严格地由左而右进行(不用考虑运算符的优先级). 如:中缀表达式 3(5–2 ...

随机推荐

  1. 集合(Collection解析 Set List Map三大集合运用)

    集合的概念:          集合是包含多个对象的简单对象,所包含的对象称为元素.集合里面可以包含任意多个对象,数量可以变化:同时对对象的类型也没有限制,也就是说集合里面的所有对象的类型可以相同,也 ...

  2. Oracle GoldenGate mysql To Kafka上车记录

    一.前言 首先要学习一下ogg的所有进程,看着这张图来学习   Manager进程是GoldenGate的控制进程,运行在源端和目标端上.它主要作用有以下几个方面:启动.监控.重启Goldengate ...

  3. tensorflow学习笔记——使用TensorFlow操作MNIST数据(2)

    tensorflow学习笔记——使用TensorFlow操作MNIST数据(1) 一:神经网络知识点整理 1.1,多层:使用多层权重,例如多层全连接方式 以下定义了三个隐藏层的全连接方式的神经网络样例 ...

  4. Linux下SVN库迁移

    在日常的工作中,可能因为一些服务器硬件损坏等问题,不得不把SVN服务器上的SVN版本库进行迁移,下面讲解一下SVN库迁移方案(采用dump & load方案),在实际操作的时候也非常的简单,有 ...

  5. JavaWeb——JSP表达式语言(EL)

    1.JSP表达式语言(EL)用于在jsp从访问存储在JavaBean中的数据,例如 User ID: ${user.userId}<br /> 这里的${user.userId}就是JSP ...

  6. linux装OpenOffice后传---中文乱码的解决

    上一篇的博客已经详细的介绍了linux系统上如何安装OpenOffice,安装之后使用发现转换的pdf出现中文乱码.后来发现是linux上没有中文对应的那个字体. 字体准备 在windows上的位置 ...

  7. 阿里巴巴_java后端面经

    自我介绍不多说! 1 多线程有什么用?( 发挥多核CPU的优势 防止阻塞 便于建模 ) 2 怎么检测一个线程是否持有对象监视器( Thread类提供了一个holdsLock(Object obj)方法 ...

  8. Linux - 通过expect工具实现脚本的自动交互

    目录 1 安装expect工具 2 expect的常用命令 3 作用原理简介 3.1 示例脚本 3.2 脚本功能解读 4 其他脚本使用示例 4.1 直接通过expect执行多条命令 4.2 通过she ...

  9. Python爬虫(二)正则表达式

    一.介绍 1.概念 正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符.及这些特定字符的组合,组成一个"规则字符串",这个"规则字符串"用来 ...

  10. MySQL-InnoDB锁(二)

    上篇文章中对InnoDB存储引擎中的锁进行学习,本文是实践部分,根据索引和查询范围,探究加锁范围的情况. 在本实例中,创建简单表如下: mysql> select * from t; +---- ...