Comment les nouveaux types d'intersection dans PHP 8.1 vous offrent plus de flexibilité

0
188

Les types d'intersection sont une nouvelle fonctionnalité du système de types à venir dans PHP 8.1. Ils vous permettent de saisir des valeurs qui doivent satisfaire plus d'une contrainte de type. PHP’a déjà des types d'union qui combinent des types avec un “ou” clause; les types d'intersection offrent un “et” clause à la place.

Certains développeurs tapent déjà des intersections à l'aide d'annotations PHPDoc. L'ajout du support natif est un pas en avant car les types seront effectivement appliqués au moment de l'exécution. Ils sont également entièrement compatibles avec l'héritage de classe, ce qui permet de réduire le risque d'erreurs dues à des annotations obsolètes ou incompatibles.

Syntaxe de base

L'intersection la syntaxe de type est similaire aux types d'union :

class DemoClass {   public Countable|Stringable $Union ;   public Countable&Stringable $Intersection;   }

Dans cet exemple, $Union sera satisfait par n'importe quelle valeur dénombrable ou pouvant être fichée. $Intersection n'acceptera que les valeurs qui répondent aux deux contraintes. Vous obtiendrez une TypeError si vous affectez à la propriété une valeur qui implémente un ou zéro des types suggérés.

Les types d'intersection sont pris en charge partout où les typeshints fonctionnent. Vous pouvez les utiliser avec des définitions de propriétés, des paramètres de fonction et des valeurs de retour.

Contrairement aux types union, les intersections ne peuvent que taper des interfaces et des classes. Une intersection scalaire comme int&string n'a pas de sens car elle ne pourrait jamais être satisfaite. Le type mixte est également interdit, car chaque valeur satisfera sa contrainte.

Type Variance

Les types d'intersection respectent les règles de variance existantes de PHP. Les types de retour des méthodes substituées doivent être covariants, tandis que les types de paramètres sont contravariants. Les propriétés de classe sont toujours invariantes, donc les enfants ne peuvent pas changer la définition de type à moins que la nouvelle signature soit à la fois un sous-type et un super-type de celle héritée.

Publicité

Pour les valeurs de retour, les règles de variance signifie que vous pouvez ajouter des types d'intersection supplémentaires dans les méthodes remplacées. Les méthodes peuvent également supprimer, mais pas ajouter, des types aux intersections utilisées comme paramètres. Ces règles appliquent le principe de substitution de Liskov.

interface I1 { fonction publique method1(A&B $demo) : X&Y; fonction publique method2(A&B $demo) : X&Y; }   l'interface I2 étend I1 {   //Fonction publique autorisée method1(A $demo) : X&Y&Z;   //NON AUTORISÉ fonction publique method2(A&B&C $demo) : X;   }

Dans le cas de la méthode 1, les contraintes n'ont pas réellement changé. Vous déclarez que la méthode remplacée peut en fait fonctionner avec n'importe quel A, même s'il ne s'agit pas également d'un X. C'est moins spécifique, ce qui entraîne une variance de paramètre acceptable. La déclaration de retour est plus spécifique, indiquant que la valeur implémentera X, Y et Z ; dans ce scénario, l'ajout de spécificité ne rompt pas le contrat, car la valeur sera toujours acceptée par tout ce qui tape I1.

Le remplacement de method2 est rompu dans les deux cas. En exigeant que le paramètre d'entrée satisfasse A, B et C, il ne remplace plus I1. De même, method2 garantit uniquement que la valeur de retour sera une instance de X. Cela rompt le contrat car tout type d'indication I1 nécessite que la valeur satisfasse l'intersection de X et Y.

Les intersections inversent les règles de variance pratique des types d'union. Comme les unions sont combinées en utilisant “ou,” les enfants peuvent ajouter des types de paramètres et supprimer des types de retour. Le principe de substitution de Liskov est satisfait lorsque l'effet d'élargissement et de rétrécissement des contraintes de type est inversé.

Comme pour les types d'union, les règles de variance s'appliquent également aux types qui composent une intersection – ce sont les parties X et Y individuelles de X&Y. Vous pouvez restreindre un type en le renvoyant – indiquant que vous renverrez une sous-classe – ou l'élargir en tant que paramètre, en acceptant une super-classe.

Publicité

Les intersections possèdent également des règles spéciales concernant les alias et les implémentations concrètes. Si vous tapez X&Y mais écrivez une interface Z qui étend X, Y, il est logique que Z satisfasse la contrainte. Par conséquent, vous pouvez taper l'indice Z au lieu de X&Y partout où la covariance est autorisée :

interface Test étend X, Y {}   interface Demo1 { démonstration de fonction publique() : X&Y; }   interface Demo2 { démonstration de fonction publique() : Test; }

Cela vous permet de taper une classe ou une interface concrète si vous dépendez de fonctionnalités supplémentaires. C'est acceptable tant que toutes les contraintes de l'intersection sont satisfaites par l'indice de type final.

Quand utiliser les types d'intersection ?

Les types d'intersection sont pour les moments où vous voulez être sûr qu'une valeur satisfait une interface composée sans réellement définir cette interface. Les versions précédentes de PHP ne fournissaient aucun support natif pour cela, vous deviez donc ajouter un gâchis de passe-partout supplémentaire à votre base de code :

interface CountableStringable étend Countable, Stringable { //… }   la classe FakeArray implémente CountableStringable {   tableau public $items = [];   nombre de fonctions publiques() : entier { retour compte($this -> articles); }   fonction publique __toString() : chaîne { renvoie imploser(", ", $this -> articles); }   }   classe DemoClass {   public CountableStringable $Value;   }

Cela conduit à un excès d'interfaces peu profondes de type stub pour obtenir un comportement d'intersection de base. Bien que les développeurs PHP aient survécu sans intersections à ce jour, leur présence contribue à solidifier les capacités composées du système de types. L'utilisation d'intersections natives crée un code plus propre et plus intuitif.

Qu'en est-il des types composites ?

Il n'est pas possible de combiner les types intersection et union dans le même typehint. Bien que cela soit techniquement possible, cela a été omis de la RFC actuelle car il existe des ambiguïtés autour de la syntaxe, de la priorité et de la variance.

Les types composites restent une idée pour l'avenir. S'ils étaient ajoutés, vous pourriez commencer à ajouter des astuces de type complexes comme celle-ci :

classe DemoClass {   public Countable&Stringable|CountableStringable $Intersection;   } Advertisement

Cet exemple de classe combine les deux implémentations ci-dessus. Si cela fonctionnait, cela vous permettrait d'adopter des types d'intersection natifs tout en conservant une compatibilité descendante avec les anciennes classes utilisant le “faux” approche.

Les types composites à part entière compléteraient la gestion des types multiples facilitée par les unions et les intersections. En attendant, vous devrez continuer à écrire vos propres interfaces composites dans ces scénarios.

Résumé

Les types d'intersection arrivent dans PHP 8.1 et débloquer des possibilités plus avancées dans le système de type. L'extension des options autour des types composites réduit la quantité de code que vous devez écrire lorsque plusieurs interfaces sont prises en charge.

Les intersections sont une fonctionnalité facultative qui n'introduira aucune incompatibilité avec le code existant. La RFC a été implémentée dans la troisième version alpha de PHP 8.1. La version finale arrivera fin novembre plus tard cette année.