La version 8 de PHP a été publiée à la fin de l’année 2020. Cinq ans après la sortie de PHP 7, cette nouvelle version était très attendue par la communauté. Dans le cadre des Midi Formations au sein de SensioLabs, la Team B a travaillé sur les nouveautés à retenir dans PHP 8.0. Découvrez notre sélection des nouvelles fonctionnalités les plus intéressantes qui vont impacter vos applications.

 

Expérience développeur

Parmi les ajouts de fonctionnalités en PHP 8.0, bon nombre d’entre eux ont pour but d’améliorer l'expérience du développeur.

Les paramètres nommés

Les paramètres nommés, contrairement aux traditionnels paramètres positionnels, permettent d'améliorer la lisibilité et de simplifier l’appel d’une fonction lorsqu’on utilise, par exemple, le dernier paramètre optionnel de cette fonction.

Exemple:

<?php

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

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

// après PHP 8
foo(p4: 'bar');

Cette nouveauté peut aussi corriger certains problèmes de cohérence dans la position des paramètres de certaines fonctions PHP. 

Par exemple, les fonctions suivantes :

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

Peuvent maintenant toutes les deux être appelées de cette façon :

<?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]);

 

Promotion des propriétés de constructeurs

Cet ajout permet d'alléger et de rendre moins fastidieuse la définition de propriétés de classes en définissant la visibilité de la propriété directement en paramètre de la fonction __construct. Cette fonctionnalité s’inspire de mécanismes identiques déjà existants dans d’autres langages.

Dans la classe suivante, nous avons trois parties :

  • Déclaration de propriétés;
  • Définition du constructeur; 
  • Assignation des propriétés
<?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;
    }
}

En PHP 8 nous pouvons regrouper les trois parties en une seule :

<?php

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

Les paramètres du constructeur sont automatiquement transformés en propriétés assignées et la visibilité peut y être précisée.

 

L’operateur Null-safe

Ce nouvel opérateur facilitera grandement la vie des développeurs. En effet, il permet de chaîner les appels à des méthodes sans se préoccuper du fait que l’une de ces méthodes renvoie une valeur ce qui causait une erreur fatale en PHP 7.4.

Considérez l’exemple suivant :

<?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();

 

L’expression match()

Cette nouvelle expression est semblable à l’expression switch() mais possède pour avantage que la comparaison effectuée lors de matching cette expression est type-safe et qu’il n’est pas nécessaire de préciser le mot clé break.

Exemple :

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

 

Nouvelles fonctions

PHP 8.0 sort avec 3 nouvelles fonctions sur les chaînes de caractères :

  • str_contains : qui permet de vérifier si une chaîne de caractères contient une sous-chaîne de caractères
  • str_starts_with : qui permet de vérifier si un chaîne commence par une sous-chaîne de caractères
  • str_ends_with : qui permet de vérifier si un chaine finit par une sous-chaîne de caractères

Exemple :

<?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';

 

Autorisation de ::class sur des objets

Il est désormais possible d'utiliser ::class sur les objets. Cela fonctionne de la même manière que 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 a ajouté la prise en charge des références faibles comme moyen de conserver une référence à un objet qui n’empêche pas l’objet lui-même d’être détruit.

PHP 8.0 introduit une classe WeakMap pour créer des objets à utiliser comme clés qui peuvent être retirées de la Weak Map en supprimant l'objet s’il n’y a pas d’autres références.

Ainsi, une WeakMap est un ensemble d'objets dans lequel les clés sont faiblement référencées, ce qui signifie qu’elles n'empêchent pas les objets d’être collectés par le Ramasse-miettes.
 

<?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 comme expression

En PHP < à 8.0, throw est une instruction, il n'est donc pas possible de l'utiliser dans des endroits où seulement une expression est autorisée.

À partir de PHP 8, throw est converti de l'instruction en une expression afin qu'elle puisse être utilisé dans n'importe quel contexte où les expressions sont autorisées

<?php

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

 

 

Plus de logique

PHP 8.0 introduit aussi un fonctionnement plus logique par rapport à sa version précédente.

Comparaisons et conversions plus saines

Si vous migrez une application en PHP >= à 8, vous pourriez avoir des petits soucis si vous n'avez pas utilisé la comparaison stricte.

Avant PHP 8.0 :

  • 0 == 'whatever' est vrai
  • 0 == "" est vrai

Veuillez garder à l'esprit que maintenant, dans les deux cas, le résultat est faux.

<?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 < à 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

 

Tableaux commençant par un index négatif

En PHP < à 8.0, l'index suivant un index négatif était 0.

En PHP 8.0, si on crée un tableau avec un index de -3, les index suivants vont s'incrémenter de un à chaque fois.

<?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]

 

Les grandes nouveautés de PHP 8.0

Enfin, nous avons eu le droit avec PHP 8.0 à des innovations qui améliorent l’utilisation du langage.

Les attributs

Avant PHP 8.0, il était commun, notamment dans Symfony et Doctrine, d’utiliser des Annotations. 

Ces annotations écrites dans les blocs de documentation de PHP étaient créés, lues et interprétées par une librairie PHP (comme doctrine/annotation par exemple) afin d’ajouter un ensemble de métadonnées qui sont ensuite utilisées pour configurer le comportement de la librairie qui les mets à disposition (Validation, Sérialisation, Api Platform, ORM/ODM, ...).

En PHP 8.0, les attributs sont arrivés avec pour but de rendre “native” (plus besoin d’une librairie PHP intermédiaire) les annotations. Le concept reste identique, les Attributs comme les Annotations sont accessibles via Reflection à ceci près que, les Attributs ont maintenant leurs propre méthodes ce qui n’est pas le cas des Annotations qui se trouvaient mélangées avec la documentation PHP et devaient donc être parsée pour être distinguée de la documentation.

Exemple:

<?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 !";
	}
}

 

Les types d’union

Les types d'union acceptent des valeurs qui peuvent être de différents types. Avant PHP8, PHP ne fournissait aucun soutien pour les types d'union, à l'exception de la syntaxe?Type et le type spécial itérable.

<?php

declare(strict_types=1);

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

Si on ne passe pas un flottant ou un entier, php lancera une exception.

 

Pour conclure

Les nouveautés de PHP 8.0 sont donc assez nombreuses. Il y a beaucoup d’éléments permettant de simplifier le code, quelques améliorations concernant les comparaisons et les conversions ainsi que des améliorations de performances.

Nous avons couvert ce qui nous semblait le plus important, mais il y aurait encore énormément à dire. Par exemple, le sujet du compilateur jit qui permet dans certains cas une nette amélioration des performances, mais il pourrait faire l’objet d’un article complet.

Pour consulter l’ensemble des RFCs implémentés dans la nouvelle version de php, rendez-vous à l’adresse suivante : https://wiki.php.net/rfc#php_80

 

Merci à Martin, Kévin, toute la team B pour ce blogpost et à Salah pour la relecture.