PHP 8 has been officially released to the General Availability on November 26, 2020!

This new major update brings many optimizations and powerful features to the language. We’re excited to drive you through the most interesting changes that will allow us to write better code and build more robust applications.

PHP 8 released
PHP 8.0 Announcement Addendum

Are you ready? 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 the source code to the final result.

The PHP execution is a 4 stage process:

  • Compilation: The interpreter traverses the tree and translates AST nodes into low-level Zend opcodes, which are numeric identifiers determining the type of instruction performed by the Zend VM.
  • Interpretation: Opcodes are interpreted and run on the Zend VM.

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 extension 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 when the script runs for the first time. 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 extension 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).

Suggested reading: How to Improve PHP Memory Limit in WordPress.

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.” Still, 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 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:

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, which would improve the calculation speed. Web apps like WordPress also rely on other factors like TTFB, database optimization, HTTP requests, etc.

PHP 8 performance diagram
Relative JIT contribution to PHP 8 performance (Image source: PHP 8.0 Announcement Addendum)

So, we shouldn’t expect a significant boost in PHP execution speed when it comes to WordPress and similar apps. 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 would be the greater complexity that can increase maintenance, stability, and debugging costs. 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 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.

Constructor Property Promotion

As a result of an ongoing discussion about improving object ergonomics in PHP, the Constructor Property Promotion RFC proposes a new and more concise syntax that will simplify the property declaration, making it shorter and less redundant.

This proposal only relates to promoted parameters, i.e. those method parameters prefixed with public, protected, and private visibility keywords.

Currently, all properties have to be repeated several times (at least four times) before we can use them with objects. Consider the following example from the RFC:

class Point {
    public int $x;
    public int $y;
    public int $z;

    public function __construct(
        int $x = 0,
        int $y = 0,
        int $z = 0,
    ) {
        $this->x = $x;
        $this->y = $y;
        $this->z = $z;
    }
}

According to Nikita Popov, the RFC author, we have to write the property name at least four times in three different places: the property declaration, the constructor parameters, and the property assignment. This syntax is not particularly usable, especially in classes with many properties and more descriptive names.

This RFC proposes to merge the constructor and the parameter definition. So, as of PHP 8, we have a more usable way of declaring parameters. The code seen above can change as shown below:

class Point {
    public function __construct(
        public int $x = 0,
        public int $y = 0,
        public int $z = 0,
    ) {}
}

And that’s it. So we have a new way to promote properties that are shorter, more readable, and less prone to errors. According to Nikita:

It’s a simple syntactic transformation that we’re doing. But that reduces the amount of boilerplate code you have to write for value objects in particular…

The property declaration is transformed as we’d explicitly declared those properties, and we can use the Reflection API to introspect property definitions before the execution (see Desugaring):

Reflection (and other introspection mechanisms) will observe the state after desugaring. This means that promoted properties will appear the same way as explicitly declared properties, and promoted constructor arguments will appear as ordinary constructor arguments.

// before desugaring
class Point {
    public function __construct(public int $x = 0) {}
}

// after desugaring
class Point {
    public int $x;

    public function __construct(int $x = 0) {
        $this->x = $x;
    }
}

Inheritance

We don’t have any limitations in using inheritance in conjunction with promoted parameters. Anyway, there’s not a particular relation between parent and child class constructors. According to Nikita:

Usually, we say that methods always have to be compatible with the parent method. […] but this rule does not apply for the constructor. So the constructor really belongs to a single class, and constructors between parent and child class do not have to be compatible in any way.

Here is an example:

class Test {
    public function __construct(
        public int $x = 0
    ) {}
}

class Child extends Test {
    public function __construct(
        $x, 
        public int $y = 0,
        public int $z = 0,
    ) {
        parent::__construct($x);
    }
}

What’s Not Allowed With Promoted Properties

Promoted properties are allowed in non-abstract constructors and traits, but there are several limitations worth mentioning here.

Abstract Constructors

Promoted properties are not allowed in abstract classes and interfaces:

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

One of the most notable constraints is related to nullability. Previously, we used a type that wasn’t explicitly nullable. But with a null default value, the type was implicitly nullable. But with property types, we don’t have this implicit behavior because promoted parameters require a property declaration, and the nullable type must be explicitly declared. See the following example from the RFC:

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) {}
}
Callable Type

As callable is not a supported type for properties, we are not allowed to use the callable type in promoted properties:

class Test {
    // Error: Callable type not supported for properties.
    public function __construct(public callable $callback) {}
}
The var Keyword Is Not Allowed

Only a visibility keyword can be used with promoted parameters, so declaring constructor properties with the var keyword is not allowed (see the following example from the RFC):

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

We can combine promoted properties and explicit properties in the same class, but properties cannot be declared twice:

class Test {
    public string $prop;
    public int $explicitProp;

    // Correct
    public function __construct(public int $promotedProp, int $arg) {
        $this->explicitProp = $arg;
    }

    // Error: Redeclaration of property.
    public function __construct(public string $prop) {}
}
Variadic Parameters Are Not Allowed

The reason here is that the declared type is different from the variadic parameter, which is actually an array:

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

Further Readings

For a closer view at Costructor Property Promotion, listen to this interview with Nikita Popov. For an in-depth overview of object ergonomics in PHP, see this post and the following interview with Larry Garfield.

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, except for 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 various 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

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 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. The Attributes v2 RFC introduces PHP attributes, 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 more in-depth overview of PHP attributes, use cases, and alternative syntax.

Named Arguments

Named arguments provide a new way of passing arguments to a function in PHP:

Named arguments allow passing arguments to a function based on the parameter name, rather than the parameter position.

We can pass named arguments to a function by simply adding the parameter name before its value:

callFunction(name: $value);

We are also allowed to use reserved keywords, as shown in the example below:

callFunction(array: $value);

But we are not allowed to pass a parameter name dynamically. The parameter must be an identifier, and the following syntax is not allowed:

callFunction($name: $value);

According to Nikita Popov, the author of this RFC, named arguments offer several advantages.

First off, named arguments will help us write more understandable code because their meaning is self-documenting. The example below from the RFC is self-explanatory:

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

Named arguments are order-independent. This means that we are not forced to pass arguments to a function in the same order as the function signature:

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

It’s also possible to combine named arguments with positional arguments:

htmlspecialchars($string, double_encode: false);

Another great advantage of named arguments is that they allow specifying only those arguments we want to change. We don’t have to specify default arguments if we don’t want to overwrite default values. The following example from the RFC makes it clear:

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

Named arguments can be used with PHP attributes, as shown in the following example from the RFC:

<<MyAttribute('A', b: 'B')>>
class Test {}

However, passing positional arguments after named arguments is not allowed and will result in a compile-time error. The same happens when you pass the same parameter name twice.

Named arguments are handy with class declarations because constructors usually have many parameters, and named arguments provide a more “ergonomic” way to declare a class.

For a closer view at Named Arguments, with constraints, backward incompatibilities, and several examples, see the Named Arguments RFC.

Nullsafe Operator

This RFC introduces the nullsafe operator $-> with full short-circuit evaluation.

In short-circuit evaluation, the second operator is evaluated only if the first operator does not evaluate to null. If an operator in a chain evaluates to null, the execution of the entire chain stops and evaluates to null.

Consider the following examples from the RFC:

$foo = $a?->b();

If $a is null, method b() isn’t called and $foo is set to null.

See the nullsafe operator RFC for additional examples, exceptions, and future scope.

Saner String to Number Comparisons

In previous PHP versions, when making a non-strict comparison between strings and numbers, PHP first casts the string to a number, then performs the comparison between integers or floats. Even if this behavior is quite useful in several scenarios, it may produce wrong results that may also lead to bugs and/or security issues.

Consider the following example from the RFC:

$validValues = ["foo", "bar", "baz"];
$value = 0;
var_dump(in_array($value, $validValues));
// bool(true)

PHP 8 introduces Saner string to number comparisons, aiming to make string to number comparisons more reasonable. In the words of Nikita Popov,

This RFC intends to give string to number comparisons a more reasonable behavior: When comparing to a numeric string, use a number comparison (same as now). Otherwise, convert the number to string and use a string comparison.

The following table compares the behavior of string to number comparison earlier PHP versions and in PHP 8:

Comparison    | Before | After
------------------------------
 0 == "0"     | true   | true
 0 == "0.0"   | true   | true
 0 == "foo"   | true   | false
 0 == ""      | true   | false
42 == "   42" | true   | true
42 == "42foo" | true   | false

Read more about the many implications of this change and how string to number comparisons change in PHP 8 in the official RFC from Nikita Popov.

Saner Numeric Strings

In PHP, strings containing numbers fall into three categories:

  • Numeric strings: strings containing a number optionally preceded by whitespaces.
  • Leading-numeric string: strings whose initial characters are numeric strings and trailing characters are non-numeric.
  • Non-numeric string: strings not falling in neither of the previous categories.

Numeric strings and leading-numeric strings are treated differently depending on the operation performed. For example:

  • Explicit string to number conversions (i.e. (int) and (float) type casts) convert numeric and leading-numeric strings numbers. Explicitly converting a non-numeric string to a number produces 0.
  • Implicit string to number conversions (i.e. no strict_type declaration) lead to different results for numeric and non-numeric strings. Non-numeric string to number conversions throw a TypeError.
  • is_numeric() returns true only for numeric strings.

String offsets, arithmetic operations, increment and decrement operations, string-to-string comparisons, and bitwise operations also lead to different results.

This RFC proposes to:

Unify the various numeric string modes into a single concept: Numeric characters only with both leading and trailing whitespace allowed. Any other type of string is non-numeric and will throw TypeErrors when used in a numeric context.

This means, all strings which currently emit the E_NOTICE “A non well formed numeric value encountered” will be reclassified into the E_WARNING “A non-numeric value encountered” except if the leading-numeric string contained only trailing whitespace. And the various cases which currently emit an E_WARNING will be promoted to TypeErrors.

For a more in-depth overview of numeric strings in PHP 8, with code examples, exceptions, and backward compatibility issues, see the RFC.

Match Expression v2

The new match expression is pretty similar to switch but with safer semantics and allowing to return values.

To understand the difference between the two control structures, consider the following switch example from the RFC:

switch (1) {
	case 0:
		$result = 'Foo';
		break;
	case 1:
		$result = 'Bar';
		break;
	case 2:
		$result = 'Baz';
		break;
}
 
echo $result;
//> Bar

We can now get the same result as the code above with the following match expression:

echo match (1) {
	0 => 'Foo',
	1 => 'Bar',
	2 => 'Baz',
};
//> Bar

A significant advantage of using the new match expression is that while switch compares values loosely (==) potentially leading to unexpected results, with match the comparison is an identity check (===).

The match expression may also contain multiple comma-separated expressions allowing for more concise syntax (source):

$result = match ($x) {
	// This match arm:
	$a, $b, $c => 5,
	// Is equivalent to these three match arms:
	$a => 5,
	$b => 5,
	$c => 5,
};

For additional examples and cases of use, see the Match expression v2 RFC and the PHP documentation.

Stricter Type Checks for Arithmetic/Bitwise Operators

In previous PHP versions, applying arithmetic and bitwise operators to an array, resource, or non-overloaded object was allowed. Anyway, the behavior was sometimes inconsistent.

In this RFC, Nikita Popov shows how unreasonable that behavior could be with a simple example:

var_dump([] % [42]);
// int(0)

Nikita explains how applying arithmetic or bitwise operator to arrays, resources, or non-overloaded objects led to different results:

Operators +, -, *, /, **:

  • Throw Error exception on array operand. (Excluding + if both operands are array.)
  • Silently convert a resource operand to the resource ID as an integer.
  • Convert an object operand to integer one, while throwing a notice.

Operators %, <<, >>, &, |, ^:

  • Silently convert an array operand to integer zero if empty or integer one if non-empty.
  • Silently convert a resource operand to the resource ID as an integer.
  • Convert an object operand to integer one, while throwing a notice.

Operator ~:

  • Throw an Error exception for array, resource and object operands.

Operators ++ and –:

  • Silently do nothing if the operand is an array, resource or object.

With PHP 8, things change, and the behavior is the same for all arithmetic and bitwise operators:

Throw a TypeError exception for array, resource, and object operands.

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. The problem is that 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 checks if two values are of the same type. This prevents us from getting 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 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:

Value gettype() get_debug_type()
1 integer int
0.1 double float
true boolean bool
false boolean bool
null NULL null
“WordPress” string string
[1,2,3] array array
A class with name “Foo\Bar” object Foo\Bar
An anonymous class object class@anonymous

Additional RFCs

Here is a quick list of additional approved improvements coming with 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 Performance Benchmarks

If you’re wondering how fast PHP 8 is, we have the answer. We benchmarked 20 PHP platforms/configurations on 7 different PHP versions (5.6, 7.0, 7.1, 7.2, 7.3, and 8.0).

PHP 8.0 emerged as the winner in most platforms that support it, including WordPress and Laravel.

Compiled PHP benchmarks of the top platforms
Compiled PHP benchmarks of the top platforms

For instance, WordPress on PHP 8.0 can handle 18.4% more requests per second than PHP 7.4. Likewise, Laravel on PHP 8.0 can run 8.5% more requests per second than PHP 7.3.

If your website or app is fully compatible with PHP 8.0, you should plan to update your server’s environment to PHP 8.0 as soon as possible. You (and your users) will definitely appreciate its performance benefits. However, please test your site thoroughly before updating.

You can read our PHP benchmarks article for more information, such as detailed performance data, insights, and pretty graphs!

Summary

What a ride! In this post, we covered the most interesting optimizations and features coming with PHP 8. The most awaited of which is surely the Just in Time compiler, but there’s so much more with PHP 8.

Make sure to bookmark this blog post for your future reference. 🤓

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

Carlo Daniele Kinsta

Carlo is a passionate lover of webdesign and front-end development. He has been playing with WordPress for more than 20 years, also in collaboration with Italian and European universities and educational institutions. He has written hundreds of articles and guides about WordPress, published both on Italian and international websites, as well as on printed magazines. You can find him on LinkedIn.