How can I code for multiple versions of PHP in the same file without error?
Asked Answered
C

6

12

I'm trying to support two versions of some PHP code in one file using version_compare, but I still get an error.

Code:

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);
}

But I get:

PHP Parse error: syntax error, unexpected T_FUNCTION

On the preg_replace_callback() calls, probably because of the anonymous functions.

Cotopaxi answered 28/2, 2015 at 22:52 Comment(2)
I know that in practice it's not always so simple, but one solution in this instance is simply not to support versions of PHP for which not even security patches are officially available, which at the time of writing means your minimum supported version should be 5.4!Duggan
Most of the sites I build are for 5.2 unfortunately, out my control unfortunatelyCotopaxi
H
15

It is not possible to use version checking to decide to use a language feature that will cause a parse error in a previous version. The parser looks at the whole file, regardless of branching.

If the lint check fails for that version, it won't work, regardless of branching:

> php -l file.php
> PHP Parse error: syntax error, unexpected T_FUNCTION
Hydrophobia answered 28/2, 2015 at 23:0 Comment(4)
he can declare function like function name(args) then pass as string name to preg_replace_callbackDuplicity
@kmlnvm Doesn't change this answer, which is still correct - the OP is asking how to use new syntax when such is available, via a conditional.Vagarious
As I mentioned below(this being the first answer) I was probably hoping for a code wrapper that tells the compiler not to parse that php code as it is for another versionCotopaxi
@Cotopaxi And this answer starts with the correct assertion that that is not possible. There is no pre-processing in PHP, so no way to "hide" code from the compiler.Duggan
S
17

One option would be to put the code in separate files, like so:

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

Then inside file-5.3.0.php, add the corresponding 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);

... and inside file-5.x.php add the remaining 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);
Sternpost answered 28/2, 2015 at 23:5 Comment(4)
I thought about this but is not ideal, I was probably hoping for a code wrapper that tells the compiler not to parse that php code as it is for another versionCotopaxi
@orbitory: PHP is not compiled, it is interpreted =)Ashtoreth
@Mints97: Technically, like most scripting languages today, PHP is first parsed and compiled into an internal representation, which is then interpreted. This distinction is actually relevant here, since it means that the parser will trip over syntax errors even in code that never gets executed.Rhodesia
@Mints97 A truly "interpreted" language runtime only applies to some cases, notably shell script code, these days.. where each line is only parsed as it is encountered. Executing a pre-processed AST or other variation - as done in any major implementation of a "scripting" language - should be taken under a more optimistic nature.. (Fun facts: for truly devious results, modify a .bat/.cmd file while it is running.)Vagarious
H
15

It is not possible to use version checking to decide to use a language feature that will cause a parse error in a previous version. The parser looks at the whole file, regardless of branching.

If the lint check fails for that version, it won't work, regardless of branching:

> php -l file.php
> PHP Parse error: syntax error, unexpected T_FUNCTION
Hydrophobia answered 28/2, 2015 at 23:0 Comment(4)
he can declare function like function name(args) then pass as string name to preg_replace_callbackDuplicity
@kmlnvm Doesn't change this answer, which is still correct - the OP is asking how to use new syntax when such is available, via a conditional.Vagarious
As I mentioned below(this being the first answer) I was probably hoping for a code wrapper that tells the compiler not to parse that php code as it is for another versionCotopaxi
@Cotopaxi And this answer starts with the correct assertion that that is not possible. There is no pre-processing in PHP, so no way to "hide" code from the compiler.Duggan
V
6

Parsing PHP files happens before any code is run. The if-approach will never work across the same code unit - ie. PHP file. (And no, I will not be one to suggest "eval".)

However, if there was a different included file (one for each version), then the if could choose which file to include - but each of the files must still be syntactically valid in the PHP version/context in which it is parsed.

This is actually a "sane" approach to use if using Dependency Injection or some variation thereof - if it is really important to maintain different component implementations. This is because the IoC container / setup will determine which file(s)/implementation(s) to include and the service consumers will be agnostic to the change.

Vagarious answered 28/2, 2015 at 23:8 Comment(0)
P
5

I know this answer why your getting the syntax error, but another option is to use create_function() which is compatible with PHP v4 and 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);

It should also be noted that PHP doesn't support conditional compilation like other programming languages (such as C/C++) do. However, as others have stated, you can get it around be utilizing require(), include() or eval().

Phebe answered 1/3, 2015 at 6:39 Comment(0)
K
3

You could use eval and heredoc notation, but as Ilmari Karonen pointed out, heredocs act as double-quoted strings, and variables will be interpolated. This requires all $ signs to be escaped, which can be messy.

Alternatively, you can use eval and nowdoc notation, which is unfortunately only available in PHP 5.3.0 and above. eval is normally proscribed but in this situation the string is not user-specified and so there is no security risk.

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
    );
}

As far as I know this is closest to the OP's intention. Additionally, if there's any good time and place to use eval, this situation is exactly that time and place.

Kep answered 1/3, 2015 at 4:12 Comment(2)
Unfornatunately, this won't work as written, because anything that looks like a variable will get interpolated into the heredoc before the eval. (See this demo on Ideone, with echo instead of eval.) You could fix it by using nowdocs, but those are only supported since PHP 5.3; the alternative would be to escape every $ (and \) sign in the evaled code with a backslash, which works, but is ugly and tedious (not to mention error-prone).Rhodesia
Oh, well that sucks. Thanks, I've added that to this answer.Kep
O
2

Just do it like this and it should also work for php versions under 5.3 which doesn't support anonymous functions:

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);
Optimistic answered 28/2, 2015 at 23:4 Comment(4)
I thought about this but is not ideal, I was probably hoping for a code wrapper that tells the compiler not to parse that php code as it is for another versionCotopaxi
@Cotopaxi I think you won't get very far with your hope :DOptimistic
just asking :), I implemented your solution but this is not really the correct answerCotopaxi
@Cotopaxi Yeah it's a good and interesting question and I think it's good that you asked that, before you wrote your entire code. Now you know it! :DOptimistic

© 2022 - 2024 — McMap. All rights reserved.