1. Understand the Basics of Transpiling
Transpiling is the process of converting code written in one programming language into another. It is a form of source-to-source compilation, where the source code is converted from one language to another while preserving its original meaning. Transpiling is often used to convert code written in a high-level language into a lower-level language, such as converting PHP code into JavaScript.
2. Choose a Transpiler
There are several transpilers available for transpiling PHP code into JavaScript. Popular options include Babel, TypeScript, and Google Closure Compiler. Each transpiler has its own advantages and disadvantages, so it’s important to research and choose the one that best suits your needs.
3. Install the Transpiler
Once you’ve chosen a transpiler, you’ll need to install it. Most transpilers are available as command-line tools, so you’ll need to install them using the command line. For example, if you’re using Babel, you can install it using npm:
npm install -g babel-cli
4. Configure the Transpiler
Once you’ve installed the transpiler, you’ll need to configure it. This involves setting up the transpiler’s options, such as the target language, the source language, and any additional plugins or presets.
5. Transpile the Code
Once you’ve configured the transpiler, you can transpile your code. This is usually done by running a command in the command line. For example, if you’re using Babel, you can transpile your code by running the following command:
babel src -d dist
This command will transpile all the files in the “src” directory and output the transpiled code to the “dist” directory.
6. Test the Transpiled Code
Once you’ve transpiled your code, it’s important to test it to make sure it works as expected. This can be done by running the transpiled code in a browser or in a testing environment.
7. Optimize the Transpiled Code
Once you’ve tested the transpiled code, you can optimize it to make it run faster and more efficiently. This can be done by minifying the code, removing unnecessary code, and using code optimization techniques.
Transpiling PHP code into JavaScript can be a complex process, but it’s an important step in modern web development. By following the steps outlined above, you can ensure that your code is transpiled correctly and efficiently.
In ideal circumstances, we should use PHP 8.0 (the latest version as of writing this) for all our sites and update it as soon as a new version is released. However, developers will often need to work with previous PHP versions, such as when creating a public plugin for WordPress or working with legacy code which impedes upgrading the webserver’s environment.
In these situations, we could give up hope of using the latest PHP code. But there is a better alternative: we can still write our source code with PHP 8.0 and transpile it to a previous PHP version — even to PHP 7.1.
In this guide, we’ll teach you everything you need to know about transpiling PHP code.
What Is Transpiling?
Transpiling converts source code from a programming language into an equivalent source code of the same or a different programming language.
Transpiling is not a new concept within web development: client-side developers will quite likely be familiar with Babel, a transpiler for JavaScript code.
Babel converts JavaScript code from the modern ECMAScript 2015+ version into a legacy version compatible with older browsers. For instance, given an ES2015 arrow function:
[2, 4, 6].map((n) => n * 2);
…Babel will convert it into its ES5 version:
[2, 4, 6].map(function(n) {
return n * 2;
});
What Is Transpiling PHP?
What is potentially new within web development is the possibility of transpiling server-side code, in particular PHP.
Transpiling PHP works the same way as transpiling JavaScript: source code from a modern PHP version is converted into an equivalent code for an older PHP version.
Following the same example as before, an arrow function from PHP 7.4:
$nums = array_map(fn($n) => $n * 2, [2, 4, 6]);
…can be transpiled into its equivalent PHP 7.3 version:
$nums = array_map(
function ($n) {
return $n * 2;
},
[2, 4, 6]
);
Arrow functions can be transpiled because they are syntactic sugar, i.e. a new syntax to produce an existing behavior. This is the low-hanging fruit.
However, there are also new features that create a new behavior, and as such, there will be no equivalent code for previous versions of PHP. That’s the case with union types, introduced in PHP 8.0:
function someFunction(float|int $param): string|float|int|null
{
// ...
}
In these situations, transpiling can still be done as long as the new feature is required for development but not for production. Then, we can simply remove the feature altogether from the transpiled code without serious consequences.
One such example is union types. This feature is used to check that there is no mismatch between the input type and its provided value, which helps prevent bugs. If there is a conflict with types, there will be an error already in development, and we should catch it and fix it before the code reaches production.
Hence, we can afford to remove the feature from the code for production:
function someFunction($param)
{
// ...
}
If the error still happens in production, the thrown error message will be less precise than if we had union types. However, this potential disadvantage is outweighed by being able to use union types in the first place.
Advantages of Transpiling PHP Code
Transpiling enables one to code an application using the latest version of PHP and produce a release that also works in environments running older versions of PHP.
This can be particularly useful for developers creating products for legacy content management systems (CMS). WordPress, for instance, still officially supports PHP 5.6 (even though it recommends PHP 7.4+). The percentage of WordPress sites running PHP versions 5.6 to 7.2 — which are all End-of-Life (EOL), meaning they’re not receiving security updates anymore — stands at a sizable 34.8%, and those running on any PHP version other than 8.0 stands at a whopping 99.5%:
Consequently, WordPress themes and plugins targeted at a global audience will quite likely be coded with an old version of PHP to increase their possible reach. Thanks to transpiling, these could be coded using PHP 8.0, and still be released for an older PHP version, thus targeting as many users as possible.
Indeed, any application that needs to support any PHP version other than the most recent one (even within the range of the presently supported PHP versions) can benefit.
This is the case with Drupal, which requires PHP 7.3. Thanks to transpiling, developers can create publicly available Drupal modules using PHP 8.0, and release them with PHP 7.3.
Another example is when creating custom code for clients who cannot run PHP 8.0 in their environments due to one reason or another. Nevertheless, thanks to transpiling, developers can still code their deliverables using PHP 8.0 and run them on those legacy environments.
When to Transpile PHP
PHP code can always be transpiled unless it contains some PHP feature that has no equivalent in the previous version of PHP.
That’s possibly the case with attributes, introduced in PHP 8.0:
#[SomeAttr]
function someFunc() {}
#[AnotherAttr]
class SomeClass {}
In the earlier example using arrow functions, the code could be transpiled because arrow functions are syntactic sugar. Attributes, in contrast, create completely new behavior. This behavior could also be reproduced with PHP 7.4 and below, but only by manually coding it, i.e. not automatically based on a tool or process (AI could provide a solution, but we’re not there yet).
Attributes intended for development use, such as #[Deprecated]
, can be removed the same way that union types are removed. But attributes that modify the application’s behavior in production cannot be removed, and they cannot be directly transpiled either.
As of today, no transpiler can take code with PHP 8.0 attributes and automatically produce its equivalent PHP 7.4 code. Consequently, if your PHP code needs to use attributes, then transpiling it will be difficult or unfeasible.
PHP Features Which Can Be Transpiled
These are the features from PHP 7.1 and above which can currently be transpiled. If your code only uses these features, you can enjoy the certainty that your transpiled application will work. Otherwise, you’ll need to assess if the transpiled code will produce failures.
PHP Transpilers
Currently, there is one tool for transpiling PHP code: Rector.
Rector is a PHP reconstructor tool, which converts PHP code based on programmable rules. We input the source code and the set of rules to run, and Rector will transform the code.
Rector is operated via command line, installed in the project via Composer. When executed, Rector will output a “diff” (additions in green, removals in red) of the code before and after conversion:
Which Version of PHP to Transpile to
To transpile code across PHP versions, the corresponding rules must be created.
Today, the Rector library includes most of the rules for transpiling code within the range of PHP 8.0 to 7.1. Hence, we can reliably transpile our PHP code as far down as version 7.1.
There are also rules for transpiling from PHP 7.1 to 7.0 and from 7.0 to 5.6, but these are not exhaustive. Work is underway to complete them, so we may eventually transpile PHP code down to version 5.6.
Transpiling vs Backporting
Backporting is similar to transpiling, but simpler. Backporting code does not necessarily rely on new features from a language. Instead, the same functionality can be provided to an older version of the language simply by copying/pasting/adapting the corresponding code from the new version of the language.
For instance, the function str_contains
was introduced in PHP 8.0. The same function for PHP 7.4 and below can be easily implemented like this:
if (!defined('PHP_VERSION_ID') || (defined('PHP_VERSION_ID') && PHP_VERSION_ID < 80000)) {
if (!function_exists('str_contains')) {
/**
* Checks if a string contains another
*
* @param string $haystack The string to search in
* @param string $needle The string to search
* @return boolean Returns TRUE if the needle was found in haystack, FALSE otherwise.
*/
function str_contains(string $haystack, string $needle): bool
{
return strpos($haystack, $needle) !== false;
}
}
}
Because backporting is simpler than transpiling, we should opt for this solution whenever backporting does the job.
Concerning the range between PHP 8.0 to 7.1, we can use Symfony‘s polyfill libraries:
These libraries backport the following functions, classes, constants, and interfaces:
Examples of Transpiled PHP
Let’s inspect a few examples of transpiled PHP code, and a few packages which are being fully transpiled.
PHP Code
The match
expression was introduced in PHP 8.0. This source code:
function getFieldValue(string $fieldName): ?string
{
return match($fieldName) {
'foo' => 'foofoo',
'bar' => 'barbar',
'baz' => 'bazbaz',
default => null,
};
}
…will be transpiled to its equivalent PHP 7.4 version, using the switch
operator:
function getFieldValue(string $fieldName): ?string
{
switch ($fieldName) {
case 'foo':
return 'foofoo';
case 'bar':
return 'barbar';
case 'baz':
return 'bazbaz';
default:
return null;
}
}
The nullsafe operator was also introduced in PHP 8.0:
public function getValue(TypeResolverInterface $typeResolver): ?string
{
return $this->getResolver($typeResolver)?->getValue();
}
The transpiled code needs to assign the value of the operation to a new variable first, as to avoid executing the operation twice:
public function getValue(TypeResolverInterface $typeResolver): ?string
{
return ($val = $this->getResolver($typeResolver)) ? $val->getValue() : null;
}
The constructor property promotion feature, also introduced in PHP 8.0, allows developers to write less code:
class QueryResolver
{
function __construct(protected QueryFormatter $queryFormatter)
{
}
}
When transpiling it for PHP 7.4, the full piece of code is produced:
class QueryResolver
{
protected QueryFormatter $queryFormatter;
function __construct(QueryFormatter $queryFormatter)
{
$this->queryFormatter = $queryFormatter;
}
}
The transpiled code above contains typed properties, which were introduced in PHP 7.4. Transpiling that code down to PHP 7.3 replaces them with docblocks:
class QueryResolver
{
/**
* @var QueryFormatter
*/
protected $queryFormatter;
function __construct(QueryFormatter $queryFormatter)
{
$this->queryFormatter = $queryFormatter;
}
}
PHP Packages
The following libraries are being transpiled for production:
Pros and Cons of Transpiling PHP
The benefit of transpiling PHP has already been described: it allows the source code to use PHP 8.0 (i.e. the latest version of PHP), which will be transformed to a lower version for PHP for production to run in a legacy application or environment.
This effectively allows us to become better developers, producing code with higher quality. This is because our source code can use PHP 8.0’s union types, PHP 7.4’s typed properties, and the different types and pseudo-types added to each new version of PHP (mixed
from PHP 8.0, object
from PHP 7.2), among other modern features of PHP.
Using these features, we can better catch bugs during development and write code that’s easier to read.
Now, let’s take a look at the drawbacks.
It Must Be Coded And Maintained
Rector can transpile code automatically, but the process will likely require some manual input to make it work with our specific setup.
Third-Party Libraries Must Also Be Transpiled
This becomes an issue whenever transpiling them produces errors since we must then delve into their source code to find out the possible reason. If the issue can be fixed and the project is open source, we will need to submit a pull request. If the library is not open source, we may hit a roadblock.
Rector Does Not Inform Us When The Code Cannot Be Transpiled
If the source code contains PHP 8.0 attributes or any other feature that cannot be transpiled, we cannot proceed. However, Rector will not check out this condition, so we need to do it manually. This may not be a big problem concerning our own source code since we are already familiar with it, but it could become an obstacle concerning third-party dependencies.
Debugging Information Uses The Transpiled Code, Not The Source Code
When the application produces an error message with a stack trace in production, the line number will point to the transpiled code. We need to convert back from transpiled to original code to find the corresponding line number in the source code.
The Transpiled Code Must Also Be Prefixed
Our transpiled project and some other library also installed in the production environment could use the same third-party dependency. This third-party dependency will be transpiled for our project and keep its original source code for the other library. Hence, the transpiled version must be prefixed via PHP-Scoper, Strauss, or some other tool to avoid potential conflicts.
Transpiling Must Take Place During Continuous Integration (CI)
Because the transpiled code will naturally override the source code, we should not run the transpiling process on our development computers, or we’ll risk creating side effects. Running the process during a CI run is more suitable (more on this below).
How to Transpile PHP
First, we need to install Rector in our project for development:
composer require rector/rector --dev
We then create a rector.php
configuration file in the root directory of the project containing the required sets of rules. To downgrade code from PHP 8.0 to 7.1, we use this config:
use Rector\Set\ValueObject\DowngradeSetList;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$containerConfigurator->import(DowngradeSetList::PHP_80);
$containerConfigurator->import(DowngradeSetList::PHP_74);
$containerConfigurator->import(DowngradeSetList::PHP_73);
$containerConfigurator->import(DowngradeSetList::PHP_72);
};
To make sure the process executes as expected, we can run Rector’s process
command in dry mode, passing the location(s) to process (in this case, all files under the folder src/
):
vendor/bin/rector process src --dry-run
To perform the transpiling, we run Rector’s process
command, which will modify the files within their existing location:
vendor/bin/rector process src
Please notice: if we run rector process
in our development computers, the source code will be converted in place, under src/
. However, we want to produce the converted code in a different location not to override the source code when downgrading code. For this reason, running the process is most suitable during continuous integration.
Optimizing the Transpiling Process
To generate a transpiled deliverable for production, only the code for production must be converted; code needed only for development can be skipped. That means we can avoid transpiling all tests (for both our project and its dependencies) and all dependencies for development.
Concerning tests, we will already know where the ones for our project are located — for instance, under the folder tests/
. We must also find out where the ones for the dependencies are — for example, under their subfolders tests/
, test/
and Test/
(for different libraries). Then, we tell Rector to skip processing these folders:
return static function (ContainerConfigurator $containerConfigurator): void {
// ...
$parameters->set(Option::SKIP, [
// Skip tests
'*/tests/*',
'*/test/*',
'*/Test/*',
]);
};
Concerning dependencies, Composer knows which ones are for development (those under entry require-dev
in composer.json
) and which ones are for production (those under entry require
).
To retrieve from Composer the paths of all dependencies for production, we run:
composer info --path --no-dev
This command will produce a list of dependencies with their name and path, like this:
brain/cortex /Users/leo/GitHub/leoloso/PoP/vendor/brain/cortex
composer/installers /Users/leo/GitHub/leoloso/PoP/vendor/composer/installers
composer/semver /Users/leo/GitHub/leoloso/PoP/vendor/composer/semver
guzzlehttp/guzzle /Users/leo/GitHub/leoloso/PoP/vendor/guzzlehttp/guzzle
league/pipeline /Users/leo/GitHub/leoloso/PoP/vendor/league/pipeline
We can extract all the paths and feed them into the Rector command, which will then process our project’s src/
folder plus those folders containing all dependencies for production:
$ paths="$(composer info --path --no-dev | cut -d' ' -f2- | sed 's/ //g' | tr '\n' ' ')"
$ vendor/bin/rector process src $paths
A further improvement can prevent Rector from processing those dependencies already using the target PHP version. If a library has been coded with PHP 7.1 (or any version below), then there’s no need to transpile it to PHP 7.1.
To achieve this, we can obtain the list of libraries requiring PHP 7.2 and above and process only those. We will obtain the names of all these libraries via Composer’s why-not
command, like this:
composer why-not php "7.1.*" | grep -o "\S*\/\S*"
Because this command does not work with the --no-dev
flag, to include only dependencies for production, we first need to remove the dependencies for development and regenerate the autoloader, execute the command, and then add them again:
$ composer install --no-dev
$ packages=$(composer why-not php "7.1.*" | grep -o "\S*\/\S*")
$ composer install
Composer’s info --path
command retrieves the path for a package, with this format:
# Executing this command
$ composer info psr/cache --path
# Produces this response:
psr/cache /Users/leo/GitHub/leoloso/PoP/vendor/psr/cache
We execute this command for all items in our list to obtain all paths to transpile:
for package in $packages
do
path=$(composer info $package --path | cut -d' ' -f2-)
paths="$paths $path"
done
Finally, we provide this list to Rector (plus the project’s src/
folder):
vendor/bin/rector process src $paths
Pitfalls to Avoid When Transpiling Code
Transpiling code could be considered an art, often requiring tweaks specific to the project. Let’s see a few problems we may come into.
Chained Rules Are Not Always Processed
A chained rule is when a rule needs to convert the code produced by a previous rule.
For instance, library symfony/cache
contains this code:
final class CacheItem implements ItemInterface
{
public function tag($tags): ItemInterface
{
// ...
return $this;
}
}
When transpiling from PHP 7.4 to 7.3, function tag
must undergo two modifications:
The end result should be this one:
final class CacheItem implements ItemInterface
{
public function tag($tags)
{
// ...
return $this;
}
}
However, Rector only outputs the intermediate stage:
final class CacheItem implements ItemInterface
{
public function tag($tags): self
{
// ...
return $this;
}
}
The issue is that Rector cannot always control the order in which the rules are applied.
The solution is to identify which chained rules were left unprocessed, and execute a new Rector run to apply them.
To identify the chained rules, we run Rector twice on the source code, like this:
$ vendor/bin/rector process src
$ vendor/bin/rector process src --dry-run
The first time, we run Rector as expected, to execute the transpiling. The second time, we use the --dry-run
flag to discover if there are still changes to be made. If there are, the command will exit with an error code, and the “diff” output will indicate which rule(s) can still be applied. That would mean that the first run was not complete, with some chained rule not being processed.
Once we’ve identified the unapplied chained rule (or rules), we can then create another Rector config file — for instance, rector-chained-rule.php
will execute the missing rule. Instead of processing a full set of rules for all files under src/
, this time, we can run the specific missing rule on the specific file where it needs to be applied:
// rector-chained-rule.php
use Rector\Core\Configuration\Option;
use Rector\DowngradePhp74\Rector\ClassMethod\DowngradeSelfTypeDeclarationRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(DowngradeSelfTypeDeclarationRector::class);
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::PATHS, [
__DIR__ . '/vendor/symfony/cache/CacheItem.php',
]);
};
Finally, we tell Rector on its second pass to use the new config file via input --config
:
# First pass with all modifications
$ vendor/bin/rector process src
# Second pass to fix a specific problem
$ vendor/bin/rector process --config=rector-chained-rule.php
Composer Dependencies May Be Inconsistent
Libraries could declare a dependency to be slated for development (i.e. under require-dev
in composer.json
), yet still, reference some code from them for production (such as on some files under src/
, not tests/
).
Usually, this isn’t a problem because that code may not be loaded on production, so there will never be an error on the application. However, when Rector processes the source code and its dependencies, it validates that all referenced code can be loaded. Rector will throw an error if any file references some piece of code from a non-installed library (because it was declared to be needed for development only).
For instance, class EarlyExpirationHandler
from Symfony’s Cache component implements interface MessageHandlerInterface
from the Messenger component:
class EarlyExpirationHandler implements MessageHandlerInterface
{
//...
}
However, symfony/cache
declares symfony/messenger
to be a dependency for development. Then, when running Rector on a project which depends on symfony/cache
, it will throw an error:
[ERROR] Could not process "vendor/symfony/cache/Messenger/EarlyExpirationHandler.php" file, due to:
"Analyze error: "Class Symfony\Component\Messenger\Handler\MessageHandlerInterface not found.". Include your files in "$parameters->set(Option::AUTOLOAD_PATHS, [...]);" in "rector.php" config.
See https://github.com/rectorphp/rector#configuration".
There are three solutions to this issue:
- In the Rector config, skip processing the file that references that piece of code:
return static function (ContainerConfigurator $containerConfigurator): void {
// ...
$parameters->set(Option::SKIP, [
__DIR__ . '/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php',
]);
};
- Download the missing library and add its path to be autoloaded by Rector:
return static function (ContainerConfigurator $containerConfigurator): void {
// ...
$parameters->set(Option::AUTOLOAD_PATHS, [
__DIR__ . '/vendor/symfony/messenger',
]);
};
- Have your project depend on the missing library for production:
composer require symfony/messenger
Transpiling and Continuous Integration
As mentioned earlier, in our development computers we must use the --dry-run
flag when running Rector, or otherwise, the source code will be overridden with the transpiled code. For this reason, it’s more suitable to run the actual transpiling process during continuous integration (CI), where we can spin up temporary runners to execute the process.
An ideal time to execute the transpiling process is when generating the release for our project. For instance, the code below is a workflow for GitHub Actions, which creates the release of a WordPress plugin:
name: Generate Installable Plugin and Upload as Release Asset
on:
release:
types: [published]
jobs:
build:
name: Build, Downgrade and Upload Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/[email protected]
- name: Downgrade code for production (to PHP 7.1)
run: |
composer install
vendor/bin/rector process
sed -i 's/Requires PHP: 7.4/Requires PHP: 7.1/' graphql-api.php
- name: Build project for production
run: |
composer install --no-dev --optimize-autoloader
mkdir build
- name: Create artifact
uses: montudor/[email protected]
with:
args: zip -X -r build/graphql-api.zip . -x *.git* node_modules/\* .* "*/\.*" CODE_OF_CONDUCT.md CONTRIBUTING.md ISSUE_TEMPLATE.md PULL_REQUEST_TEMPLATE.md rector.php *.dist composer.* dev-helpers** build**
- name: Upload artifact
uses: actions/[email protected]
with:
name: graphql-api
path: build/graphql-api.zip
- name: Upload to release
uses: JasonEtco/[email protected]
with:
args: build/graphql-api.zip application/zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
This workflow contains a standard procedure to release a WordPress plugin via GitHub Actions. The new addition, to transpile the plugin’s code from PHP 7.4 to 7.1, happens in this step:
- name: Downgrade code for production (to PHP 7.1)
run: |
vendor/bin/rector process
sed -i 's/Requires PHP: 7.4/Requires PHP: 7.1/' graphql-api.php
Taken together, this workflow now performs the following steps:
- Checks out the source code for a WordPress plugin from its repository, written with PHP 7.4
- Installs its Composer dependencies
- Transpiles its code from PHP 7.4 to 7.1
- Modifies the “Requires PHP” entry in the plugin’s main file’s header from
"7.4"
to"7.1"
- Removes the dependencies needed for development
- Creates the plugin’s .zip file, excluding all unneeded files
- Uploads the .zip file as a release asset (and, in addition, as an artifact to the GitHub Action)
Testing the Transpiled Code
Once the code has been transpiled to PHP 7.1, how do we know that it works well? Or, in other words, how do we know it has been thoroughly converted, and no remnants of higher versions of PHP code were left behind?
Similar to transpiling the code, we can implement the solution within a CI process. The idea is to set up the runner’s environment with PHP 7.1 and run a linter on the transpiled code. If any piece of code is not compatible with PHP 7.1 (such as a typed property from PHP 7.4 that wasn’t converted), then the linter will throw an error.
A linter for PHP that works well is PHP Parallel Lint. We can install this library as a dependency for development in our project, or have the CI process install it as a standalone Composer project:
composer create-project php-parallel-lint/php-parallel-lint
Whenever the code contains PHP 7.2 and above, PHP Parallel Lint will throw an error like this one:
Run php-parallel-lint/parallel-lint layers/ vendor/ --exclude vendor/symfony/polyfill-ctype/bootstrap80.php --exclude vendor/symfony/polyfill-intl-grapheme/bootstrap80.php --exclude vendor/symfony/polyfill-intl-idn/bootstrap80.php --exclude vendor/symfony/polyfill-intl-normalizer/bootstrap80.php --exclude vendor/symfony/polyfill-mbstring/bootstrap80.php
PHP 7.1.33 | 10 parallel jobs
............................................................ 60/2870 (2 %)
............................................................ 120/2870 (4 %)
...
............................................................ 660/2870 (22 %)
.............X.............................................. 720/2870 (25 %)
............................................................ 780/2870 (27 %)
...
............................................................ 2820/2870 (98 %)
.................................................. 2870/2870 (100 %)
Checked 2870 files in 15.4 seconds
Syntax error found in 1 file
------------------------------------------------------------
Parse error: layers/GraphQLAPIForWP/plugins/graphql-api-for-wp/graphql-api.php:55
53| '0.8.0',
54| \__('GraphQL API for WordPress', 'graphql-api'),
> 55| ))) {
56| $plugin->setup();
57| }
Unexpected ')' in layers/GraphQLAPIForWP/plugins/graphql-api-for-wp/graphql-api.php on line 55
Error: Process completed with exit code 1.
Let’s add the linter into our CI’s workflow. The steps to execute to transpile code from PHP 8.0 to 7.1 and test it are:
- Check out the source code
- Have the environment run PHP 8.0, so Rector can interpret the source code
- Transpile the code to PHP 7.1
- Install the PHP linter tool
- Switch the environment’s PHP version to 7.1
- Run the linter on the transpiled code
This GitHub Action workflow does the job:
name: Downgrade PHP tests
jobs:
main:
name: Downgrade code to PHP 7.1 via Rector, and execute tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/[email protected]
- name: Set-up PHP
uses: shivammathur/[email protected]
with:
php-version: 8.0
coverage: none
- name: Local packages - Downgrade PHP code via Rector
run: |
composer install
vendor/bin/rector process
# Prepare for testing on PHP 7.1
- name: Install PHP Parallel Lint
run: composer create-project php-parallel-lint/php-parallel-lint --ansi
- name: Switch to PHP 7.1
uses: shivammathur/[email protected]
with:
php-version: 7.1
coverage: none
# Lint the transpiled code
- name: Run PHP Parallel Lint on PHP 7.1
run: php-parallel-lint/parallel-lint src/ vendor/ --exclude vendor/symfony/polyfill-ctype/bootstrap80.php --exclude vendor/symfony/polyfill-intl-grapheme/bootstrap80.php --exclude vendor/symfony/polyfill-intl-idn/bootstrap80.php --exclude vendor/symfony/polyfill-intl-normalizer/bootstrap80.php --exclude vendor/symfony/polyfill-mbstring/bootstrap80.php
Please notice that several bootstrap80.php
files from Symfony’s polyfill libraries (which need not be transpiled) must be excluded from the linter. These files contain PHP 8.0, so the linter would throw errors when processing them. However, excluding these files is safe since they will be loaded on production only when running PHP 8.0 or above:
if (\PHP_VERSION_ID >= 80000) {
return require __DIR__.'/bootstrap80.php';
}
Summary
This article taught us how to transpile our PHP code, allowing us to use PHP 8.0 in the source code and create a release that works on PHP 7.1. Transpiling is done via Rector, a PHP reconstructor tool.
Transpiling our code makes us better developers since we can better catch bugs in development and produce code that’s naturally easier to read and understand.
Transpiling also enables us to decouple our code with specific PHP requirements from the CMS. We can now do so if we desire to use the latest version of PHP to create a publicly available WordPress plugin or Drupal module without severely restricting our userbase.
Do you have any questions left about transpiling PHP? Let us know in the comments section!
Get all your applications, databases and WordPress sites online and under one roof. Our feature-packed, high-performance cloud platform includes:
- Easy setup and management in the MyKinsta dashboard
- 24/7 expert support
- The best Google Cloud Platform hardware and network, powered by Kubernetes for maximum scalability
- An enterprise-level Cloudflare integration for speed and security
- Global audience reach with up to 35 data centers and 275 PoPs worldwide
Get started with a free trial of our Application Hosting or Database Hosting. Explore our plans or talk to sales to find your best fit.