6. Les interfaces
Une interface est une structure définissant des prototypes de méthodes. Une classe implémentant une interface doit définir le code de toutes les méthodes de l'interface. Une interface est un contrat : elle dit ce que sait faire un objet, sans dire comment il le fait.
6.1. L'arborescence des scripts
Les scripts de ce chapitre sont dans le dossier [scripts-console/interfaces] :
6.2. Une première interface
Nous définissons une interface [IExemple] dans un script [IExemple.php]. Dans ce même script, nous définissons deux classes implémentant l'interface [IExemple] :
Commentaires
- lignes 10-15 : l'interface [IExemple] définit deux méthodes : la fonction [ajouter] (ligne 13) et la fonction [soustraire] (ligne 15). L'interface ne définit pas le code de ces méthodes. Ce sont les classes implémentant l'interface qui vont le faire. Les méthodes d'une interface sont obligatoirement publiques ;
- ligne 19 : la classe [Classe1] implémente ([implements]) l'interface [IExemple]. Elle définit donc un code pour les méthodes [ajouter] (ligne 21) et [soustraire] (ligne 26). Les noms des paramètres peuvent différer de ceux de l'interface ([$a] au lieu de [$i]) mais leurs types doivent être compatibles. Attention toutefois : avec les arguments nommés de PHP 8, un appel [ajouter(i: 1, j: 2)] ne fonctionnerait pas avec [Classe1]. Il est donc préférable de garder les noms de l'interface ;
- lignes 33-44 : idem pour la classe [Classe2] .
Nous utilisons l'interface [IExemple] dans le script [interface-01.php] suivant :
Résultats :
Commentaires
- lignes 7-10 : inclusion et déclaration des classes et interfaces utilisées par le script. Le fichier [IExemple.php] est inclus avec l'instruction [require_once] : il ne sera inclus qu'une fois même si plusieurs scripts du projet l'incluent ;
- lignes 13-17 : on définit une fonction ;
- ligne 13 : nous avons déclaré ici le type des trois paramètres. Si à l'exécution, le paramètre effectif n'est pas du type attendu, une erreur [TypeError] est lancée. Le 3e paramètre est de type [IExemple]. Cela veut dire que le paramètre effectif doit être une instance de classe implémentant l'interface [IExemple], donc dans notre exemple, une instance des classes [Classe1] ou [Classe2]. La fonction [calculer] ne connaît pas ces classes : elle ne connaît que l'interface. On pourra lui passer des objets de classes écrites plus tard, pourvu qu'elles implémentent l'interface [IExemple] ;
- lignes 15-16 : nous utilisons les méthodes [ajouter, soustraire] de l'interface [IExemple] ;
- ligne 24 : le 3e paramètre effectif est de type [Classe1] compatible avec le type [IExemple] du 3e paramètre formel de la fonction ;
- ligne 25 : idem pour la classe [Classe2] ;
- ligne 28 : une interface ne peut pas être instanciée : on obtient une erreur (ligne 6 des résultats).
6.3. Dérivation d'une interface
De la même façon qu'une classe fille peut étendre une classe parent, une interface fille peut étendre une interface parent.
Considérons l'interface [IExemple2] définie dans le fichier [IExemple2.php] :
Commentaires
- ligne 7 : l'interface [IExemple2] sera dans le même espace de noms que l'interface [IExemple] ;
- ligne 10 : on inclut le fichier de définition de l'interface [IExemple] ;
- lignes 13-17 : définissent l'interface [IExemple2] qui étend ([extends]) l'interface [IExemple] et lui ajoute une nouvelle méthode (ligne 16). Une interface peut même étendre plusieurs interfaces : [interface I3 extends I1, I2] ;
- lignes 20-28 : la classe [Classe3] étend la classe [Classe2]. Comme celle-ci implémente l'interface [IExemple], ce sera également le cas de la classe [Classe3]. En effet, [Classe3] hérite de toutes les méthodes publiques et protégées de la classe [Classe2], donc des méthodes [ajouter] et [soustraire]. Par ailleurs, on indique que [Classe3] implémente l'interface [IExemple2]. À ce titre, elle doit également implémenter la méthode [multiplier], ce que ne fait pas [Classe2]. C'est ce que font les lignes 24-27 ;
- ligne 23 : depuis PHP 8.3, l'attribut [#[\Override]], rencontré au chapitre précédent, s'applique aussi à une méthode qui implémente une méthode d'interface. Si la méthode [multiplier] disparaissait de l'interface [IExemple2], PHP le signalerait.
L'interface [IExemple2] est exploitée par le script [interface-02.php] suivant :
Résultats :
Commentaires
- ligne 12 : le 3e paramètre de la fonction [calculer] est de type [IExemple2] ;
- lignes 14-16 : on utilise les trois méthodes de l'interface [IExemple2] : les deux héritées de [IExemple] et la sienne propre ;
- ligne 23 : on appelle la fonction [calculer] avec comme 3e paramètre un objet de type [Classe3] qui implémente [IExemple2] (ligne 21).
6.4. Passage d'une interface en paramètre d'une fonction
Nous avons vu au chapitre précédent (paragraphe « Passage d'un objet en paramètre d'une fonction ») que lorsque le type attendu pour un paramètre de fonction est une classe alors le paramètre effectif peut être un objet du type attendu ou dérivé. Il en est de même pour les interfaces comme le montre le script suivant [interface-03.php] :
Résultats :
Commentaires
- ligne 15 : la fonction [calculer] attend comme troisième paramètre un type [IExemple]. Cela signifie que le type du paramètre effectif pourra être de type [IExemple] ou dérivé ;
- ligne 23 : on instancie un objet [$c3] de type [Classe3], classe qui implémente l'interface [IExemple2] qui elle-même étend l'interface [IExemple]. Donc finalement [$c3] implémente l'interface [IExemple] ;
- ligne 25 : on appelle la fonction [calculer] avec comme troisième paramètre l'objet [$c3] de type [Classe3]. Par le jeu des héritages de classes et des implémentations, on a vu que l'objet [$c3] implémentait le type [IExemple]. On peut donc l'utiliser comme troisième paramètre ;
- lignes 27-30 : l'opérateur [instanceof] teste si un objet implémente une interface. Un objet [Classe1] implémente [IExemple] mais pas [IExemple2] (ligne 4 des résultats). Un objet [Classe3] implémente les deux (ligne 5) ;
- ligne 32 : la fonction [class_implements] rend la liste des interfaces implémentées par un objet ou une classe, directement ou par héritage (lignes 6-10 des résultats).
6.5. Constantes d'interface, interfaces multiples et types intersection
Une interface peut aussi déclarer des constantes, et une classe peut implémenter plusieurs interfaces. Nous définissons deux interfaces [ICalcul] et [INommable] et deux classes qui les implémentent dans le fichier [ICalcul.php] :
Commentaires
- ligne 13 : l'interface [ICalcul] déclare une constante. Toutes les classes qui implémentent l'interface en héritent. Depuis PHP 8.3, une constante de classe ou d'interface peut être typée (ici [int]) ;
- lignes 19-22 : une seconde interface, avec une seule méthode ;
- ligne 25 : la classe [Addition] implémente les deux interfaces : les noms des interfaces sont séparés par des virgules. Elle doit définir les méthodes de l'une et de l'autre ;
- ligne 30 : la constante héritée de l'interface est atteinte par [self::PRECISION]. La fonction [round] arrondit un nombre au nombre de décimales indiqué ;
- ligne 46 : on peut aussi désigner la constante par le nom de l'interface : [ICalcul::PRECISION].
Ces interfaces sont utilisées par le script [interface-04.php] :
Résultats :
Commentaires
- ligne 14 : le 3e paramètre de la fonction [executer] est de type [ICalcul] et a une valeur par défaut, un objet [Addition] créé avec [new]. Jusqu'à PHP 8.0, la valeur par défaut d'un paramètre ne pouvait être qu'une constante. Depuis PHP 8.1, l'expression [new] est acceptée dans les valeurs par défaut des paramètres (« new in initializers »), ainsi que dans les arguments des attributs PHP ([#[...]]), les variables statiques et les constantes globales. Elle reste interdite dans la valeur par défaut d'un attribut de classe. L'objet est créé à chaque appel qui omet le paramètre ;
- ligne 20 : le type [ICalcul&INommable] est un type intersection (PHP 8.1) : le paramètre effectif doit implémenter à la fois [ICalcul] et [INommable]. La fonction peut alors appeler les méthodes des deux interfaces. Il ne faut pas confondre avec le type union [ICalcul|INommable] (l'une ou l'autre) ;
- ligne 27 : la constante est accessible de l'extérieur par le nom de l'interface (ligne 2 des résultats) ;
- ligne 29 : le 3e paramètre est omis : c'est un objet [Addition] qui fait le calcul (ligne 3) ;
- ligne 31 : on passe explicitement un objet [Division] (ligne 4). On remarquera que les entiers 10 et 3 sont acceptés pour des paramètres de type [float] malgré la déclaration [strict_types=1] : la conversion d'un [int] en [float] est la seule conversion automatique autorisée en mode strict ;
- lignes 33-34 : les deux classes implémentent les deux interfaces : elles sont acceptées par la fonction [afficher] (lignes 5-6) ;
- ligne 36 : la notation [Division::class] rend le nom complet de la classe, ici [Exemples\Division]. La fonction [class_implements] accepte un nom de classe aussi bien qu'un objet.
Les interfaces sont omniprésentes dans PHP lui-même. Nous en avons déjà rencontré deux au chapitre précédent : [Stringable], implémentée automatiquement par toute classe ayant une méthode [__toString], et [JsonSerializable], qui permet de contrôler l'encodage jSON d'un objet. Nous rencontrerons au chapitre suivant l'interface [Throwable], implémentée par toutes les erreurs et exceptions. Une énumération peut également implémenter une interface. Enfin, nous verrons dans la suite du cours que les interfaces sont la base de l'architecture en couches de nos applications : chaque couche ne connaît de la couche voisine que son interface.