PHP 8 is expected to be released in December 2020 and will bring us a whole bunch of powerful features and great language improvements.

Many RFCs have already been approved and implemented, so it’s time for us to dive into some of the most exciting additions that should make PHP faster and more reliable.

As PHP 8 is still under development, we could see several changes before the final release. We’ll keep track of these changes and update this post regularly, so make sure you don’t miss anything about PHP 8 and check this post again from time to time.

So, what features and improvements should we expect with PHP 8? What’s the biggest thing coming with PHP 8, the next major release of the language?

Let’s dive in!

PHP JIT (Just in Time Compiler)

The most acclaimed feature coming with PHP 8 is the Just-in-time (JIT) compiler. What is JIT all about?

The RFC proposal describes JIT as follows:

“PHP JIT is implemented as an almost independent part of OPcache. It may be enabled/disabled at PHP compile time and at run-time. When enabled, native code of PHP files is stored in an additional region of the OPcache shared memory and op_array→opcodes[].handler(s) keep pointers to the entry points of JIT-ed code.”

So, how did we get to JIT and what is the difference between JIT vs OPcache?

To better understand what JIT is for PHP, let’s take a quick look at how PHP executes from the source code to the final result.

The PHP execution is a 4 stage process:

The following image shows a visual representation of the basic PHP execution process.

Basic PHP execution process

Basic PHP execution process

So, how does OPcache make PHP faster? And what changes in the execution process with JIT?

The OPcache Extension

PHP is an interpreted language. This means, when a PHP script runs, the interpreter parses, compiles, and executes the code over and over again on each request. This may result in wasting CPU resources and additional time.

This is where the OPcache extention comes in to play:

“OPcache improves PHP performance by storing precompiled script bytecode in shared memory, thereby removing the need for PHP to load and parse scripts on each request.”

With OPcache enabled, the PHP interpreter goes through the 4 stage process mentioned above only the first time the script runs. Since PHP bytecodes are stored in shared memory, they are immediately available as low-level intermediate representation and can be executed on the Zend VM right away.

PHP execution process with OPcache enabled

PHP execution process with OPcache enabled

As of PHP 5.5, the Zend OPcache extention is available by default and you can check if you have it correctly configured by simply calling phpinfo() from a script on your server or checking out your php.ini file (see OPcache configuration settings).

Zend OPcache section in a phpinfo page

Zend OPcache section in a phpinfo page

Preloading

OPcache has been recently improved with the implementation of preloading, a new OPcache feature added with PHP 7.4. Preloading provides a way to store a specified set of scripts into OPcache memory “before any application code is run“, but it doesn’t bring tangible performance improvement for typical web-based applications.

You can read more about preloading in our introduction to PHP 7.4.

With JIT, PHP moves a step forward.

JIT — The Just in Time Compiler

Even if opcodes are in the form of low-level intermediate representation, they still have to be compiled into machine code. JIT “doesn’t introduce any additional IR (Intermediate Representation) form”, but uses DynASM (Dynamic Assembler for code generation engines) to generate native code directly from PHP byte-code.

In short, JIT translates the hot parts of the intermediate code into machine code. Bypassing compilation, it’d be able to bring considerable improvements in performance and memory usage.

Zeev Surasky, co-author of the PHP JIT proposal, shows how much calculations would be faster with JIT:

[embedded content]

But, would JIT effectively improve WordPress performance?

JIT for Live Web Apps

According to the JIT RFC, the just in time compiler implementation should improve PHP performance. But would we really experience such improvements in real-life apps like WordPress?

The early tests show that JIT would make CPU-intensive workloads run significantly faster, however, the RFC warns:

“… like the previous attempts – it currently doesn’t seem to significantly improve real-life apps like WordPress (with opcache.jit=1235 326 req/sec vs 315 req/sec).

It’s planned to provide additional effort, improving JIT for real-life apps, using profiling and speculative optimizations.”

With JIT enabled, the code wouldn’t be run by the Zend VM, but by the CPU itself, and this would improve speed in calculation. Web apps like WordPress also rely on other factors like TTFB, database optimization, HTTP requests, etc.

So, when it comes to WordPress and similar apps, we shouldn’t expect a great boost in PHP execution speed. Nevertheless, JIT could bring several benefits for developers.

According to Nikita Popov:

“The benefits of the JIT compiler are roughly (and as already outlined in the RFC):

  • Significantly better performance for numerical code.
  • Slightly better performance for “typical” PHP web application code.
  • The potential to move more code from C to PHP, because PHP will now be sufficiently fast.”

So, while JIT will hardly bring huge improvements to WordPress performance, it’ll be upgrading PHP to the next level, making it a language many functions could now be written directly in.

The downside, though, would be the greater complexity that can lead to increasing costs in maintenance, stability, and debugging. According to Dmitry Stogov:

“JIT is extremely simple, but anyway it increases the level of the whole PHP complexity, risk of new kind of bugs and cost of development and maintenance.”

The proposal to include JIT in PHP 8 passed with 50 to 2 votes.

PHP 8 is coming later this year. 🚀 Check out our deep dive into the new features! Click to Tweet

PHP 8 Improvements and New Features

Apart from JIT, we can expect many features and improvements with PHP 8. The following list is our handpicked selection of the upcoming additions and changes that should make PHP more reliable and efficient.

Validation for Abstract Trait Methods

Traits are defined as “a mechanism for code reuse in single inheritance languages such as PHP”. Typically, they are used to declare methods that can be used in multiple classes.

A trait can also contain abstract methods. These methods simply declare the method’s signature, but the method’s implementation must be done within the class using the trait.

According to the PHP manual,

“Traits support the use of abstract methods in order to impose requirements upon the exhibiting class.”

This also means that the signatures of the methods must match. In other words, the type and the number of required arguments need to be the same.

Anyway, according to Nikita Popov, author of the RFC, signature validation is currently enforced only spottily:

The following example from Nikita relates to the first case (not enforced signature):

trait T { abstract public function test(int $x);
} class C { use T; // Allowed, but shouldn't be due to invalid type. public function test(string $x) {}
}

With that being said, this RFC proposes to always throw a fatal error if the implementing method is not compatible with the abstract trait method, regardless of its origin:

Fatal error: Declaration of C::test(string $x) must be compatible with T::test(int $x) in /path/to/your/test.php on line 10

This RFC has been unanimously approved.

Incompatible Method Signatures

In PHP, inheritance errors due to incompatible method signatures throw either a fatal error or a warning depending on what is causing the error.

If a class is implementing an interface, incompatible method signatures throw a fatal error. According to Object Interfaces documentation:

“The class implementing the interface must use a method signature which is compatible with LSP (Liskov Substitution Principle). Not doing so will result in a fatal error.”

Here is an example of an inheritance error with an interface:

interface I { public function method(array $a);
}
class C implements I { public function method(int $a) {}
}

In PHP 7.4, the code above would throw the following error:

Fatal error: Declaration of C::method(int $a) must be compatible with I::method(array $a) in /path/to/your/test.php on line 7

A function in a child class with an incompatible signature would throw a warning. See the following code from the RFC:

class C1 { public function method(array $a) {}
}
class C2 extends C1 { public function method(int $a) {}
}

In PHP 7.4, the code above would simply throw a warning:

Warning: Declaration of C2::method(int $a) should be compatible with C1::method(array $a) in /path/to/your/test.php on line 7

Now, this RFC proposes to always throw a fatal error for incompatible method signatures. With PHP 8, the code we saw earlier above would prompt the following:

Fatal error: Declaration of C2::method(int $a) must be compatible with C1::method(array $a) in /path/to/your/test.php on line 7

Arrays Starting With a Negative Index

In PHP, if an array starts with a negative index (start_index < 0), the following indices will start from 0 (more on this in array_fill documentation). Look at the following example:

$a = array_fill(-5, 4, true);
var_dump($a);

In PHP 7.4 the result would be the following:

array(4) { [-5]=> bool(true) [0]=> bool(true) [1]=> bool(true) [2]=> bool(true)
}

Now, this RFC proposes to change things so that the second index would be start_index + 1, whichever the value of start_index.

In PHP 8, the code above would result in the following array:

array(4) { [-5]=> bool(true) [-4]=> bool(true) [-3]=> bool(true) [-2]=> bool(true)
}

With PHP 8, arrays starting with a negative index change their behavior. Read more about backward incompatibilities in the RFC.

Union Types 2.0

Union types accept values that can be of different types. Currently, PHP doesn’t provide support for union types, with the exception of the ?Type syntax and the special iterable type.

Before PHP 8, union types could only be specified in phpdoc annotations, as shown in the following example from the RFC:

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; }
}

Now, the Union types 2.0 RFC proposes to add support for union types in function signatures, so that we won’t rely on inline documentation anymore, but would define union types with a T1|T2|... syntax instead:

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

As explained by Nikita Popov in the RFC,

“Supporting union types in the language allows us to move more type information from phpdoc into function signatures, with the usual advantages this brings:

  • Types are actually enforced, so mistakes can be caught early.
  • Because they are enforced, type information is less likely to become outdated or miss edge-cases.
  • Types are checked during inheritance, enforcing the Liskov Substitution Principle.
  • Types are available through Reflection.
  • The syntax is a lot less boilerplate-y than phpdoc.”

Union types support all available types, with some limitations:

  • The void type could not be part of a union, as void means that a function does not return any value.
  • The null type is only supported in union types but it’s usage as a standalone type is not allowed.
  • The nullable type notation (?T) is also allowed, meaning T|null, but we are not allowed to include the ?T notation in union types (?T1|T2 is not allowed and we should use T1|T2|null instead).
  • As many functions (i.e. strpos(), strstr(), substr(), etc.) include false among the possible return types, the false pseudo-type is also supported.

You can read more about Union Types V2 in the RFC.

Consistent Type Errors for Internal Functions

When passing a parameter of illegal type, internal and user-defined functions behave differently.

User-defined functions throw a TypeError, but internal functions behave in a variety of ways, depending on several conditions. Anyway, the typical behavior is to throw a warning and return null. See the following example in PHP 7.4:

var_dump(strlen(new stdClass));

This would result in the following warning:

Warning: strlen() expects parameter 1 to be string, object given in /path/to/your/test.php on line 4
NULL

If strict_types is enabled, or argument information specifies types, the behavior would be different. In such scenarios, the type error is detected and results in a TypeError.

This situation would lead to a number of problems well explained in the RFC’s issues section.

To remove these inconsistencies, this RFC proposes to make the internal parameter parsing APIs to always generate a ThrowError in case of a parameter type mismatch.

In PHP 8, the code above throws the following error:

Fatal error: Uncaught TypeError: strlen(): Argument #1 ($str) must be of type string, object given in /path/to/your/test.php:4
Stack trace:
#0 {main} thrown in /path/to/your/test.php on line 4

throw Expression

In PHP, throw is a statement, so it’s not possible to use it in places where only an expression is allowed.

This RFC proposes to convert the throw statement into an expression so that it can be used in any context where expressions are allowed. For example, arrow functions, null coalesce operator, ternary and elvis operators, etc.

See the following examples from the RFC:

$callable = fn() => throw new Exception(); // $value is non-nullable.
$value = $nullableValue ?? throw new InvalidArgumentException(); // $value is truthy.
$value = $falsableValue ?: throw new InvalidArgumentException();

Weak Maps

A weak map is a collection of data (objects) in which keys are weakly referenced, meaning that they are not prevented from being garbage collected.

PHP 7.4 added support for weak references as a way to retain a reference to an object that doesn’t prevent the object itself from being destroyed. As pointed out by Nikita Popov,

“Raw weak references are only of limited usefulness by themselves and weak maps are much more commonly used in practice. It is not possible to implement an efficient weak map on top of PHP weak references because the ability to register a destruction callback is not provided.”

That’s why this RFC introduces a WeakMap class to create objects to be used as weak map keys that can be destroyed and removed from the weak map if there aren’t any further references to the key object.

In long-running processes, this would prevent memory leaks and improve performance. See the following example from the RFC:

$map = new WeakMap;
$obj = new stdClass;
$map[$obj] = 42;
var_dump($map);

With PHP 8, the code above would produce the following result (see the code in action here):

object(WeakMap)#1 (1) { [0]=> array(2) { ["key"]=> object(stdClass)#2 (0) { } ["value"]=> int(42) }
}

If you unset the object, the key is automatically removed from the weak map:

unset($obj);
var_dump($map);

Now the result would be the following:

object(WeakMap)#1 (0) {
}

For a closer look at Weak maps, see the RFC. The proposal was unanimously approved.

Trailing Comma in Parameter List

Trailing commas are commas appended to lists of items in different contexts. PHP 7.2 introduced trailing commas in list syntax, PHP 7.3 introduced trailing commas in function calls.

PHP 8 now introduces trailing commas in parameter lists with functions, methods, and closures, as shown in the following example:

class Foo { public function __construct( string $x, int $y, float $z, // trailing comma ) { // do something }
}

This proposal passed with 58 to 1 votes.

Allow ::class syntax on objects

In order to fetch the name of a class, we can use the Foo\Bar::class syntax. This RFC proposes to extend the same syntax to objects so that it’s now possible to fetch the name of the class of a given object as shown in the example below:

$object = new stdClass;
var_dump($object::class); // "stdClass" $object = null;
var_dump($object::class); // TypeError

With PHP 8, $object::class provides the same result as get_class($object). If $object is not an object, it throws a TypeError exception.

This proposal was unanimously approved.

Attributes v2

Attributes, also known as annotations, are a form of structured metadata that can be used to specify properties for objects, elements, or files.

Until PHP 7.4, doc-comments were the only way to add metadata to declarations of classes, functions, etc. Now, the Attributes v2 RFC introduces attributes for PHP defining them as a form of structured, syntactic metadata that can be added to declarations of classes, properties, functions, methods, parameters, and constants.

Attributes are added before the declarations they refer to. See the following examples from the RFC:

<<ExampleAttribute>>
class Foo
{ <<ExampleAttribute>> public const FOO = 'foo'; <<ExampleAttribute>> public $x; <<ExampleAttribute>> public function foo(<<ExampleAttribute>> $bar) { }
} $object = new <<ExampleAttribute>> class () { }; <<ExampleAttribute>>
function f1() { } $f2 = <<ExampleAttribute>> function () { }; $f3 = <<ExampleAttribute>> fn () => 1;

Attributes can be added before or after a doc-block comment:

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

Each declaration may have one or more attributes and each attribute may have one or more associated values:

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

See the RFC for a deeper overview of PHP attributes, use cases, and alternative syntax. Note that Attributes v2 is currently pending implementation.

New PHP Functions

PHP 8 brings several new functions to the language:

str_contains

Before PHP 8, strstr and strpos were the typical options for developers to search for a needle inside a given string. Problem is, both functions aren’t considered very intuitive and their usage can be confusing for new PHP developers. See the following example:

$mystring = 'Managed WordPress Hosting';
$findme = 'WordPress';
$pos = strpos($mystring, $findme); if ($pos !== false) { echo "The string has been found";
} else { echo "String not found";
}

In the example above we used the !== comparison operator, which also checks if two values are of the same type. This prevents us to get an error if the position of the needle is 0:

“This function may return Boolean FALSE, but may also return a non-Boolean value which evaluates to FALSE. […] Use the === operator for testing the return value of this function.”

Furthermore, several frameworks provide helper functions to search for a value inside a given string (see Laravel Helpers documentation as an example).

Now, this RFC proposes the introduction of a new function allowing to search inside a string: str_contains.

str_contains ( string $haystack , string $needle ) : bool

Its usage is pretty straightforward. str_contains checks if $needle is found in $haystack and returns true or false accordingly.

So, thanks to str_contains, we can write the following code:

$mystring = 'Managed WordPress Hosting';
$findme = 'WordPress'; if (str_contains($mystring, $findme)) { echo "The string has been found";
} else { echo "String not found";
}

Which is more readable and less prone to errors (see this code in action here).
At the time of this writing, str_contains is case-sensitive, but this could change in the future.

The str_contains proposal passed with 43 to 9 votes.

str_starts_with() and str_ends_with()

In addition to the str_contains function, two new functions allow to search for a needle inside a given string: str_starts_with and str_ends_with.

These new functions check if a given string starts or ends with another string:

str_starts_with (string $haystack , string $needle) : bool
str_ends_with (string $haystack , string $needle) : bool

Both functions return false if $needle is longer than $haystack.

According to Will Hudgins, the author of this RFC,

“The str_starts_with and str_ends_with functionality is so commonly needed that many major PHP frameworks support it, including Symfony, Laravel, Yii, FuelPHP, and Phalcon.”

Thanks to them, we could now avoid using sub-optimal and less intuitive functions like substr, strpos. Both functions are case sensitive:

$str = "WordPress";
if (str_starts_with($str, "Word")) echo "Found!"; if (str_starts_with($str, "word")) echo "Not found!";

You can see this code in action here.

This RFC has been approved with 51 to 4 votes.

get_debug_type

get_debug_type is a new PHP function that returns the type of a variable. The new function works in quite a similar way as the gettype function, but get_debug_type returns native type names and resolves class names.

That’s a good improvement for the language, as gettype() is not useful for type checking.

The RFC provides two useful examples to better understand the difference between the new get_debug_type() function and gettype(). The first example shows gettype at work:

$bar = [1,2,3]; if (!($bar instanceof Foo)) { throw new TypeError('Expected ' . Foo::class . ', got ' . (is_object($bar) ? get_class($bar) : gettype($bar)));
}

With PHP 8, we could use get_debug_type, instead:

if (!($bar instanceof Foo)) { throw new TypeError('Expected ' . Foo::class . ' got ' . get_debug_type($bar));
}

The following table shows returning values of get_debug_type and gettype:

Valuegettype()get_debug_type()
1integerint
0.1doublefloat
truebooleanbool
falsebooleanbool
nullNULLnull
“WordPress”stringstring
[1,2,3]arrayarray
A class with name “Foo\Bar”objectFoo\Bar
An anonymous classobjectclass@anonymous

Additional RFCs

At the time of this writing, several RFCs targeted for PHP 8 are still in draft and/or pending implementation. We’ll add them as soon as their status changes to “Implemented”.

Here is a quick list of additional approved improvements that will be part of PHP 8:

  1. Stringable interface: this RFC introduces a Stringable interface that is automatically added to classes implementing the __to String() method. The main goal here is to use the string|Stringable union type.
  2. New DOM Living Standard APIs in ext/dom: this RFC proposes to implement the current DOM Living Standard to the PHP DOM extension by introducing new interfaces and public properties.
  3. Static return type: PHP 8 introduces the usage of static as return type next to self and parent types.
  4. Variable Syntax Tweaks: this RFC resolves some residual inconsistencies in PHP’s variable syntax.

PHP 8 is coming later this year and it’ll bring lots of changes and improvements. 🚀 Check out our deep dive into the new features! Click to Tweet

Summary

What a ride! In this post, we covered all the key changes and improvements expected with the release of PHP 8. The most awaited of which is surely the Just in Time compiler, but there’s so much more coming with PHP 8.

Make sure to bookmark this blog post as we’ll add our favorites to the list as soon as they are approved. 🤓

Now it’s your turn: are you ready to test the upcoming PHP features? Which one is your favorite? Drop a line in the comments section below.


If you enjoyed this article, then you’ll love Kinsta’s WordPress hosting platform. Turbocharge your website and get 24/7 support from our veteran WordPress team. Our Google Cloud powered infrastructure focuses on auto-scaling, performance, and security. Let us show you the Kinsta difference! Check out our plans

Source: Kinsta