Ready for PHP 8? Let's see what we should expect.

Panos

Panos Kesisis · 03rd August 2020·Optimisation, PHP, Hosting

Ready for PHP 8? Let's see what we should expect.

With an expected release date of December 2020, we are waiting to see tons of new features and improvements from PHP 8.

Core development of PHP 8 started around late January 2019. With about 2 years of active development, this major update should be ready to be released soon.

Of course, once it's released, we will aim to push it across all our servers ASAP and support it. We can also help you upgrading your site and make it compatible with PHP 8. Note that WordPress hasn't officially announced a PHP 8 compatibility yet. If you are unsure, please drop us a message.

A lot of RFCs are already approved and even implemented, so let’s take a look at the exciting new additions which are expected to make PHP more reliable and faster.

PHP JIT (Just in Time Compiler)

The most notable feature is the JIT compiler. But what exactly is it?

JIT is incorporated as a nearly independent part of OPcache. It can be enabled or disabled at run-time and PHP compile time. When in enabled/disabled mode, native code of PHP files is being 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, what is actually the difference between OPcache and JIT?

The PHP execution happens in 4 stages:

  • Lexing or Tokenizing: after reading the PHP code, the interpreter builds a set of tokens.
  • Parsing: after checking of the script matches the syntax rules, the interpreter uses tokens in order to build an AST (Abstract Syntax Tree).
  • Compilation: The interpreter will then travers the tree and translate AST nodes into low level Zend opcodes – they are numeric identifiers which determine the type of the instruction being performed by the Zend VM.
  • Interpretation: Opcodes are being interpreted and run on end VM.
php 8 JIT

PHP New features and improvements

Aside from the new JIT Compiler, a lot of new improvements and features are expected from PHP 8. Here are some of the expected upcoming additions and changes.

Constructor Property Promotion

A proposition was made regarding the Constructor Property Promotion RFC, for a new version of the syntax which will significantly simplify the property declaration, which would make it a lot shorter and less redundant.

The proposition relates to the promoted parameters. At the moment all properties need to be repeated several times prior to being used with objects.

class xyz {
	public int $a;
	public int $b;
	public int $c;

	public function __construct(
		init $a = 0,
		init $b = 0,
		init $c = 0,
	) {
		$this->a=$a;
		$this->b=$b;
		$this->c=$c;
	}
} 

The idea is to merge the parameter definition and the constructor. This way there will be a more usable way to declare the parameters and above code can be changes as such:

class xyz {
	public function __construct(
		public int $a = 0,
		public int $b = 0,
		public int $c = 0,
	) {}
}

Validation for Abstract Trait Methods

The definition of traits is “a mechanism for code reuse in single inheritance languages such as PHP”. Often, they are used for declaring methods which can be used in a number of classes.

One trait can also declare abstract method. They just declare the signature of the method, but the implementation itself needs to be done within the class using the trait.

Also bear in mind, that the signatures of the methods need to match, meaning the type as well as the number of required arguments has to be the same.

RFC proposes to throw a fatal error in case the implementing method is not compatible with the abstract trait method, without regards to its origin.

Fatal error: Declaration of C::xyz(string $a) must be compatible with T::xyz(int $a) in file.php on line 1

Incompatible Method Signature

In the case of PHP, inheritance errors which are due to incompatible method signatures throw either a warming for e certain error or a fatal error.

If for example a class is implementing an interface, there will be a fatal error. The class which implements the interface needs to use a method signature which is compatible with the Liskov Substitution Principle. In other case, this will result in a fatal error.

Bellow you can find an example of such an inheritance error:

interface A {

public function method(array $a);

} class B implements A {

public function method(int $a) {}

}

In PHP 7.4, the above code will result in a fatal error.

A function in a child class having an incompatible signature will show you a warning:

Warning: Declaration of A2::method(int $a) should be compatible with A1::method(array $a) in file.php on line 1

Arrays starting with a negative index

In the case of PHP 7.4 and below, if any array starts with a negative index, the indices will start from 0. You can see an example bellow:

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

This code will give you the following output:

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

The new proposition is to make changes so that the second index would be start_index + 1, regardless of what the value of start_index is. The above code would be rendered as follows in PHP 8:

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

Arrays which start with a negative index will change their behavior and can now start with a negative value.

Union Types 2.0

Union types accept values which can be of different types. At the moment, PHP does not offer support for union types, except of the ? syntax and the special iterable type.

Prior to PHP 8, union types could be specified in PHPdoc annotations, as you can see in the example bellow:

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

RFC proposes adding support for union types in function signatures, so that we would not need online documentation anymore, instead defining with a T1|T2|… syntax. So in theory, something like this:

class Number {
	private int|float $number;

	public function setNumber(int|float $number): void {
		$this->number = $number;
	}

	public function getNumber(): int|float {
		return $this->number;
	}
}

Consistent Type Errors for internal functions

In cases when we pass a parameter of illegal type to a function, user-defined and internal functions behave differently.

User-defined functions will simply throw a TypeError, but that's not the case with the internal functions. The typical behavior is to simply throw a warning and return null.

var_dump(strlen(new stdClass));

Will result in the following warning:

Warning: strlen() expects parameter 1 to be string, object given in file.php on line 1
NULL

If 'strict_types' is enabled, then the behavior will be different. In such case, the type error will be detected and will result in TypeError.

In order to avoid these kinds of problems, this RFC suggests for the internal parameter parsing APIs generating a ThrowError in cases when there is a parameter type mismatch.

The code will change as such in PHP 8:

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

Expression throwing

throw is a statement, meaning you cannot use in places where just expressions are allowed. This RFC suggest converting the throw statement into an expression, making it usable in any context with expressions allowed. Such are null coalesce operator, arrow functions, ternary and elvis operators, etc.

$callable = fn() => throw new Exception();

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

New PHP Functions

Several new functions will be added to the core of php 8:

str_contains

the usual choices for developers were strpos and strstr prior PHP 8. The problem with these functions is that they are not very intuitive and can confuse new developers.

$a = 'This is a test';
$b = 'test';
$pos = strpos($a, $b);

if ($pos !== false) {
	echo "The string has been found";
} else {
	echo "String not found";
}

In this example the !== comparison operator is used to check if two values are of the same type. This will prevent an error in case the position of the needle is 0.

This function can return Boolean FALSE, but it can also return non-Boolean value evaluating to FALSE. […] You need to use the === operator in order to test the return value of the function.

This RFC proposes to introduce a new function which allows to search inside a string: str_contains

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

str_contains checks if $needle can be found in $haystack and in returns shows true or false.

Now the code will look like this:

$a = 'This is a test';
$b   = 'test';

if (str_contains($a, $b)) {
	echo "The string has been found";
} else {
	echo "String not found";
}

str_starts_with() and str_ends_with()

Two new functions will allow you to search for a needle in a certain string: str_starts_with and str_ends_with.

These functions give you details whether a 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 will state false if $needle is longer than $haystack. These functionalities are so often needed that a lot of PHP frameworks provide support for it, including Laravel, Symfony, Yii, Phalcon and FuelPHP.

Now we can avoid using sub-optimal and less intuitive functions such as strops, substr. They are not case sensitive.

$str = "Test";
if (str_starts_with($str, "Te")) echo "Found!";

if (str_starts_with($str, "te")) echo "Not found!";

get-debug_type

This is a new function which returns the type of a variable. It works similarly to the gettype function, but the get-debug_type will return native type names and will resolve class names.

Here is an example better explaining the difference between the two functions:

$a = [1,2,3];

if (!($a instanceof Foo)) { 
	throw new TypeError('Expected ' . Foo::class . ', got ' . (is_object($a) ? get_class($a) : gettype($a)));
}

In PHP 8, you can use get_debug_type, instead:

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

Additional RFCs

Here's a few other approved changes and improvements for PHP 8:

  • Stringable interface: a Stringable interface will be introduced which will automatically be added to classes implementing __to String() method. The idea to use the string|Stringable union type.
  • New DOM Living standard APIs in ext/dom: the RFC suggests implementing the current DOM Living Standard to the PHP DOM extension with the help of new interfaces and public properties.
  • Static return type: PHP 8 will introduce static as return type next to self and parent types.

Conclusion

We tried to cover all the major improvements and additions of PHP 8, the most exciting of which is probably the brand new Just in Time Compiler.

Are you ready for the upcoming changes? Will you be updating to PHP 8 as soon as it is out? Which one are you most excited about? Drop a comment bellow!