In a world where tech never stops evolving, staying current is crucial for both optimal user experience and robust security. PHP, the engine behind WordPress, has transitioned through versions 5.x, 6.x, and 7.x, and now stands at version 8. Many are using the sub-version, 8.2. PHP8 brings a host of improvements and compelling features that we’ll delve into. Importantly, support for older PHP versions will cease on November 28, 2022.
The foremost incentive for migrating to PHP 8 is to guarantee that your WordPress platform runs on a version that regularly receives updates and security patches. Unsupported versions of PHP lack these critical updates, leaving your web applications vulnerable to security risks. Thus, it’s wise to confirm whether your hosting provider offers the most recent PHP versions before deploying a web application.
Another compelling reason to embrace the latest PHP version is to avoid the pitfalls of incompatibility. Operating on outdated versions could jeopardize the functionality of your website—a major concern for any web developer. Supported versions of PHP are continually refined and monitored to fix any issues or vulnerabilities, making it imperative to stay abreast of the latest updates.
PHP 8 brings a notable increase in performance. For instance, PHP 7.4 enabled systems to execute thrice as many REST API requests per second compared to PHP 5.6. PHP 8 takes this a step further, offering performance that is almost four times better. Refer to the table below for detailed speed scores and REST API performance metrics.
Upgrading to a newer PHP version can be a smooth process if you’re prepared. However, potential challenges such as bugs or failed upgrades can occur.
First, make sure that your website is compatible with the latest PHP version. You can check this information through your web hosting control panel. If an upgrade is available, you can usually complete it in a few simple steps. Switching to a supported PHP version through the control panel is generally straightforward.
Note that a PHP version typically has a lifespan of three years, after which it’s advisable to move on to a newer version.
Use a Backup Plugin
With a backup in hand, you can confidently upgrade your PHP version.
Alternatively, some service providers allow the backup and generation of a staging site. If that’s available, try it out.
Create an identical copy of your website in a staging environment to test the PHP upgrade. Benefits of using a staging site include:
Update the staging site to PHP 8.1 or 8.2 and then see what breaks. Current code will be ready for PHP8 in most cases. You may see very few issues. If the result is a disaster, make note of all of the errors that came up, then downgrade back to your previous PHP version and proceed cautiously.
If things go wrong with the “Let ‘er rip” approach, you need to do some detective work.
This list of changes and issues comes from PHP.net:
match is now a reserved keyword.
mixed is now a reserved word, so it cannot be used to name a class, interface or trait, and is also prohibited from being used in namespaces.
Assertion failures now throw by default. If the old behavior is desired, assert.exception=0 can be set in the INI settings.
Methods with the same name as the class are no longer interpreted as constructors. The __construct() method should be used instead.
The ability to call non-static methods statically has been removed. Thus is_callable() will fail when checking for a non-static method with a classname (must check with an object instance).
The (real) and (unset) casts have been removed.
The track_errors ini directive has been removed. This means that php_errormsg is no longer available. The error_get_last() function may be used instead.
The ability to define case-insensitive constants has been removed. The third argument to define() may no longer be true.
The ability to specify an autoloader using an __autoload() function has been removed. spl_autoload_register() should be used instead.
The errcontext argument will no longer be passed to custom error handlers set with set_error_handler().
create_function() has been removed. Anonymous functions may be used instead.
each() has been removed. foreach or ArrayIterator should be used instead.
The ability to unbind this from closures that were created from a method, using Closure::fromCallable() or ReflectionMethod::getClosure(), has been removed.
The ability to unbind this from proper closures that contain uses of this has also been removed.
The ability to use array_key_exists() with objects has been removed. isset() or property_exists() may be used instead.
The behavior of array_key_exists() regarding the type of the key parameter has been made consistent with isset() and normal array access. All key types now use the usual coercions and array/object keys throw a TypeError.
Any array that has a number n as its first numeric key will use n+1 for its next implicit key, even if n is negative.
The default error_reporting level is now E_ALL. Previously it excluded E_NOTICE and E_DEPRECATED.
display_startup_errors is now enabled by default.
Using parent inside a class that has no parent will now result in a fatal compile-time error.
The @ operator will no longer silence fatal errors (E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR, E_PARSE). Error handlers that expect error_reporting to be 0 when @ is used, should be adjusted to use a mask check instead:
<?php
// Replace
function my_error_handler($err_no, $err_msg, $filename, $linenum) {
if (error_reporting() == 0) {
return false;
}
// ...
}// With
function my_error_handler($err_no, $err_msg, $filename, $linenum) {
if (!(error_reporting() & $err_no)) {
return false;
}
// …
}
?>
Additionally, care should be taken that error messages are not displayed in production environments, which can result in information leaks. Please ensure that display_errors=Off is used in conjunction with error logging.
#[ is no longer interpreted as the start of a comment, as this syntax is now used for attributes.
Inheritance errors due to incompatible method signatures (LSP violations) will now always generate a fatal error. Previously a warning was generated in some cases.
The precedence of the concatenation operator has changed relative to bitshifts and addition as well as subtraction.
<?php
echo "Sum: " . $a + $b;
// was previously interpreted as:
echo ("Sum: " . $a) + $b;
// is now interpreted as:
echo "Sum:" . ($a + $b);
?>Arguments with a default value that resolves to null at runtime will no longer implicitly mark the argument type as nullable. Either an explicit nullable type, or an explicit null default value has to be used instead.
<?php
// Replace
function test(int $arg = CONST_RESOLVING_TO_NULL) {}
// With
function test(?int $arg = CONST_RESOLVING_TO_NULL) {}
// Or
function test(int $arg = null) {}
?>A number of warnings have been converted into Error exceptions:
A number of notices have been converted into warnings:
Attempting to assign multiple bytes to a string offset will now emit a warning.
Unexpected characters in source files (such as NUL bytes outside of strings) will now result in a ParseError exception instead of a compile warning.
Uncaught exceptions now go through “clean shutdown”, which means that destructors will be called after an uncaught exception.
The compile time fatal error “Only variables can be passed by reference” has been delayed until runtime, and converted into an “Argument cannot be passed by reference” Error exception.
Some “Only variables should be passed by reference” notices have been converted to “Argument cannot be passed by reference” exception.
The generated name for anonymous classes has changed. It will now include the name of the first parent or interface:
<?php
new class extends ParentClass {};
// -> ParentClass@anonymous
new class implements FirstInterface, SecondInterface {};
// -> FirstInterface@anonymous
new class {};
// -> class@anonymous
?>The name shown above is still followed by a NUL byte and a unique suffix.
Non-absolute trait method references in trait alias adaptations are now required to be unambiguous:
<?php
class X {
use T1, T2 {
func as otherFunc;
}
function func() {}
}
?>If both T1::func() and T2::func() exist, this code was previously silently accepted, and func was assumed to refer to T1::func. Now it will generate a fatal error instead, and either T1::func or T2::func needs to be written explicitly.
The signature of abstract methods defined in traits is now checked against the implementing class method:
<?php
trait MyTrait {
abstract private function neededByTrait(): string;
}class MyClass {
use MyTrait;
// Error, because of return type mismatch.
private function neededByTrait(): int { return 42; }
}
?>
Disabled functions are now treated exactly like non-existent functions. Calling a disabled function will report it as unknown, and redefining a disabled function is now possible.
data:// stream wrappers are no longer writable, which matches the documented behavior.
The arithmetic and bitwise operators +, -, *, /, **, %, <<, >>, &, |, ^, ~, ++, -- will now consistently throw a TypeError when one of the operands is an array, resource or non-overloaded object. The only exception to this is the array + array merge operation, which remains supported.
Float to string casting will now always behave locale-independently.
<?php
setlocale(LC_ALL, "de_DE");
$f = 3.14;
echo $f, "\n";
// Previously: 3,14
// Now: 3.14
?>See printf(), number_format() and NumberFormatter() for ways to customize number formatting.
Support for deprecated curly braces for offset access has been removed.
<?php
// Instead of:
$array{0};
$array{"key"};
// Write:
$array[0];
$array["key"];
?>Applying the final modifier on a private method will now produce a warning unless that method is the constructor.
If an object constructor exit()s, the object destructor will no longer be called. This matches the behavior when the constructor throws.
Namespaced names can no longer contain whitespace: While Foo\Bar will be recognized as a namespaced name, Foo \ Bar will not. Conversely, reserved keywords are now permitted as namespace segments, which may also change the interpretation of code: new\x is now the same as constant('new\x'), not new \x().
Nested ternaries now require explicit parentheses.
debug_backtrace() and Exception::getTrace() will no longer provide references to arguments. It will not be possible to change function arguments through the backtrace.
Numeric string handling has been altered to be more intuitive and less error-prone. Trailing whitespace is now allowed in numeric strings for consistency with how leading whitespace is treated. This mostly affects:
The concept of a “leading-numeric string” has been mostly dropped; the cases where this remains exist in order to ease migration. Strings which emitted an E_NOTICE “A non well-formed numeric value encountered” will now emit an E_WARNING “A non-numeric value encountered” and all strings which emitted an E_WARNING “A non-numeric value encountered” will now throw a TypeError. This mostly affects:
This E_WARNING to TypeError change also affects the E_WARNING “Illegal string offset ‘string'” for illegal string offsets. The behavior of explicit casts to int/float from strings has not been changed.
Magic Methods will now have their arguments and return types checked if they have them declared. The signatures should match the following list:
__call(string $name, array $arguments): mixed__callStatic(string $name, array $arguments): mixed__clone(): void__debugInfo(): ?array__get(string $name): mixed__invoke(mixed $arguments): mixed__isset(string $name): bool__serialize(): array__set(string $name, mixed $value): void__set_state(array $properties): object__sleep(): array__unserialize(array $data): void__unset(string $name): void__wakeup(): voidcall_user_func_array() array keys will now be interpreted as parameter names, instead of being silently ignored.
Declaring a function called assert() inside a namespace is no longer allowed, and issues E_COMPILE_ERROR. The assert() function is subject to special handling by the engine, which may lead to inconsistent behavior when defining a namespaced function with the same name.
Several resources have been migrated to objects. Return value checks using is_resource() should be replaced with checks for false.
curl_init() will now return a CurlHandle object rather than a resource. The curl_close() function no longer has an effect, instead the CurlHandle instance is automatically destroyed if it is no longer referenced.
curl_multi_init() will now return a CurlMultiHandle object rather than a resource. The curl_multi_close() function no longer has an effect, instead the CurlMultiHandle instance is automatically destroyed if it is no longer referenced.
curl_share_init() will now return a CurlShareHandle object rather than a resource. The curl_share_close() function no longer has an effect, instead the CurlShareHandle instance is automatically destroyed if it is no longer referenced.
enchant_broker_init() will now return an EnchantBroker object rather than a resource.
enchant_broker_request_dict() and enchant_broker_request_pwl_dict() will now return an EnchantDictionary object rather than a resource.
The GD extension now uses GdImage objects as the underlying data structure for images, rather than resources. The imagedestroy() function no longer has an effect; instead the GdImage instance is automatically destroyed if it is no longer referenced.
openssl_x509_read() and openssl_csr_sign() will now return an OpenSSLCertificate object rather than a resource. The openssl_x509_free() function is deprecated and no longer has an effect, instead the OpenSSLCertificate instance is automatically destroyed if it is no longer referenced.
openssl_csr_new() will now return an OpenSSLCertificateSigningRequest object rather than a resource.
openssl_pkey_new() will now return an OpenSSLAsymmetricKey object rather than a resource. The openssl_pkey_free() function is deprecated and no longer has an effect, instead the OpenSSLAsymmetricKey instance is automatically destroyed if it is no longer referenced.
shmop_open() will now return a Shmop object rather than a resource. The shmop_close() function no longer has an effect, and is deprecated; instead the Shmop instance is automatically destroyed if it is no longer referenced.
socket_create(), socket_create_listen(), socket_accept(), socket_import_stream(), socket_addrinfo_connect(), socket_addrinfo_bind(), and socket_wsaprotocol_info_import() will now return a Socket object rather than a resource. socket_addrinfo_lookup() will now return an array of AddressInfo objects rather than resources.
msg_get_queue() will now return an SysvMessageQueue object rather than a resource.
sem_get() will now return an SysvSemaphore object rather than a resource.
shm_attach() will now return an SysvSharedMemory object rather than a resource.
xml_parser_create() and xml_parser_create_ns() will now return an XMLParser object rather than a resource. The xml_parser_free() function no longer has an effect, instead the XMLParser instance is automatically destroyed if it is no longer referenced.
The XMLWriter functions now accept and return, respectively, XMLWriter objects instead of resources.
inflate_init() will now return an InflateContext object rather than a resource.
deflate_init() will now return a DeflateContext object rather than a resource.