如何在同一文件中编写多个版本的PHP代码而不出错?

12

我正在尝试使用version_compare在一个文件中支持两个版本的一些PHP代码,但是我仍然遇到错误。

代码:

if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
    $alias = preg_replace_callback('/&#x([0-9a-f]{1,7});/i', function($matches) { return chr(hexdec($matches[1])); }, $alias);
    $alias = preg_replace_callback('/&#([0-9]{1,7});/', function($matches) { return chr($matches[1]); }, $alias);
} else {
    $alias = preg_replace('/&#x([0-9a-f]{1,7});/ei', 'chr(hexdec("\\1"))', $alias);
    $alias = preg_replace('/&#([0-9]{1,7});/e', 'chr("\\1")', $alias);
}

但是我得到:

PHP解析错误:语法错误,T_FUNCTION意外

preg_replace_callback()调用中出现该错误,可能是由于匿名函数引起的。


4
我知道实际情况并不总是如此简单,但在这种情况下的一个解决方案就是不支持已经不再提供安全补丁的PHP版本,因此在撰写本文时,最低支持版本应该是5.4! - IMSoP
我建立的大多数网站都是为5.2版本设计的,不幸的是,这超出了我的控制。 - orbitory
6个回答

17

其中一个选项是将代码放在单独的文件中,如下所示:

if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
    include('file-5.3.0.php');
} else {
    include('file-5.x.php');
}

然后,在 file-5.3.0.php 文件中添加相应的代码:

$alias = preg_replace_callback('/&#x([0-9a-f]{1,7});/i', function($matches) { return chr(hexdec($matches[1])); }, $alias);
$alias = preg_replace_callback('/&#([0-9]{1,7});/', function($matches) { return chr($matches[1]); }, $alias);

...并在file-5.x.php中添加剩余的代码:

$alias = preg_replace('/&#x([0-9a-f]{1,7});/ei', 'chr(hexdec("\\1"))', $alias);
$alias = preg_replace('/&#([0-9]{1,7});/e', 'chr("\\1")', $alias);

我考虑过这个问题,但并不理想。我可能希望有一个代码包装器,告诉编译器不要将那段 PHP 代码解析为另一个版本的代码。 - orbitory
2
@orbitory:PHP并非编译语言,而是解释型语言 =) - user3079266
2
@Mints97:从技术上讲,像今天大多数脚本语言一样,PHP首先被解析和编译成内部表示形式,然后再进行解释。这个区别实际上在这里是相关的,因为它意味着即使在从未执行的代码中,解析器也会遇到语法错误。 - Ilmari Karonen
1
@Mints97 一个真正的“解释型”语言运行时只适用于某些情况,特别是 shell 脚本代码,这些天..每一行只有在遇到时才被解析。执行预处理的 AST 或其他变体 - 如任何主要实现的“脚本”语言所做的那样 - 应该采取更乐观的态度..(有趣的事实:为了获得真正恶毒的结果,请在运行时修改 .bat/.cmd 文件。) - user2864740

16

无法使用版本检查来决定是否使用语言特性,因为在先前版本中会导致解析错误。解析器会查看整个文件,而不考虑分支。

如果该版本的 lint 检查失败,那么无论如何都不会起作用,而与分支无关:

> php -l file.php
> PHP Parse error: syntax error, unexpected T_FUNCTION

他可以像这样声明函数 function name(args),然后将字符串 name 作为参数传递给 preg_replace_callback - Kamil Karkus
2
@kmlnvm 不改变这个答案,它仍然是正确的 - OP正在询问如何在有条件语句的情况下使用新语法。 - user2864740
1
正如我在下面提到的(这是第一个答案),我可能希望有一个代码包装器,告诉编译器不要将该PHP代码解析为另一个版本的代码。 - orbitory
4
这个答案开始时给出了正确的说法,即这是不可能的。在PHP中没有预处理,因此无法“隐藏”代码使编译器无法看到。 - IMSoP

6
解析PHP文件是在任何代码运行之前进行的。使用“if”方法永远不会跨越相同的代码单元 - 即PHP文件。 (不,我不会建议使用“eval”。)
但是,如果有一个不同的“包含文件”(每个版本一个),则“if”可以选择要包含的文件 - 但是每个文件在解析它的PHP版本/上下文中仍必须是语法上有效的。
如果使用依赖注入或某种变体,这实际上是一种“明智”的方法 - 如果维护不同的组件实现真的很重要。这是因为IoC容器/设置将确定要包括哪个文件/实现,并且服务消费者将对更改保持不可知。

5

我知道你为什么会得到语法错误,但另一个选项是使用create_function(),它与PHP v4和v5兼容...

$alias = preg_replace(
              '/&#x([0-9a-f]{1,7});/i', 
              create_function(
                  '$matches',
                  'return chr(hexdec($matches[1]));'
              ), 
              $alias);
$alias = preg_replace(
              '/&#([0-9]{1,7});/', 
              create_function(
                  '$matches',
                  'return chr($matches[1]);'
              ), 
              $alias);

需要注意的是,与其他编程语言(如C / C ++)不同,PHP不支持条件编译。但是,正如其他人所指出的那样,您可以通过使用 require(), include()eval() 来解决这个问题。


3
你可以使用evalheredoc表示法,但正如Ilmari Karonen所指出的那样,heredocs像双引号字符串一样,变量将被插值。这需要所有的$符号都被转义,可能会很混乱。
或者,您可以使用evalnowdoc表示法,不幸的是,它只在PHP 5.3.0及以上版本中可用。eval通常是禁止的,但在这种情况下,该字符串不是用户指定的,因此不存在安全风险。
if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
    eval(<<<'CODE'
    $alias = preg_replace_callback('/&#x([0-9a-f]{1,7});/i', function($matches) { return chr(hexdec($matches[1])); }, $alias);
    $alias = preg_replace_callback('/&#([0-9]{1,7});/', function($matches) { return chr($matches[1]); }, $alias);
CODE
    );
} else {
    eval(<<<'CODE'
    $alias = preg_replace('/&#x([0-9a-f]{1,7});/ei', 'chr(hexdec("\\1"))', $alias);
    $alias = preg_replace('/&#([0-9]{1,7});/e', 'chr("\\1")', $alias);
CODE
    );
}

据我所知,这是最接近原帖意图的。此外,如果有任何好的时间和地点可以使用eval,那么这种情况正是那个时候和地点。

1
很遗憾,这段代码无法正常工作,因为任何看起来像变量的东西都会在 eval 之前插入到 heredoc 中。(请参见 Ideone 上的演示,使用 echo 而不是 eval。)你可以通过使用 nowdocs 来修复它,但是这些只支持 PHP 5.3 及以上版本;另一种选择是在 evaled 代码中用反斜杠转义每个 $(和 \)符号,虽然这样可以解决问题,但是很丑陋、繁琐(更不用说容易出错了)。 - Ilmari Karonen
哦,那太糟糕了。谢谢,我已经将它添加到这个答案中了。 - Fengyang Wang

2

按照以下方式操作,它应该也适用于不支持匿名函数的 PHP 版本低于 5.3:

function one($matches) {
    return chr(hexdec($matches[1])); 
}

function two($matches) {
    return chr($matches[1]);
}

$alias = preg_replace_callback('/&#x([0-9a-f]{1,7});/i', "one", $alias);
                                                       //^^^^ See here I
                                                       //just passed the function name
                                                       //as string
$alias = preg_replace_callback('/&#([0-9]{1,7});/', "two", $alias);

我考虑过这个,但并不理想。我可能希望有一个代码包装器,告诉编译器不要将那段php代码解析为另一个版本的代码。 - orbitory
@orbitory 我想你不会抱太大的希望 :D - Rizier123
只是问一下:)我实现了你的解决方案,但这并不是真正的答案。 - orbitory
@orbitory 是的,这是一个好问题,也很有趣。我认为你在编写完整代码之前提出这个问题是很好的。现在你知道了! :D - Rizier123

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接