PHP 8.0: Introducing the New PHP Version

PHP 8.0: Introducing the New PHP Version

We’re thrilled to announce that PHP 8.0 is now available on Hostinger servers. This PHP version comes with new features and changes to provide the best possible performance for your web projects. 

Since this is a major update, we encourage all users migrating to the latest release to get familiar with the new changes which we will cover in this article. 

Why Do You Need PHP 8.0?

The latest benchmark tests by Phoronix show that PHP 8.0 is performing 10% better than its predecessors. This data suggests a promising future for PHP-based websites and applications. 

PHP 9.0-rc5 benchmark

Better yet, the test was not even conducted using the JIT compiler, a new optimization feature introduced with PHP 8.0. Developers can expect a much faster performance if it is enabled. 

Furthermore, the 8.0 version implements new features to make coding much quicker and cleaner, reducing plenty of boilerplate and redundant code. 

Since this is a version update, your website will likely experience changes that might break it if you transition to PHP 8.0 without making any prior modifications. To help prepare for the migration, we will walk you through all the newest features. 

Changing Your PHP Version

Hostinger clients can follow these steps to update their PHP version:

  1. Log in to hPanel and open your Hosting Account dashboard.
  2. Under the Advanced section, click PHP Configuration.
  3. The PHP Version tab will show which PHP you’re using. To update it to the latest release, select PHP 8.0 and press Save.
PHP 8.0 selected on PHP Configurations screen in hPanel

Note that the update may take a couple of minutes to complete and cause your website to be temporarily unavailable. 

What’s New in PHP 8.0

There are plenty of quality-of-life changes coming with the new update alongside plenty of brand-new features. Let’s explore what’s new and what’s changing in PHP 8.0. 

JIT (Just-in-Time) Compiler

The JIT compiler is one of the most exciting updates of PHP 8.0. This feature aims to work with opcache to improve performance in script execution.

What Is JIT? 

Short for just-in-time, JIT compiles opcode into machine code right before it’s run for output. To understand what that means and how it works, we need to understand the PHP script execution stages:

  • Lexical analysis. The Zend Engine, the PHP interpreter, translates the code into machine-readable tokens.
  • Parser. The interpreter parses the tokens to generate an Abstract Syntax Tree (AST) – a tree-like representation that shows how the code works. 
  • Compilation. The interpreter converts the AST nodes into opcode which is a machine-readable instruction that tells the Zend virtual machine (VM) what operation to perform. 
  • Execution. The interpreter delivers the opcode to the Zend VM, which will compile the opcode into machine code for execution. 

This process uses a significant amount of server resources, especially if a PHP script gets repeat requests. 

That’s why PHP 5.5 introduced the opcache extension, which stores the opcode from the compilation stage. 

When the server receives a new request for the same script, the interpreter can immediately run the opcode from the opcache. That way, there’s no need to restart the execution process from the beginning.

PHP 7.4 added a preloading feature several years later to have the opcache precompile scripts into opcode during startup. As a result, the interpreter can immediately deliver the opcode for execution when the server first receives a request for the script.

Despite these perks, there are several downsides. One is that towards the end of the process, the Zend VM still needs to convert the opcode into machine code before running it, which can take significant time and resources.

That’s where the JIT compiler comes in. It will compile the opcode into machine code during its first run to prepare for the next execution. 

When there is a request for a JIT-compiled script, PHP will run it directly by the CPU instead of the Zend VM, resulting in faster performance. Here’s what the script execution stages will look like on PHP 8.0, in comparison to the previous version:

PHP 8 script execution stages

There are two JIT compilation engines:

  • Function. This approach will identify and compile an entire function without figuring out which parts are frequently called. 
  • Tracing. This mode will only analyze and compile the parts most often used in the function to save time and memory. This is the default engine of PHP 8.0.

What Does JIT Mean for Me? 

According to the RFC, enabling JIT is the most significant way to improve PHP performance. Skipping this feature may result in you missing out on a major benefit. 

Thankfully, recent tests show that the JIT compiler has been able to enhance the script execution process – mainly if you use the Tracing engine. 

PHP’s synthetic benchmarks revealed a three-time larger boost in speed after enabling the Tracing mode. In long-running applications, you can expect to see a performance improvement by up to two times. 

JIT contribution to PHP 8 performance

For WordPress users and developers, the JIT compiler can add a slight boost as well, though it may not be as significant. 

You will need to reduce the TTFB, optimize the database, and lower the number of HTTP requests to get the best possible performance. That said, PHP developers will continue their improvement efforts by using profiling and speculative optimizations. 

If you want to enable JIT, ensure that the opcache extension is active. 

Shared Hosting plan clients can do this by opening the Hosting Account dashboard -> PHP Configuration. On the PHP Extensions tab, make sure to tick the opcache box.

PHP extensions screen

New Features in PHP 8.0 

There are tons of exciting features besides JIT. This section will provide an overview of the major additions and changes to PHP 8.0.

Union Types 2.0

In many cases, a function can use more than one type, but it wasn’t possible to specify this in previous PHP versions unless you declared the types using DocComments.

Here’s an example of what that looks like:

class Number {
    /**
     * @var int|float $number
     */
    private $number;
 
    /**
     * @param int|float $number
     */
    public function setNumber($number) {
        $this->number = $number;
    }
 
    /**
     * @return int|float
     */
    public function getNumber() {
        return $this->number;
    }
}

Previous PHP versions introduced two special union types – Nullable (using the ?Type syntax) and Iterable (for array and Traversable).

However, what was missing was native support for arbitrary union types, which is a feature that comes with PHP 8.0. Now, you can simply write the types that the function can use and separate them using the T1|T2|… syntax, like so:

class Number {
    private int|float $number;
 
    public function setNumber(int|float $number): void {
        $this->number = $number;
    }
 
    public function getNumber(): int|float {
        return $this->number;
    }
}

Notice that the example doesn’t include @var, @param, or @return anymore, making the code much cleaner. 

You can use union types for properties, arguments, and return types – though there are a few limitations to look out for. Check out the RFC for more information.

Named Arguments

In previous PHP versions, passing multiple arguments to a function required using the order positions in which the parameters were declared, like so:

array_fill(0, 100, 50);

One challenge with this is that you may not remember the order of parameters. Plus, it can be difficult to understand what each refers to when you revisit the code. 

With PHP 8.0, you have the option to add a name next to the parameter so that you can pass it to a function instead. Here’s what it usually looks like:

// Using positional arguments:
array_fill(0, 100, 50);
// Using named arguments:
array_fill(start_index: 0, num: 100, value: 50);

One benefit of this feature is that learning what each parameter does will make the overall process much quicker. 

Additionally, named arguments are order-independent, so you won’t have to remember each parameter’s order positions in their declarations. Therefore, the example below will have the same meaning as the one above:

array_fill(value: 50, num: 100, start_index: 0);

Mixing named and positional arguments is also possible, so long as the named ones come second. In other words, the following code is acceptable:

test($foo, param: $bar);

On the other hand, this will result in an error:

test(param: $bar, $foo);

Finally, with named arguments, you only need to write parameters whose default values you want to overwrite. Feel free to skip the ones with default values you’d like to keep. Here is an example provided in the RFC

htmlspecialchars($string, default, default, false);
// vs
htmlspecialchars($string, double_encode: false);

The standard syntax for named arguments is paramName: $value. Do not write the name dynamically as illustrated below as it will result in an error.

// NOT supported.
function_name($variableStoringParamName: $value);

Match Expressions

A match expression is similar to a switch statement in that its purpose is to compare multiple values. However, the semantics are much more efficient and less prone to errors.

Consider the following switch statement example from the RFC:

switch ($this->lexer->lookahead['type']) {
    case Lexer::T_SELECT:
        $statement = $this->SelectStatement();
        break;
 
    case Lexer::T_UPDATE:
        $statement = $this->UpdateStatement();
        break;
 
    case Lexer::T_DELETE:
        $statement = $this->DeleteStatement();
        break;
 
    default:
        $this->syntaxError('SELECT, UPDATE or DELETE');
        break;
}

With a match expression, the same statement will look a lot shorter:

$statement = match ($this->lexer->lookahead['type']) {
    Lexer::T_SELECT => $this->SelectStatement(),
    Lexer::T_UPDATE => $this->UpdateStatement(),
    Lexer::T_DELETE => $this->DeleteStatement(),
    default => $this->syntaxError('SELECT, UPDATE or DELETE'),
};

The code block above shows that a match expression can return a value. This is unlike a switch statement where you have to assign $result towards the end. 

Moreover, there is no need to add a break after every arm because it has been implicitly implemented. 

On top of that, this feature will conduct a strict comparison “===” rather than a loose one “==”. A loose comparison, as performed by switch statements, can often result in unpredictable outcomes, creating bugs in your code. Let’s look at the code below:

switch ('foo') {
    case 0:
      $result = "Oh no!\n";
      break;
    case 'foo':
      $result = "This is what I expected\n";
      break;
}
echo $result;
//> Oh no!

A match expression will present a more appropriate result: 

echo match ('foo') {
    0 => "Oh no!\n",
    'foo' => "This is what I expected\n",
};
//> This is what I expected

An important thing to remember when using this feature is that one of the arms should have a value that matches the condition. Alternatively, there should be a default one declared. Otherwise, it will throw an UnhandledMatchError

Attributes

An attribute is a metadata function that can document what a section of code means. Developers can place it in functions, classes, class constants, class properties, class methods, and function or method parameters. 

This feature is similar to DocComments, though there are some differences. 

Firstly, the fact that it is native to the PHP system makes it readable by tools like static analyzers and syntax highlighters. That’s why the RFC proposes attributes as a more structured and syntactic form of metadata. 

To signify attributes, enclose the text in between two less-than and greater-than symbols, such as <<Example>>. You can add them before the declarations they specify, like so:

<<ExampleAttribute>>
class Foo
{
    <<ExampleAttribute>>
    public const FOO = 'foo';
 
    <<ExampleAttribute>>
    public $x;
 
    <<ExampleAttribute>>
    public function foo(<<ExampleAttribute>> $bar) { }
}

On top of that, users can include one or multiple associated values in the attribute, as illustrated below:

<<WithoutArgument>>
<<SingleArgument(0)>>
<<FewArguments('Hello', 'World')>>
function foo() {}

It’s also possible to attach attributes before or after a DocComment.

<<ExampleAttribute>>
/** docblock */
<<AnotherExampleAttribute>>
function foo() {}

Constructor Property Promotion

This feature is a new shorthand syntax aiming to reduce the amount of boilerplate when using the constructor function. 

In previous PHP versions, one would define simple value objects by repeating the properties – once in the property declaration, once in the constructor parameters, and twice in the property assignments. Take a look at the following example:

class Point {
    public float $x;
    public float $y;
    public float $z;
 
    public function __construct(
        float $x = 0.0,
        float $y = 0.0,
        float $z = 0.0,
    ) {
        $this->x = $x;
        $this->y = $y;
        $this->z = $z;
    }
}

With PHP 8.0, you can declare the property once and combine it with the constructor signature, offering a more effective code-writing experience.

class Point {
    public function __construct(
        public float $x = 0.0,
        public float $y = 0.0,
        public float $z = 0.0,
    ) {}
}

The script will run the same way as the previous instance. Should the parameters be prefixed with a visibility marker, PHP will translate the syntax into the traditional version and execute it after that. 

The RFC explains that there are several constraints to consider with constructor property promotions. One is to avoid using var keywords, as the feature already requires promoting properties using a visibility marker.

class Test {
    // Error: "var" keyword is not supported.
    public function __construct(var $prop) {}
}

Secondly, when the type is null, it should be explicitly declared using the ?Type nullable syntax.

class Test {
    // Error: Using null default on non-nullable property
    public function __construct(public Type $prop = null) {}
    // Correct: Make the type explicitly nullable instead
    public function __construct(public ?Type $prop = null) {}
}

Promoted parameters can only occur in non-abstract constructors. Thus, the following example will result in an error: 

// Error: Not a constructor.
function test(private $x) {}
abstract class Test {
    // Error: Abstract constructor.
    abstract public function __construct(private $x);
}

A class can have both constructor-promoted properties and explicit property declarations. However, it’s not possible to use the same property in both, like so:

class Test {
    public $prop;
 
    // Error: Redeclaration of property.
    public function __construct(public $prop) {}
}

There is also no promoting variadic parameters in constructors. 

class Test {
    // Error: Variadic parameter.
    public function __construct(public string ...$strings) {}
}

Finally, there is no support to use the callable type in the constructor property declaration.

class Test {
    // Error: Callable type not supported for properties.
    public function __construct(public callable $callback) {}
}

Nullsafe Operator

Checking for null using a conditional statement usually results in code that’s deeply nested and difficult to read. Consider the example below:

$country =  null;
 
if ($session !== null) {
    $user = $session->user;
 
    if ($user !== null) {
        $address = $user->getAddress();
 
        if ($address !== null) {
            $country = $address->country;
        }
    }
}
// do something with $country

To combat this problem, PHP 8.0 presents the nullsafe operator. With this feature, you can rewrite the code in the above example by using the ?-> syntax.

$country = $session?->user?->getAddress()?->country;
// do something with $country

According to the RFC, PHP will check whether the first operator $session results in null. If it doesn’t, the execution will continue until the last operator. PHP will stop the execution process when one of the operators evaluates null. 

One limitation with using a nullsafe operator is that it cannot exist in a write context. As a result, the following will create an error:

foreach ([1, 2, 3] as $foo?->bar->baz) {}
// Can't use nullsafe operator in write context

Additionally, taking a reference of a nullsafe chain is not allowed.

// 1
$x = &$foo?->bar;
// Compiler error: Cannot take reference of a nullsafe chain
// 2
takes_ref($foo?->bar);
// Error: Cannot pass parameter 1 by reference 
// 3
function &return_by_ref($foo) {
    return $foo?->bar;
    // Compiler error: Cannot take reference of a nullsafe chain
}

Weak Maps

With PHP 7.4, developers can make weak references to a key object so that unlike strong relations they don’t increase the object’s reference counter and prevent the object’s removal when it’s no longer used. 

A weak map is an improvement to that feature. It stores arbitrary values used as weak references to an object. That way, when the object falls out of scope or gets unset, PHP can clear any associated values in the weak map. 

The example below, sourced from the RFC, shows how weak maps will look like: 

$map = new WeakMap;
$obj = new stdClass;
$map[$obj] = 42;
var_dump($map);
// object(WeakMap)#1 (1) {
//   [0]=>
//   array(2) {
//     ["key"]=>
//     object(stdClass)#2 (0) {
//     }
//     ["value"]=>
//     int(42)
//   }
// }
 
// The object is destroyed here, and the key is automatically removed from the weak map.
unset($obj);
var_dump($map);
// object(WeakMap)#1 (0) {
// }

This feature will be more beneficial for long-running applications, particularly if you want to prevent memory leaks and implement caching. 

Minor New Features

Here are some minor additional features you can expect in PHP 8.0:

  • Trailing commas in parameter lists. You can list down the parameters in a vertical format instead of the standard horizontal one.
  • str_contains(). This function will examine if a string is incorporated as an element of another string. It will return a boolean value depending on whether the condition is true or false. You may consider it as a more readable alternative to str_pos().
  • str_starts_with(). It will check if a string starts with a specified substring.
  • str_ends_with(). It will determine whether a string ends with a given substring. 
  • Stringable interface. Classes that use the __toString() method will now have the Stringable interface automatically implemented. The objective is to allow the string|Stringable union type, which means PHP can accept either a string or an object using the __toString() method. 
  • fdiv(). PHP 8.0 accepts division by zero and will return INF, -INF, or NAN instead of an error.  
  • get_debug_type(). This function works similar to gettype(), though it will return the native type names and class names. The reference table below shows how the two differ.
PHP 8 reference table
  • get_resource_id(). This function is a type-safe alternative to (int) $resource. It will fetch the ID of an external resource, such as a database connection.
  • PhpToken::tokenize(). This method will replace the token_get_all() function. Instead of returning strings or arrays of values, it will return an array of objects. The resulting code will be more readable and memory-efficient. 
  • New DOM Living Standard in ext/dom. New interfaces and public properties to adapt to the current DOM APIs, which have become necessary when dealing with XML and HTML documents. 

Deprecated Features in PHP 8.0 

PHP 8.0 will deprecate the following features: 

  • Including a default value in a parameter followed by a required parameter, as the default value has no effect
  • Using get_defined_functions() with exclude_disabled explicitly set to false
  • enchant_broker_set_dict_path() function
  • enchant_broker_get_dict_path() function
  • enchant_dict_add_to_personal() function
  • enchant_dict_is_in_session() function
  • enchant_broker_free() function
  • enchant_broker_free_dict() function
  • ENCHANT_MYSPELL constant
  • ENCHANT_ISPELL constant
  • libxml_disable_entity_loader() function
  • The PGSQL_LIBPQ_VERSION_STR constant, along with some functions aliases in the pgsql extension
  • Using sort comparison functions that return true or false
  • Using an empty file as ZipArchive
  • Zip’s procedural API
  • ReflectionFunction::isDisabled() function
  • ReflectionParameter::getClass() function
  • ReflectionParameter::isArray() function
  • ReflectionParameter::isCallable() function

Check out this resource for more information about the deprecated features. 

Backward Incompatible Changes for PHP 8.0

The migration to PHP 8.0 brings many changes that are incompatible with older PHP versions. Some of them include:

  • String to number comparison. Comparing numbers to non-numeric strings will now return as false. See the reference table below:
PHP 8 string comparison table
  • No whitespace in namespaced names. For instance, names like “Foo \ bar” won’t be acceptable. Use “Foo\bar” instead.
  • Resource to object migration. Some functions will now return an object instead of a resource. 
  • The removal of The FILTER_FLAG_SCHEME_REQUIRED and FILTER_FLAG_HOST_REQUIRED flags. This is because FILTER_VALIDATE_URL already uses these flags by default when combined with the filter_var() function. 
  • E_ALL is now the default error_reporting level. PHP 8.0 will include notices and deprecated changes in the error messages. 

Make sure to read the official documentation to see other backward-incompatible changes. 

Other PHP 8.0 Changes

The changes below won’t result in backward incompatibility, but they are still essential to note. Some of them include:

  • You can filter results from ReflectionClass::getConstants() and ReflectionClass::getReflectionConstants() using the new filter parameter.
  • The ZipArchive::addGlob() and ZipArchive::addPattern() methods now accept flags, comp_method, comp_flags, env_method, and enc_password in the options array. 
  • There is a new flags argument for ZipArchive::addEmptyDir(), ZipArchive::addFile(), and ZipArchive::addFromString() methods.
  • Due to its essential role in PHP, the JSON extension is now enabled by default.
  • The array_slice() function won’t have to find the starting position if the array doesn’t have gaps.

This resource contains more information about the changes. 

WordPress and PHP 8.0 

WordPress 5.6 and upwards is compatible with PHP 8.0. The development team has removed many deprecated features to achieve this.

However, they cannot claim full compatibility as many plugins and themes may not support PHP 8.0 yet. If you want to make a switch to PHP 8.0, ensure you can make the transition without any issues. Otherwise, if you’re working with a professional web developer, ask them to help you with the transition.

If you’re a plugin or theme developer, WordPress encourages adapting your product’s code to the new PHP version. That way, the plugin or theme’s users won’t experience problems on their website should they update the software.

This article has outlined the major changes that may apply to WordPress developers. That said, we recommend reading this WordPress announcement to see which ones you should pay special attention to. 

Remember that you can always downgrade your version to PHP 7.4 if there are too many issues to resolve.

Conclusion

PHP 8.0 has some exciting changes and improvements for PHP developers, like the JIT compiler, constructor property promotion, and weak maps. Expect to see a boost in website performance and code-writing experience. 

Make sure to check out the RFCs and documentation so that your projects can be fully compatible with the latest PHP version. Good luck!

Author
The author

Arvydas L.

Being a developer team lead can be a tough gig, but Arvydas likes challenges. If you have any technical issues that need solving, you can rely on him. Once he makes sure that everything works smoothly, you'll find him avidly watching basketball games.