php变量和运算符的数学公式字符串



我有以下问题。我觉得很难解释,因为我是一个业余程序员。如果犯下任何重大错误,请原谅我:

我正在开发一个棒球数据库,该数据库处理棒球特定指标和非特定指标/统计数据。当指标是累积的,或者当我只想显示手动输入或从csv导入的一天的数据集时,大多数日期输出都很简单。(所有百分比都经过计算并以数字形式输入数据库)

例如,如果我输入的统计数据

{ Hits:1, 
Walks:2, 
AB:2, 
Bavg:0.500 } 

第一天和

{ Hits:2, 
Walks:2, 
AB:6, 
Bavg:0.333 } 

然后尝试获得总数,点击、步行和AB很简单:SUM。但CCD_ 2必须是一个公式CCD_。其他(非棒球特定的)指标,如垂直跳跃或60码次数,也相当直接:MAXMIN

用户应该能够添加自己的度量。所以他必须能够输入一个计算公式。此计算存储在数据库表中,作为度量名称、id、类型(累计、最大、最小、已计算)旁边的列。

生成html表的php脚本被设置为动态的,无论查询发送多少度量(度量可以是几个类别的一部分)。最终,我想用它们的公式替换计算类型的度量的所有值。

我的方法是将mysql表中的公式作为字符串。然后,在php中,将可能是Strikes/Pitches*100的字符串转换为$strikes/$pitches*100——假设这是我可以放入phpsql查询中的内容。然而,在将其放入$strikes/$pitches*100格式之前,我需要有这些变量来定义它们。我相信我能做到,但我到了那里就会跨过那座桥。

你能告诉我如何实现这一目标的正确方向吗?或者告诉我在哪里或寻找什么?我相信这以前在某个地方做过。。。

我非常感谢您的帮助!

克莱门斯

Vilx-已经给出了正确的解决方案。所以我会给你一个不太正确的,的解决方案。

正如正确的解决方案所指出的,eval是邪恶的。但是,这也很容易,也很强大(就像邪恶经常发生的那样——但我不会让你加入黑暗面,卢克)。

由于您需要做的是非常小且简单的SQL子集,因此实际上可以使用eval(),或者更好地使用其SQL等价物,将用户提供的代码插入SQL查询,只要安全即可;对于较小的需求,这是可能的。

(在一般情况下,绝对不是。请记住,此解决方案简单、快速,但不会扩展。如果程序超过一定的复杂性,您无论如何都必须采用Vilx-的解决方案)。

您可以验证用户提供的字符串,以确保它在语法或逻辑上可能不正确,但至少不会执行任意代码。

这没关系:

SELECT SUM(pitch)+AVG(runs)-5*(MIN(balls)) /* or whatever */

这虽然是错误的,但也是无害的:

SELECT SUM(pitch +

但这绝对不是(强制性XKCD参考):

SELECT "Robert'); DROP TABLE Students;--

更糟糕的是,因为以上内容在标准MySQL上不起作用(默认情况下不允许多个语句),而这将:

SELECT SLEEP(3600)

那么,我们如何区分无害和有害呢?我们首先定义可以在公式中使用的占位符变量。让我们假设它们将始终以{name}的形式存在。所以,把那些我们知道是安全的东西从公式中去掉:

$verify = preg_replace('#{[a-z]+}#', '', $formula);

然后,算术运算符也被删除;他们也很安全。

$verify = preg_replace('#[+*/-]#', '', $verify);

然后是数字和看起来像数字的东西:

$verify = preg_replace('#[0-9.]+#', '', $verify);

最后是一些你信任的函数。这些函数的自变量可能是变量或变量的组合,因此它们已被删除,而函数现在没有自变量(例如SUM()),或者您有嵌套的函数或嵌套的括号,如SUM (SUM( ()()))

你一直用一个空格替换()(里面有任何空格),直到替换项找不到任何东西:

for ($old = ''; $old !== $verify; $verify = preg_replace('#\s*\(\s*\)\s*#', ' ', $verify)) {
$old = $verify;
}

现在,您可以从结果中删除您信任的任何函数的出现,作为一个完整的词:

for ($old = ''; $old !== $verify; $verify = preg_replace('#\b(SUM|AVG|MIN|MAX)\b#', ' ', $verify)) {
$old = $verify;
}

最后两个步骤必须合并,因为您可能有两个嵌套的括号和函数,相互干扰:

for ($old = ''; $old !== $verify; $verify = preg_replace('#\s*(\b(SUM|AVG|MIN|MAX)\b|\(\s*\))\s*#', ' ', $verify)) {
$old = $verify;
}

在这一点上,如果您只剩下nothing,这意味着原始字符串是无害的(最坏的情况下,它可能会触发0除法,或者如果语法错误,则会触发SQL异常)。如果您只剩下内容,则该公式将被拒绝,并且永远不会保存在数据库中。

当您有一个有效的公式时,您可以使用Bavg0替换变量,使它们成为数字(或列名称)。剩下的要么是有效的、无害的SQL代码,要么是不正确的SQL代码。在将其封装在try/catch中以截取任何PDOException或除以零之后,您可以将其直接插入到查询中。

我假设要求确实是允许用户输入任意公式。正如评论中所指出的,这确实是一项不小的任务,所以如果你能满足于少做一些,我建议你这样做。但是,假设少做什么也行,让我们看看能做些什么。

当然,最简单的想法是使用PHP的eval()函数。您可以从字符串中执行任意PHP代码。您所需要做的就是预先设置所有必要的变量并获取返回值。

然而,它确实有缺点。最大的问题是安全问题。您实际上是在服务器上执行任意用户提供的代码。它可以做任何你自己的代码可以做的事情。访问文件、使用数据库连接、更改变量等等。除非您完全信任您的用户,否则这将是一场安全灾难。

此外,语法或运行时错误可能会影响脚本的其余部分。eval()也相当慢,因为它每次都要解析代码。在你的特殊情况下,也许不是什么大不了的事,但值得关注。

总而言之,在每一种具有eval()函数的语言中,它几乎都被普遍认为是邪恶的,需要不惜一切代价避免。

那么,还有什么选择呢?好吧,一个专用的公式解析器/执行器会很好。我已经写过几次了,但它远非微不足道。如果公式是用波兰符号或反向波兰符号写的,这项工作会更容易,但除非你练习过,否则写起来很痛苦。对于常规公式,请参阅调车场算法。它很简单,可以很容易地适应函数之类的东西。但它仍然相当乏味。

所以,除非你想把它当作一个有趣的挑战,否则就去找一个已经做过的图书馆。那里似乎有很多这样的图书馆。沿着";算术表达式解析器库php";。

最新更新