New Features And Breaking Changes PHP8

29th October 2020

PHP8 will be released on November 26th 2020 and there is lots being written about the upcoming features in this release. As this is a major version there will be new features as well as breaking changes so it's important to be aware of what is changing. This will make it easier to think about how PHP8 will effect your applications and what actions you will need to take to ensure you can upgrade without incident.

I thought I would go through a few of the main changes to see whats going to be in the next PHP release.

Running PHP8

A good first step is to look at how to install PHP8 so that you can check it out for yourself. If you are using Ubuntu then the simplest way to install PHP8 is to use the existing ondrej/php PPA library. This can be installed using the following commands.

  1. sudo add-apt-repository ppa:ondrej/php
  2. sudo apt-get update

You can make sure that the right library is installed by searching for PHP8.

sudo apt-cache search php8.0

Assuming you see some output from the last command you can now install PHP8 using this command.

sudo apt-get install php8.0

Once that has run you can now run PHP8.

  1. $ php --version
  2. PHP 8.0.0rc1 (cli) (built: Oct 18 2020 19:43:43) ( NTS )
  3. Copyright (c) The PHP Group
  4. Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies
  5. with Zend OPcache v8.0.0rc1, Copyright (c), by Zend Technologies

Aside from installing it yourself, you can also run PHP8 right now on the following platforms.

This is handy if you area also hosting with these platforms as it will help you test your application in plenty of time before the release. If you know of any other platforms running supporting PHP8 then post a comment and let me know.

New Features

There's plenty of new features going into PHP8. I've read plenty of articles around the internet about these new features, but I thought I would dive into each one in detail.

Just-In-Time Compiler (RFC)

The Just-In-Time (or JIT) complier was born as a result of speed improvements made before the release of PHP7. It is being introduced to PHP8 as there are probably no more speed improvements that can be made without the use of a JIT. The idea is that it will further improve PHP performance.

When PHP code is executed it is translated into bytecodes and those bytecodes are used to execute the steps in the program. A JIT means that PHP will analyse the code being executed and will be able to make real time decisions over performance improvements on the code as it is being executed. The idea is that it will be of use in CPU intensive applications and not necessarily when used in web based scenarios. This means that server side PHP applications might be more prevalent with a JIT system built into PHP.

To use the JIT you first need to activate it. On my test system (Ubuntu 20.04) I already have the the PHP opcache module installed, which was installed with the main PHP8 package. This is configured in the file located at /etc/php/8.0/cli/conf.d/10-opcache.ini.

To activate the JIT you need to enable the opcache and assign some memory to the opcache.jit_buffer_size setting. This is what the file looks like on my system.

  1. ; configuration for php opcache module
  2. ; priority=10
  3. zend_extension=opcache.so
  4.  
  5. opcache.enable_cli=1
  6. opcache.jit_buffer_size=256M

You can ensure that it's active you can use the opcache_get_status() function. You can look at the 'jit' part of this array to get information about the current status of JIT.

var_dump(opcache_get_status()['jit']);

This should print out something like the following if JIT has been correctly activated.

  1. array(7) {
  2. ["enabled"]=>
  3. bool(true)
  4. ["on"]=>
  5. bool(true)
  6. ["kind"]=>
  7. int(5)
  8. ["opt_level"]=>
  9. int(4)
  10. ["opt_flags"]=>
  11. int(6)
  12. ["buffer_size"]=>
  13. int(268435440)
  14. ["buffer_free"]=>
  15. int(268432880)
  16. }

So is it faster? In a word, yes. I've seen a few people doing benchmarks using a mandlebrot set so I decided to use a library that I created a while ago that draws a few different types of fractals in PHP. All I did was generate three fractals and time how long it took using the microtime() function. Here are the results for PHP 7.4.8.

  1. Burningship
  2. 84.20269203186
  3.  
  4. Mandlebrot
  5. 21.552599906921
  6.  
  7. Tricorn
  8. 32.685042858124

When I ran exactly the same code on PHP8 it was substantially faster. The numbers speak for themselves.

  1. Burningship
  2. 15.272277116776
  3.  
  4. Mandlebrot
  5. 3.7528541088104
  6.  
  7. Tricorn
  8. 4.4957919120789

This massive speed increase is really interesting. The code I used here creates decent sized fractals, but I remember when creating the code that most of my time was spent waiting for the fractals to be generated. This addition is of interest to me as I have pushed PHP to its limits a few times (outside of generating fractals). I can see this being of real benefit to the future of PHP and will allow the language to be selected for situations outside of the usual website language.

I haven't look at the speed increase for applications like Drupal or WordPress, but from what I had read there is probably little difference in those types of application. I'll be running benchmarks on these platforms in the future to see what kind of a difference the JIT makes there.

Union Types (RFC)

Since PHP7 it has been possible to stipulate what kind of types arguments and return values must have. This will allow PHP to throw an error if the type of argument you are passing is a different type to the expected type. In a loosely typed language like PHP it is important to ensure that functions both receive and produce the correct types of value.

In PHP8 it is now possible to stipulate different types for the arguments and return values, separated by a pipe character. Here is an example of a function that can accept either an integer or a float value.

  1. function addNumbers(int|float $number1, int|float $number2) : int|float
  2. {
  3. return $number1 + $number2;
  4. }

Previously, a function like this would need to be created without any type hinting as PHP could silently cast the type if the passed argument wasn't correct. This meant that if we set the argument type as an integer then PHP would cast any float values into an integer, which can lead to some tricky bugs to catch if you aren't unit testing.

To use the above function we just call it like any other.

  1. echo addNumbers(1, 1); // prints 2
  2. echo addNumbers(1.1, 1.1); // prints 2.2

If we try to pass a string to the function like this.

echo addNumbers('one', 'two');

We will receive a PHP Fatal error stating that we need to pass either an int or a float into the function.

  1. PHP Fatal error: Uncaught TypeError: addNumbers(): Argument #1 ($number1) must
  2. be of type int|float, string given, called in union_types.php on line 10 and defined
  3. in union_types.php:3

This is a simple example, but we can expand on this and create a function that accepts multiple different types of variable. The following is an example of a terribly designed function that shows how this feature can be expanded.

  1. function printNumber(int|float|string|array|bool|null $number) : void
  2. {
  3. printf("%f\n", $number);
  4. }

The exception to this rule is the void type, which can be see in the above function. The void type can't be used as a union type as it stipulates that the function will return nothing. In other words, you can't say that a function will return an integer or a void or you will receive a PHP fatal error.

Whilst a simple change I can see this feature being used quite a bit as it has previously only been possible to stipulate different types of value in comments, which led to doc block comments becoming more descriptive than code.

The Nullsafe Operator (RFC)

In addition to the null coalescing operator there is now the ability to detect null return values directly from methods. The null coalescing operator, if you weren't aware, allows us to get a value without having to test if the value is present and to return a different value if the first value is null. This means that we can do things this to grab a value from the $_GET super global or 0 if that value isn't present.

  1. $page = $_GET['page'] ?? 0;
  2. echo $page;

The nullsafe operator works in a similar way, but allows us to create a handy shortcut to test for a null return from a method before trying to use that value. Take the following couple of classes that create a Horn and a Car object.

  1. class Horn {
  2. public function beep() {
  3. print 'beep';
  4. }
  5. }
  6.  
  7. class Car {
  8. protected $horn;
  9.  
  10. public function setHorn(Horn $horn) {
  11. $this->horn = $horn;
  12. }
  13.  
  14. public function getHorn() {
  15. return $this->horn;
  16. }
  17. }

In this situation it is possible to create a Car object without an Horn object to drive the horn. In which case we need to make sure that the getHorn() method doesn't return null before trying to use it. Normally this would mean if statements and is_null() checks to ensure that things are present, but with the nullsafe operator we can do this inline. The following example creates a Car object and the calls the beep() method only if the getHorn() method does not return a null value.

  1. $car = new Car();
  2. $car->getHorn()?->beep();

I can see that this will be incredibly useful in Drupal where I tend to write a lot of checking code to make sure that object properties or returns from methods actually have things in them before using them. This is necessary due to the contextual nature of the objects in Drupal due to the content they contain and this change will certainly simplify some of the checks.

Named Arguments (RFC)

Named arguments allows you to call functions and stipulate a different order of arguments. Take the following simple function that has two parameters and fills an array to the specified length.

  1. function fillArray(array $arrayToFill, int $number) : array
  2. {
  3. for ($i = 0; $i < $number; ++$i) {
  4. $arrayToFill[$i] = 1;
  5. }
  6. return $arrayToFill;
  7. }

We can call this method in the normal way by passing the arguments in the order they are defined.

$newArray = fillArray([], 2);

As of PHP8 we can now name the parameters as we pass them to the function, which also allows us to send the parameters in any order we want.

$newArray = fillArray(number: 2, arrayToFill: []);

A simple example, but it can also make the code more readable.

This technique works with any function in PHP, not just user defined ones. With PHP being a language where array and string functions have different orders of parameters with is a really welcome addition.

Attributes V2 (RFC1 RFC2 RFC3)

Attributes provide a mechanism to add metadata to PHP classes, methods, functions, class properties, function parameters, and constants. They aren't directly accessible via code and need to be pulled out using PHP built in reflection classes. The ReflectionClass class has existed in PHP since PHP5, but new for PHP8 is the getAttribute() method. This method returns an array of ReflectionAttribute objects that will contain information about attributes.

This addition has gone through a few changes (as you can see from the multiple RFCs above) but the current syntax to add attributes to you code is as follows.

  1. #[Automobile]
  2. #[MyCustomAttribute(1, 'string')]
  3. class Car {
  4. #[ThingSetup(123)]
  5. public function doThing() {}
  6. }

If we instantiate the class we can then use the ReflectionClass to print out the attribute information contained on a class level.

  1. $car = new Car();
  2.  
  3. $reflectionClass = new ReflectionClass($car);
  4. foreach ($reflectionClass->getAttributes() as $attribute) {
  5. echo $attribute->getName() . ' ' . PHP_EOL;
  6. $arguments = $attribute->getArguments();
  7. foreach ($arguments as $argument) {
  8. echo '-' . $argument . PHP_EOL;
  9. }
  10. }

This prints out the following.

  1. Automobile
  2. MyCustomAttribute
  3. -1
  4. -string

We can use the getMethods() method of the ReflectionClass to find and report on attributes on the methods in the Car class.

  1. foreach ($reflectionClass->getMethods() as $method) {
  2. foreach ($method->getAttributes() as $attribute) {
  3. echo $attribute->getName() . ' ' . PHP_EOL;
  4. $arguments = $attribute->getArguments();
  5. foreach ($arguments as $argument) {
  6. echo '-' . $argument . PHP_EOL;
  7. }
  8. }
  9. }

This code prints out the following.

  1. ThingSetup
  2. -123

There is actually quite a lot to attributes in PHP8 so I would recommend reading through the RFCs to get familiar with what they are and how they can be integrated into your code.

Match expression (RFC)

The new match expression is like a short hand switch statement. It looks a little like a function declaration that will return a value based on the value passed in. The syntax of the match statement looks like this.

  1. $value = 2;
  2.  
  3. $result = match($value) {
  4. 1 => 'One',
  5. 2 => 'Two',
  6. 3 => 'Three',
  7. };
  8.  
  9. echo $result; // Prints 'Two'.

One thing to be careful of is that the switch statement uses a '==' comparison and the match statement uses a '===' comparison. Also, the word 'match' is now a reserved keyword so you can't have any classes or functions called match any more.

Breaking Changes

Since this is a new version of PHP there will be a few changes that will break existing code, and the PHP team has been hard at work compiling a list of breaking changes in this upcoming release. Breaking changes are mainly due to code that was marked as deprecated in PHP7 being removed in the transition to PHP8. I have picked out some of the more interesting changes that you might want to be aware of here.

Constructor Methods Removal

When object orientation was added to PHP it was possible to create constructor methods that had the same name as the class, which is similar to how other languages like Java work. The __construct() method was added in PHP5 and since then it has been advised to move constructors over to the new format. With PHP8 it is no longer possible to use the class name as a constructor so you must use the __construct() method format.

Removed each() Method

The each() method retrieved the next item and key from an array and advanced the pointer on by one. This was a convenient way to move the pointer to the next item in the array at the same time as getting a value out of the array. This has now been removed and foreach or ArrayIterator should be used instead.

Curly Braces For Offset Access Removed (RFC)

It is no longer possible to use curly braces to access elements in an array or characters in a string. I have to admit I haven't seen this being used recently, but you might find some older code that contains this kind of code. This is an example of this feature taken from the PHP docs.

  1. $array = [1, 2];
  2. echo $array[1]; // prints 2
  3. echo $array{1}; // also prints 2
  4.  
  5. $string = "foo";
  6. echo $string[0]; // prints "f"
  7. echo $string{0}; // also prints "f"

Stricter Arithmetic Operator Type Checks (RFC)

In previous versions of PHP, running an arithmetic or bitwise operation on an array or object would return a nonsense value.

var_dump([123] % [321]); // Prints "int(0)".

Running the same code in PHP8 will produce a TypeError.

PHP Fatal error:  Uncaught TypeError: Unsupported operand types: array % array in /vagrant/union_types.php:3

The @ Operator No Longer Silences Fatal Errors

Since it was possible to catch fatal errors in PHP7 it has also been possible to suppress them with the @ operator. In PHP8 this is no longer the case and and fatal errors might get revealed that would have been hidden in PHP7. Make sure to always set display_errors to off on your production servers so that you don't leak any errors to your users if this does happen.

Saner Numeric Parsing For Strings (RFC)

PHP does a lot of smart things when parsing strings into number values. In PHP8 this is changing slightly so it's worth checking the RFC to make sure that you understand where the changes are coming from. Basically, don't always rely on a string value of "42" being interpreted into an integer correctly.

Saner Numeric Strings Comparison (RFC)

Some minor changes have been made to how PHP compares strings and numbers. Check the RFC for details, but for an example the following will return true in PHP7 and false in PHP8.

var_dump(42 == "42abc");

Conclusion

There are a few changes going into PHP8, but it looks like most of the deprecated code being removed is for older features that I haven't seen being used for a while. I'm really interested to see what the JIT engine will have on codebases I use, and even on the wider PHP community. That said, I heavily recommend scanning your codebase for PHP8 incompatibilities and running your unit tests to make sure that your PHP applications will function correctly before going for the upgrade.

For the time being I would reading through the breaking changes document and keep an eye on the PHP8 release calendar for any announcements.

Add new comment

The content of this field is kept private and will not be shown publicly.