Version 8 of PHP was deployed at the end of 2020. Five years after PHP 7, this new version has been highly anticipated by the community. Developers from SensioLabs prepared a one-hour training session about the new features you need to know in PHP 8.0. Discover our selection of the most interesting new features that will impact your applications.

 

Developer Experience

Among the feature additions in PHP 8.0, many of them aim at improving the developer experience.

Named parameters

Unlike traditional positional parameters, named parameters improve readability and simplify the call of a function when using, for instance, the last optional parameter of this function.

Example:

<?php

function foo(string $p1 = '', string $p2 = '', string $p3 = '', string $p4 = '')
{
    echo $p1 . ' ' . $p2 . ' ' . $p3 . ' ' . $p4;
}

// before PHP 8
foo('', '', '', 'bar');

// with PHP 8
foo(p4: 'bar');

 

This new feature can also correct consistency issues about the position of parameters of some PHP functions. 

For example, the following functions :

 array_map ( callable|null $callback , array $array , array ...$arrays ) : array

array_filter ( array $array , callable|null $callback = null , int $mode = 0 ) : array

 

Can now both be rearranged this way:

<?php

array_map(callback: fn($value) => ++$value, array: [1, 2, 3, 4, 5]);

array_filter(callback: fn($value) => 0 !== $value, array: [1, 2, 3, 4, 5]);

 

The promotion of constructor properties

This add-on makes the definition of class properties easier and less tedious by defining the visibility of the property directly as a parameter of the __construct function. This functionality is inspired by identical mechanisms already existing in other languages.

In the following class we have three parts:

  • Property declaration;
  • Definition of the constructor; 
  • Assignment of properties

<?php

class Address
{
    private string $street;
    private string $city;
    private string $country;

    public function __construct(string $street, string $city, string $country))
    {
        $this->street = $street;
        $this->city = $city;
        $this->country = $country;
    }
}

 

In PHP 8, we can group these three parts into one:

<?php

class Address
{
    public function __construct(
        private string $street,
        private string $city,
        private string $country,
    )
    {}
}

 

The parameters of the constructor are automatically transformed into assigned properties and you can specify the visibility there.

The Null-safe operator

This new operator will make developers’ lives a lot easier. In fact, you can chain calls to methods. You don’t need to worry about the fact that one of these methods returns a value causing a fatal error in PHP 7.4.

Consider the following example:
 

<?php

class Foo {
	private ?Bar $bar = null;

	public function getBar(): ?Bar
	{
		return $this->bar;
	}
}

class Bar {
	private string $baz = "someString";

	public function getBaz(): string
	{
		return $this->baz;
	}
}

$foo = new Foo;

// PHP 7.4
if (null !== $foo->getBar()) {
	echo $foo->getBar()->getBaz();
}

// PHP 8.0
echo $foo->getBar()?->getBaz();

 

The match() expression

This new expression is similar to the switch() expression. However, its advantage is that the comparison you perform when matching this expression is type-safe and that it is not necessary to specify the break keyword.

Example:

<?php

$foo = match(0) {
    0.0 => 'float has matched',
    0 => 'integer has matched',
    '0' => 'string has matched',
};

// print : integer has matched
echo $foo;

$foo = match(0.0) {
    0.0 => 'float has matched',
    0 => 'integer has matched',
    '0' => 'string has matched',
};

// print : float has matched
echo $foo;

$foo = match('0') {
    0.0 => 'float has matched',
    0 => 'integer has matched',
    '0' => 'string has matched',
};

// print : string has matched
echo $foo;

 

 

New functions

PHP 8.0 comes with 3 new functions on strings:

  • str_contains: to check if a string contains a substring
  • str_starts_with: to check if a string starts with a substring
  • str_ends_with: to check if a string ends with a substring

Example:

<?php

// PHP 7.4
// prints : contains
echo -1 !== strpos('Foo', 'Bar Foo Baz') ? 'contains' : 'does not contains';

// PHP 8.0
// prints : contains
echo str_contains('Bar Foo Baz', 'Foo') ? 'contains' : 'does not contains';


// PHP 7.4
// prints : starts with
echo preg_match('/^Bar/', 'Bar Foo Baz') ? 'starts with' : 'does not starts with';

// PHP 8.0
// prints : starts with
echo str_starts_with('Bar Foo Baz', 'Bar') ? 'starts with' : 'does not starts with';


// PHP 7.4
// prints : ends with
echo preg_match('/Baz$/', 'Bar Foo Baz') ? 'ends with' : 'does not ends with';

// PHP 8.0
// prints : ends with
echo str_ends_with('Bar Foo Baz', 'Baz') ? 'ends with' : 'does not ends with';

Authorization of ::class on objects

It is now possible to use ::class on objects. It works in the same way as get_class().

<?php

namespace SL\PHP8;

class Person {}

$p = new Person();

// Print : string(14) "SL\PHP8\Person"
var_dump($p::class);

 

Weak Maps

PHP 7.4 added support for weak references. It is a way to keep the reference to an object that does not prevent the object itself from being destroyed.

PHP 8.0 introduces a WeakMap class to create objects as removable keys from the Weak Map by deleting the object if there are no other references.

This way, a WeakMap is a set of objects in which the keys are weakly referenced. Meaning that they do not prevent the objects from being collected by the Crumb Picker.

<?php

$map = new WeakMap;
$obj = new stdClass;
$map[$obj] = 1;

var_dump($map);
/** Print :
object(WeakMap)#1 (1) {
	[0]=>
	array(2) {
		["key"]=>
		object(stdClass)#2 (0) {
		}
		["value"]=>
		int(1)
	}
}
*/

unset($obj);

var_dump($map);
/** Print :
object(WeakMap)#1 (0) {
}
*/

 

Throw as an expression

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

As of PHP 8, throw is converted from a statement to an expression so that you can use it in any context where expressions are allowed.

<?php

function div(int $a, int $b)
{
    return $b !== 0
        ? $a / $b
        : throw new \InvalidArgumentException('"b" should not be equal to zero.');
}

 

 

More logic

PHP 8.0 also introduces more logical operations compared to its previous version.

Healthier comparisons and conversions

If you migrate an application in PHP >= to 8, you might have some minor issues if you did not use strict comparison.

Before PHP 8.0 :

0 == 'whatever' is true

0 == "" is true

Please keep in mind that now, in both cases, the result is false.

<?php

function test($value) {
    return sprintf("Cast \"%s\" to int : %d.\n", $value, toInt($value));
}

function toInt(int $value) {
    return $value;
}

echo test('    1');
echo test('1    ');
echo test('1a');

PHP < PHP 8.0 :

Notice: A non well formed numeric value encountered in /in/68KAX on line 6
Cast "    1" to int : 1.

Notice: A non well formed numeric value encountered in /in/u4Wts on line 6
Cast "1    " to int : 1.

Notice: A non well formed numeric value encountered in /in/u4Wts on line 6
Cast "1a" to int : 1.

 

PHP 8.0 :

Cast "    1" to int : 1.

Cast "1    " to int : 1.

Fatal error: Uncaught TypeError: toInt(): Argument #1 ($value) must be of type int, string given

 

Tables starting with a negative index

In PHP < to 8.0, the index following a negative index was 0.

In PHP 8.0, if we create an array with an index of -3, the following indexes will increment by one each time.

<?php

$a = array_fill(-3, 5, true);

var_dump($a); // result [-3, -2, -1, 0, 1]
var_dump($a); // in previous versions => result [-3, 0, 1, 2, 3]

 

Great new features

Finally, PHP 8.0 introduces innovations to improve the use of the language.

The attributes

Before PHP 8.0, it was common to use Annotations in Symfony and Doctrine. 

These annotations are written in PHP documentation blocks and created, read, and interpreted by a PHP library (like doctrine/annotation for example) to add a set of metadata. These metadata are then used to configure the behavior of the library providing them (Validation, Serialization, API Platform, ORM/ODM, ...).

In PHP 8.0, the attributes are now available to make the annotations "native" (no need for an intermediate PHP library). The concept remains the same. Both Attributes and Annotations are accessible via Reflection except that Attributes now have their methods. It is not the case for Annotations that were mixed with PHP documentation. So they had to be parsed to distinguish them from the documentation.

Example:

<?php
// PHP 7.4
namespace App\Controller;

use Symfony\Component\Routing\Annotation\Route;

class HelloWorldController
{
	/** @Route('/foo', methods={"GET"}) */
	public function __invoke()
	{
		echo "Hello world !";
	}
}

// PHP 8.0
namespace App\Controller;

use Symfony\Component\Routing\Annotation\Route;

class HelloWorldController
{
	#[Route('/foo', methods: ["GET"])]
	public function __invoke()
	{
		echo "Hello world !";
	}
}

 

The union types

Union types accept values of different types. Prior to PHP 8, PHP did not provide any support for union types, except for the ?Type syntax and the special iterable type.

<?php

declare(strict_types=1);

function square(int|float $number): int|float
{
    return $number * $number;
}

 

If we don't pass a float or an integer, PHP will throw an exception.

To conclude

There are many new features in PHP 8. There are many elements to simplify the code. PHP 8 also brings many improvements concerning comparisons, conversions, and performance improvements.

We have covered the most important things, but there is still a lot more to say. For example, the jit compiler provides in some cases a significant performance improvement. But it could be the subject of a dedicated article.

 

To consult all the RFCs implemented in the new version of php, go to the following address: https://wiki.php.net/rfc#php_80

 

Thanks to Martin, Kévin, all the B team for this blog post and to Salah for the proofreading.