On November 26th 2020, there will be a new version of PHP released. This new release has a number of new features, as well as implementing some non-backward compatible changes. Seravo has already started testing and deploying the release candidates of PHP 8.0 to our servers.
Published
Updated

On November 26th 2020, there will be a new version of PHP released. This new release has a number of new  features, as well as implementing some non-backward compatible changes. Seravo has already started testing and deploying the release candidates of PHP 8.0 to our servers.

Important: WordPress 5.5 is not compatible with PHP 8.0

As a major release, there are some major changes. Many of these are breaking changes. Developers need to be aware that some of these can have severe impacts on the plugins and themes they maintain. Our initial tests of release candidates mean we can confirm that WordPress 5.5 is not PHP 8.0 safe. If you attempt to upgrade to PHP 8.0 without running at least WordPress 5.6, you will experience major front-end issues. Therefore to test PHP 8.0 with the projects that you maintain, prior to release, you will need to upgrade to WordPress 5.6 beta version. Find out more about this issue, and how to test with WordPress 5.6.

What Are The Major Changes To PHP 8.0 That Developers Need To Be Aware Of?

WordPress developers and website managers may expect there to be significant improvements in PHP performance with this next release. While there are improvements in benchmark results, these are not to the levels experienced when PHP upgraded from 5.6 to 7.0 releases. The main improvements in performance are focused around mathematical processing and functionality, mainly due to the ‘Just In Time’ compiler (JIT). This is a major change in terms of how PHP operates, but shouldn’t affect how you develop your existing PHP projects.

However, there are major code changes that developers and maintainers need to be aware of. Some of our personal favourites (i.e. the ones that could REALLY break things) include: the new reserved keyword match; changes to constructor method naming conventions; deprecation of case-insensitive constants created with define(); and union types.

Breaking Change: match Is Now A Reserved Keyword

If you are currently using match in your PHP code as a function name, class name or method name, then expect things to break when your users upgrade to the PHP 8.0 release. If you are unfamiliar with the list of reserved keywords within PHP, then you can find a list of keywords from PHP.net (although this has not been updated with the PHP 8.0 release information). At present, some PHPCS tools do not identify the match keyword issues that may arise.

What Will Happen On Your WordPress Site If The match Keyword Is Used?

There are two scenarios where you may encounter errors using match as a function, class or method name: when you upgrade your php version, or when you have upgraded and attempt to activate a new theme or plugin.

When Upgrading To PHP 8.0

If you upgrade to PHP 8.0 and the match keyword has been used to name a function, class or method, you will be met with HTTP 500 error. With WordPress, this is can be shown through the “There has been a critical error on your website” message. 

If you see this message after upgrading to PHP 8.0, check your error logs to identify the cause.
If you see this message after upgrading to PHP 8.0, check your error logs to identify the cause.

In your PHP error logs you may see a similar entry:

PHP Parse error:  syntax error, unexpected token "match", expecting "(" 

The error message should also tell you exactly which file and which line number that the offending reserved keyword is used. 

Activating Plugins After Upgrading To PHP 8.0

You will also encounter problems when you attempt to activate an incompatible plugin after you have completed your initial PHP 8.0 upgrade. This will create the following error:

This plugin could not be activated because in PHP 8.0 ‘match’ is a reserved keyword
This plugin could not be activated because in PHP 8.0 ‘match’ is a reserved keyword

Helpfully, if you have enabled debug logging, you should also see this error in your PHP logs as well. If you are testing your code changes on Seravo, then you can find your PHP logs at /data/log/php-error.log. Alternatively, you can also use our wp-watch-php command line tool, which will allow you to get live updates on PHP errors and warnings within your terminal window.

How To Fix This Issues With The Keyword ‘Match’:

Fixing this problem is theoretically straightforward. You cannot use match as a function, class or method name in PHP 8.0, so you need to rename it. Because you have already checked your error logs, you should know where the declarations have been made. Make sure that you also check any other references that call on these declarations as well. If you are using a WordPress add_action or add_filterfunction to call these, remember to include these variations in any search and replace queries that you run.

If you are using a regex search to find references to a function called match, you could use a needle such as:

/*
* This Regex expression will find 'match' when used with
* add_action, including when used within a class
*/

/add_action\(.*'match'/

/*
* This Regex expression will find 'match' when used with
* add_filter, including when used in a class
*/

/add_filter\(.*'match'/

Breaking Change: Constructor methods should only be named __construct

This is another potential site-breaker, especially if you are using Object-Orientated Programming (OOP). In previous versions of PHP, it was possible for the class constructor function to be automatically called if the function was named the same as the class. If you have been keeping your code maintained, and have been dealing with the warnings (not fatal errors) that started to arise in PHP 7 releases (as shown below), then this issue should not be a problem for you. However, if you have ignored these warnings, then you will run into issues when your users try to run your code on PHP 8.0 when they upgrade.

Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP

Prior to the PHP 8.0 there were two styles of constructor methods within a class. The old-style method, synonymous with PHP 4 was to assign the constructor function the same name as the class. In versions prior to the PHP 8.0 this was allowed as a constructor method for backward compatibility purposes. As of PHP 8.0 the __construct function is now the only one that will trigger when a new class is initialized.

<?php

/*
* Old Style Constructors For Backwards Compatibility
*/

class oldExampleClass{
    public function oldExampleClass(){
        // Your Constructor Code
    } 
}

/*
* PHP 8.0 Style Constructors
*/

class newExampleClass{
    public function __construct(){
        // Your Constructor Code
    } 
}

Using the Seravo wp-check-php-compatibility command-line tool to check compatibility before upgrading

One of the developer tools that we have implemented is wp-check-php-compatibility which allows developers and maintainers to check for compatibility pre-upgrade. This command-line test scans the code of WordPress core, themes and plugins against PHPCS rules. It then outputs any warnings or error messages that would be triggered if an upgrade takes place. You can find out more about this useful command-line tool from our comprehensive post about PHPCS.

A simple command-line tool that is invaluable when checking your code compatibility when upgrading to PHP 8.0

With this tool, you can check an entire WordPress installation at once, or you can run the tests against a specific directory. This is incredibly useful when you are only want to analyze the code that you maintain. Below is an example of how to run this command, and also the output you would expect if you are using the PHP 4 constructor style within a class.

$ wp-check-php-compatibility --php 8.0 /data/wordpress/htdocs/wp-content/plugins/test-bm

Scanning code in path /data/wordpress/htdocs/wp-content/plugins/test-bm for PHP 8.0 compatibility...
W 1 / 1 (100%)



FILE: /data/wordpress/htdocs/wp-content/plugins/test-bm/test-bm.php
---------------------------------------------------------------------------------------------
FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
---------------------------------------------------------------------------------------------
 13 | WARNING | Use of deprecated PHP4 style class constructor is not supported since PHP 7.
---------------------------------------------------------------------------------------------

Time: 35ms; Memory: 8MB

Scan complete. Results are stored in the logfile /data/log/php-compatibility.log

It is worth noting that the example given here uses positional arguments to define which plugin folder to check, and which PHP version to run the tests against. In this example, we are testing an example plugin designed for the purpose, and using the PHPCS rules for the PHP 8.0 release candidate. Before running these tests, you should familiarise yourself with the documentation for the wp-check-php-compatibility function. You can also use this command to check for errors and warnings after you have upgraded as well. This can be an especially useful tool when you are looking for an issue with your code that does not generate a log entry.

Detecting PHP 4 Style Class Constructors After Upgrading To PHP 8.0

Unfortunately this error in class constructor naming is difficult to detect after upgrading from PHP 7.4 to the new version. Since PHP 7.0 if you have used the old-style of constructor naming, you should have been receiving warnings like the one below:

PHP Deprecated:  Methods with the same name as their class will not be constructors in a future version of PHP;

In this case, the constructor method would still trigger when the class initialised. As of PHP 8.0 this is no longer the case. The constructor method will not trigger and you will receive no warning or error message regarding this. The only error messages you will receive will occur if you trigger a public function within the class, that depends on a process that was supposed to occur in your constructor function. If you have a badly designed class, with a constructor that isn’t named __construct, then you could run into serious problems if your code is executed on PHP 8.0 when it is released. This is an example of why you should be following the advisory warnings that have been appearing in previous PHP versions.

As previously mentioned, the best way to analyze if this problem exists with your code is to assess it against PHPCS rules. Of course, there is a handy tool called wp-check-php-compatibility, and it is already installed on your Seravo WordPress installation.

Fixing Constructor Naming Issues

The fix for this is simple. Find any classes that use a function that duplicates the class name to trigger the class constructor using the command-line tool. Then change the name of that function to __construct.

If you are not a Seravo customer, or are not taking advantage of our pre-installed PHPCS codesniffer tools, you can read more about how to use these in your development environment here. Alternatively, you can use IDE plugins such as a PHP codesniffer extension for VS Code, or even running your code on a local development environment. However, you should be aware that the warnings about this issue maybe inconsistent, depending on what version of PHP you are running on your local machine, and how you have setup your local development environment.

Breaking Change: PHP Constants Can No Longer Be Defined As Case Insensitive Or Be Called Insensitively

If you have been adopting the WordPress Coding Standards, then this major change should not be an issue. Constants that you use in your PHP code should be defined in UPPERCASE, as prescribed the WordPress Coding Standards.

“Constants should be in all upper-case with underscores separating words”

WordPress Coding Standards / PHP Coding Standards

This is even a suggested convention on the PHP documentation.

“A constant is case-sensitive by default. By convention, constant identifiers are always uppercase.

The name of a constant follows the same rules as any label in PHP. A valid constant name starts with a letter or underscore, followed by any number of letters, numbers, or underscores.”

PHP.net : Constants

Therefore, if you are following the conventions of PHP and WordPress Coding Standards, then this should not be a problem. However, if you have not adhered to these standards, or are using code from other developers that does not, then you can expect to see the following issues arise after upgrading to the PHP 8.0 release.

Prior to PHP 8.0 developers were able to define a constant as case-insensitive using a third boolean parameter. This meant you could cover up the inconsistent use of mixed case constants within your code.

<?php

define('MY_CONSTANT_1', 'Hello World', true);
echo My_Constant_1;     // prints Hello World
echo MY_CONSTANT_1;     // also prints Hello World

define('MY_CONSTANT_2', 'Hello World');
echo My_Constant_2;     // Warning: Use of undefined constant  
echo MY_CONSTANT_2;     // prints Hello World

define('My_Constant_3', 'Hello World', true);
echo My_Constant_3;     // prints Hello World
echo MY_CONSTANT_3;     // also prints Hello World

define('My_Constant_4', 'Hello World');
echo My_Constant_4;     // prints Hello World
echo MY_CONSTANT_4;     // Warning: Use of undefined constant

Define() & PHP 8.0: The Third Parameter Is Now Ignored

However, the third parameter of the define() function is now ignored in PHP 8.0 which means you can no longer define case-insensitive constants. In the next examples will cause fatal errors in PHP 8.0 release.

<?php

/**
* Define using case insensitive parameter set to true generating PHP Warning
* Parameter ignored by PHP 8.0
* Use of constant in incorrect case resulting in a Fatal Error
*/

define('My_Constant', 'Hello World', true);
echo MY_CONSTANT;	

This example attempts to define My_Constant using the case insensitive argument set to true. PHP 8.0 ignores this argument, meaning that this constant can only be retrieved using an exact, case-sensitive match. Therefore PHP does not recognise My_Constant and MY_CONSTANT as the same constant. This code will generate the following errors:

PHP Warning:  define(): Argument #3 ($case_insensitive) is ignored since declaration of case-insensitive constants is no longer supported

PHP Fatal error:  Uncaught Error: Undefined constant "MY_CONSTANT"

A similar error will also occur in PHP 8.0 if you define a constant using WordPress Coding Standards naming conventions, using the true parameter in the define() function, and then attempt to reference the constant in a case-insensitive manner.

<?php

/**
* Define using case insensitive parameter set to true generating PHP Warning
* Parameter ignored by PHP 8.0
* Use of constant in incorrect case resulting in a Fatal Error
*/

define('MY_CONSTANT', 'Hello World', true);
echo My_Constant;	

This example attempts to define MY_CONSTANT using the case insensitive argument set to true. PHP 8.0 ignores this argument, meaning that this constant can only be retrieved using an exact, case-sensitive match. Therefore when My_Constant is requested, it will generate the following errors:

PHP Warning:  define(): Argument #3 ($case_insensitive) is ignored since declaration of case-insensitive constants is no longer supported

PHP Fatal error:  Uncaught Error: Undefined constant "My_Constant"

This means, that if you are using case insensitive constants, and you have ignored all the warnings that have been triggered since PHP 7, then fatal errors will occur on WordPress websites when you upgrade to PHP 8.0 if you have not fixed this problem within your code.

How To Detect This Incompatibility In Your Themes & Plugins

If you wish to detect potential issues before running your code on PHP 8.0, then we recommend that you use the wp-check-php-compatibility command-line tool. Currently it is detecting these errors when the constant is defined, but not necessarily when it is referenced. Below is an example of our command-line tool detecting that the case_insensitive parameter has been detected in a define() function.

$ wp-check-php-compatibility --php 8.0 /data/wordpress/htdocs/wp-content/plugins/test-bm

Scanning code in path /data/wordpress/htdocs/wp-content/plugins/test-bm for PHP 8.0 compatibility...
W 1 / 1 (100%)



FILE: /data/wordpress/htdocs/wp-content/plugins/test-bm/test-bm.php
--------------------------------------------------------------------------------------------------
FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
--------------------------------------------------------------------------------------------------
 7 | WARNING | The "case_insensitive" parameter for function define() is deprecated since PHP 7.3
--------------------------------------------------------------------------------------------------

Time: 39ms; Memory: 8MB

However, the best method of detecting issues with both definition and with referencing insensitively is to use the php-error log while running PHP 7.3 or PHP 7.4, where warning similar to the following will be triggered. Alternatively, using the wp-watch-php is an easy way of accessing the latest php error log entries, also allowing live updates of errors to be received directly into your terminal window via SSH.

New Feature: Union Types To Declare Multiple Expected Output & Incoming Variables Types

PHP 7.0 introduced the ability to declare data types for variables being declared, and for the return values of a function. While PHP is a dynamically typed language (i.e. it doesn’t require the same strict static definitions required by languages such as C), the ability to define what was expected, either as input or output from a function, does provide many benefits. If you do attempt to use data types with PHP 5 then you may encounter errors similar to the following:

// Error message for type hinting on function arguments in PHP 5
Catchable fatal error: Argument 1 passed to your_function_name() must be an instance of int, integer given

// Error message for type hinting on function output in PHP 5
Parse error: syntax error, unexpected ':', expecting ';' or '{'

WordPress still supports PHP 5.6, and if providing backward compatibility matters then you should not be using data types in your plugin or theme functions. There is also an issue with backwards compatibility if you implement PHP 8.0 union types, as this will break on sites running PHP 7.4 or below. The reality is that unless you are going to withdraw support for PHP 5.6 for your code, then PHP types should not be used. It could be argued that PHP types are one of the main reasons (beyond the obvious ones to do with speed and security updates), that WordPress should withdraw support for older PHP versions. When this support is officially withdrawn, developers can start to take advantage of these enhanced features. As PHP 8.0 matures and is surpassed by subsequent releases, Union Types will no doubt become one of the main arguments to withdraw PHP 7.4 support for WordPress when the time comes.

However, at this time, that possibility is in a distant future.

Why Should You Be Using Types In PHP?

Simply, it’s good practice. If you are passing arguments into a function, and then performing actions upon that data, returning a value, then these should be predictable. One of the advantages of using types, is that it aids developers in debugging their code. Take the following examples of code:


<?php

function no_types_set($variable_items){
	$return_value = 0;
	foreach($variable_items as $variable_item){
		$return_value ++;
      }
      return $return_value;
}

In this scenario if a string is mistakenly passed into the no_types_function the following warning will be generated, but a value of 0 will be returned, and will not generate a fatal error, but only a warning. As a developer, you then have to start searching for the reason that an invalid argument was passed to the foreach loop.


Warning: Invalid argument supplied for foreach() 

Declaring types for function arguments

However, if you define the type of variable that should be passed to the function, it allows for more effective debugging. First, a fatal error is generated. There is also a better explanation as to the issue being with an invalid argument being passed.

<?php

function types_set(array $variable_items){
	$return_value = 0;
	foreach($variable_items as $variable_item){
		$return_value ++;
      }
      return $return_value;
}

In the second example, if a string is passed into the function, as fatal error will be generated with the error message being generated being similar to the one below. The reason for this is because we have statically typed the data type for the variable that we are expecting. If a different data type is passed, a fatal error occurs. This feature was introduced in the PHP 7.0 release and is know as Type Hinting or Type Declaration.

Fatal error: Uncaught TypeError: Argument 1 passed to types_set() must be of the type array, string given

Interestingly, a parameter that is passed as an argument is not required to remain as the same type throughout the function. The type of the parameter is only checked when the argument is passed into the function, and is not checked on reassignment or change.

Declared types of reference parameters are checked on function entry, but not when the function returns, so after the function had returned, the argument’s type may have changed.

PHP.net

The following code example shows how you can pass a types argument into a function, change the data type (e.g. from int to string) and return the variable with the new data type with no errors or warnings being generated by PHP.

<?php

/**
* This function will print "Hello World"
*
* It passes the integer 12345 to chang_int_to_string() 
* and uses echo to print the result
*
*/

function print_string_from_int()
    echo change_int_to_string(12345);
}

/**
* This function checks that the $int_to_change argument is an integer 
*
* It then changes the variable to contain the string "Hello World" 
* and returns this variable instead
*
*/

function change_int_to_string(int $int_to_change){
    $int_to_change = "Hello World";
    return $int_to_change;
}

With this code, if you passed a data type that wasn’t an integer, a fatal error would be generated, such as the one in our previous example. However, if you pass an integer and the change the variable data type within the function, then no warnings or errors occur. This means that the although the argument appears to be statically typed, it still retains the abilities associated with a dynamically typed language like PHP.

Declaring Types For Function Return

As well as declaring what data types can enter a function via arguments, it is also possible for developers to declare what data type should be returned by a function. The format should appear similar to the following example, where it is statically declared that the function return an integer (and not a string).

<?php

/**
* Function can only return an int
* Returning a string will cause an error
*
*/

function types_set() : int {
       $return_value = strlen(“Hello World”);
       return "Hello World";
}

This is a great way of ensuring that your function output does not produce anything unexpected. In this example, we are stating that the return value should be an integer. This throws an error because we are returning a string. Whereas, if we returned the value of $return_value, then this would pass validation. The only issue comes when passing an integer or float value, as PHP does initiate some type switching on your behalf. However, in this instance, this function will create a fatal error similar to the following:

Uncaught TypeError: Return value of types_set() must be of the type int, string returned

The next example shows how you can use type hinting on function output to ensure your foreach loop receives an array instead of a string,

<?php

function no_types_set(){
    $variable_items = get_variable_items();
    $return_value = 0;
    foreach($variable_items as $variable_item){
        $return_value ++;
    }

    return $return_value;
}

function get_variable_items() : array {
    $return_array = "Hello World";
    return $return_array;
}

In this example, it has been statically typed that the function will return an array. However, this function returns a string, creating the following error messages.

Fatal error: Uncaught TypeError: Return value of get_variable_items() must be of the type array, string returned

TypeError: Return value of get_variable_items() must be of the type array, string returned

This is far more informative than what is returned if a data type is not statically defined. In this code example, we would be passing a string into a foreach() loop, which would produce a non-descriptive fatal error as shown below. Developers likely know the inconvenience of trying to ascertain and trace which error in their code has resulted in an incorrect data type being based into a foreach() loop.

Warning: Invalid argument supplied for foreach()

Why Are Some WordPress Plugins Not Using Types Already?

As a developer, who is creating plugins for use in PHP 7.0 or above, this is a really powerful feature for security, validation and debugging purposes. However, if you are making your plugin backwards compatible with versions of WordPress that are running on PHP 5.6 (or heaven forbid lower), then you simply cannot use these features in your code.

What are union types?

Union types are an extension to the PHP’s typing system. As the name suggests, union types are a combination of at least two separate types (an union, if you will). This means that typing system can be used to describe multiple possible types for the variable. For example, consider a situation, where you would like to calculate a numerical value based on the input variable. If the input variable could be either an int or a float, it would have been impossible to document that in php without commenting. Now, it is possible by using union type int|float, which makes makes it clear that the input variable can be either one.

Should I Upgrade to PHP 8.0?

While keeping an eye on upcoming PHP 8.0 is important for all developers who use PHP and WordPress in their work is important, we do not recommend using PHP 8.0 in production on real sites in nearby future, as it will take months for both WordPress core and all the plugins and themes out there to become PHP 8.0 compatible. However, we do recommend everybody to ensure that their site is now using the latest PHP 7.4 and that at the 7.4 level there are no notices or warnings emitted from your code and website, as many of them become fatal errors in PHP 8.0. Ensuring that a site works perfectly with PHP 7.4 now is the best way to prepare for a future PHP 8.0 upgrade.