Readonly in PHP 7.4

728 views Asked by At

I have written a PHP library using the PHP 8.0 readonly keyword and then I realised that it would be good to support earlier versions of PHP such as 7.4.

I could easily remove the readonly keywords from my code but I don't want to do that -- they were put there for a reason!

Having a C background, I immediately thought of macros but PHP doesn't seem to have any. I've googled this answer for adding macro-like behaviour to PHP, but it looks like an overkill. And it's just for one file, and my library has 26 files at present.

Is there an easy way to make PHP 7.4 just ignore the readonly keyword and make my code cross-version? Something to the effect of this C code?

#if PHP_VERSION < 8
#define readonly /**/
#enif

Perphaps some composer build option that can pre-process files before packaging them up?

3

There are 3 answers

3
hanshenrik On

most frameworks simply avoid using modern features for this reason alone (WordPress, Symfony, Laravel), but if you insist, your best bet is probably Composer, you can have a v1.x.x with composer.json

{
    "require": {
        "php": ">=7.4"
    },
}

and a v2.x.x with composer.json

{
    "require": {
        "php": ">=8.0"
    },
}

then when people do composer require lib, composer will automatically scan for and install the newest version of your library that is compatible with the local php version and composer.json-constraints (-:

the downside is that you'll have to maintain both v1 and v2 of your library for as long as you intend to support php 7.4 though..

another option is to have a loader like lib.php

if(PHP_MAJOR_VERSION >= 8){
    require("lib_modern.php");
} else{
    require("lib_legacy.php");
}

again with the downside of having to maintain both lib_modern and lib_legacy

7
Martin Zeitler On

PHP is not being compiled, therefore there's no compiler macros.
According to Backward Incompatible Changes it's a new keyword:

readonly is a keyword now. However, it still may be used as function name.

So you have two choices: a) don't re-assign it's value or b) maintain two versions.

7
IMSoP On

Out of the box, PHP does not include conditional compilation of the type you're hoping for.

One option would be to pre-process the source files on the fly, using a custom autoloader or Composer hook. The idea would be to let the normal code run to the point where it was going to include the file, then instead fetch its contents and manipulate it.

Note that this would not need to be a fully-functional macro system, you could just surround the code with some clear markers, like /* IF PHP 8 */ readonly /* END IF */ and match them with a simple regex pattern:

$php_code = file_get_contents($php_file_being_loaded);
if ( PHP_VERSION_ID < 80000 ) {
    $php_code = preg_replace('#/\* IF PHP 8 \*/.*?/\* END IF \*/#s', '', $php_code);
}
eval($php_code);

Alternatively, you could run the pre-processing "offline", to automatically produce parallel versions of the library: one for PHP 8.0 and above, and a different one for PHP 7.4. Again, this could be as simple as the above, or you could use a tool like Rector which parses and rewrites normal PHP code (with no extra markers) according to set rules, including "downgrading" it to be compatible with a particular version of PHP.