验证用户输入的PHP代码,然后将其传递给eval()

验证用户输入的PHP代码,然后将其传递给eval(),php,Php,在将字符串传递给eval()之前,我希望确保语法正确,并允许: 两个函数:a()和b() 四家运营商:/*-+ 括号:() 数字:1.2,-1,1 我怎样才能做到这一点,也许这与PHP标记器有关 实际上,我正在尝试制作一个简单的公式解释器,以便将a()和b()替换为ln()和exp()。我不想从头开始编写标记器和解析器。确实已经为PHP编写了解析器生成器,特别是“LIME”附带了典型的“calculator”示例,这将是“mini language”的一个明显起点: 自从我上次玩莱姆已经有好几年

在将字符串传递给eval()之前,我希望确保语法正确,并允许:

  • 两个函数:a()和b()
  • 四家运营商:/*-+
  • 括号:()
  • 数字:1.2,-1,1
  • 我怎样才能做到这一点,也许这与PHP标记器有关


    实际上,我正在尝试制作一个简单的公式解释器,以便将a()和b()替换为ln()和exp()。我不想从头开始编写标记器和解析器。

    确实已经为PHP编写了解析器生成器,特别是“LIME”附带了典型的“calculator”示例,这将是“mini language”的一个明显起点:

    自从我上次玩莱姆已经有好几年了,但那时它已经成熟稳定了

    注:

    1) 如果您愿意,使用完全启用的解析器生成器可以完全避免使用PHP eval()——您可以让它发出一个解析器,该解析器可以有效地为用mini语言编写的表达式提供一个“eval”函数(带有验证)。这为您提供了额外的优势,允许您根据需要添加对新函数的支持


    2) 对于这样一个显然很小的任务,一开始使用解析器生成器似乎有些过火,但一旦示例运行起来,您就会对修改和扩展它们的简单性印象深刻。而且很容易低估从头开始编写一个无bug解析器(甚至是一个“微不足道的”解析器)的难度。

    是的,您需要标记器或类似的东西,但这只是故事的一部分。标记器(通常称为“lexer”)只能读取和解析表达式的元素,但无法检测“foo()+*bar”之类的内容是否无效。您需要第二个部分,名为,它将能够在一种树(称为“AST”)中排列令牌,或者在未能这样做时提供错误消息。讽刺的是,一旦你有了一棵树,“eval”就不再需要了,你可以直接从树上计算你的表达式


    我建议您手工编写解析器,因为这是一个非常有用的练习,而且非常有趣。很容易编程

    就验证而言,以下字符标记是有效的:

    operator: [/*+-]
    funcs:    (a\(|b\()
    brackets: [()]
    numbers:  \d+(\.\d+)?
    space:    [ ]
    
    然后,一个简单的验证可以检查输入字符串是否匹配这些模式的任何组合。由于
    funcs
    标记非常精确,并且与其他标记没有太多冲突,因此该验证应该非常稳定,不需要实现任何语法/语法:

    $tokens = array(
        'operator' => '[/*+-]',
        'funcs' => '(a\(|b\()',
        'brackets' => '[()]', 
        'numbers' => '\d+(\.\d+)?',
        'space' => '[ ]',
    );
    
    $pattern = '';
    foreach($tokens as $token)
    {
        $pattern .= sprintf('|(?:%s)', $token);
    }
    $pattern = sprintf('~^(%s)*$~', ltrim($pattern, '|'));
    
    echo $pattern;
    
    只有当整个输入字符串与基于令牌的模式匹配时,才会进行验证。它仍然可能是语法错误的PHP,因此您可以确保它仅构建在指定的令牌上:

    ~^((?:[/*+-])|(?:(a\(|b\())|(?:[()])|(?:\d+(\.\d+)?)|(?:[ ]))*$~
    
    如果您动态地构建模式(如示例中所示),那么以后就可以更轻松地修改语言标记

    此外,这可能是您自己的标记器/词条器的第一步。然后,令牌流可以传递给解析器,解析器可以对其进行语法验证和解释。这就是部分

    除了编写一个完整的lexer+解析器,并且需要验证语法之外,您还可以基于令牌制定语法,然后在输入的令牌表示上执行基于regex的令牌语法


    标记是您在语法中使用的单词。您需要比在令牌中更精确地描述括号和函数定义,并且令牌化器应该遵循更明确的规则,即哪个令牌取代另一个令牌。本节概述了这一概念。它还使用正则表达式进行语法公式和语法验证,但它仍然不解析。在您的情况下,
    eval
    将是您正在使用的解析器。

    您可以使用、检查每个令牌,并在第一个无效令牌时中止。

    hakre的回答是,使用正则表达式是一个不错的解决方案,但有点复杂。处理函数的白名单也变得相当混乱。如果这真的出了问题,它可能会对你的系统产生非常恶劣的影响


    您不使用javascript“eval”有什么原因吗?

    您关心这些可能输入的顺序吗?应该在您的问题中添加一个您允许的函数和一个不应该传递的函数的示例。注意eval的使用不能掉以轻心。这就是为什么我怀疑他想事先消毒;)我正在尝试制作一个简单的公式解释器,以便将a()和b()替换为ln()和exp()。在求值之前,您是否需要检查语法,或者您只是担心没有执行其他代码?我同意手工编写解析器很有趣,并且是任何严肃程序员的基本培训的一部分。但是,如果你在“按时钟”工作,我认为最好重新使用一个预先存在的解析器生成器,并花一些节省的时间学习(和使用)语法定义。我明白你的观点,但我不同意特别是在解析器的情况下。首先,即使您有兴趣了解stuff是如何工作的,在构建一个足够健壮的解析器的过程中,您可能会犯一些标准错误,以确定是否可以安全地将来自大型坏Web的给定字符串传递给PHP eval()——尽管我们不喜欢无知,但我们也不喜欢易受攻击的Web应用程序。第二,我不认为不学习一些新的和有用的东西就可以使用解析器生成器(即使是表面上),即使你最终决定使用另一种方法。@stereofrog-对不起,我误读了更改历史。现在链接至少应该可以工作了,尽管我知道它与首选格式不匹配(因为某些原因,括号中的链接在Chrome中没有正确呈现)。我需要从数据库中获取一些数据到公式中,所以我不能使用JS。