Skip to content

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

1
2
3
4
5
6
7
8
php8-oct-2026/scripts-console/interfaces
├── ICalcul.php
├── IExemple.php
├── IExemple2.php
├── interface-01.php
├── interface-02.php
├── interface-03.php
└── interface-04.php

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

<?php

// respect strict des types déclarés des paramètres de fonctions
declare(strict_types=1);

// espace de noms
namespace Exemples;

// une interface
interface IExemple
{
    // les méthodes de l'interface
    public function ajouter(int $i, int $j): int;

    public function soustraire(int $i, int $j): int;
}

// implémentation 1 de l'interface IExemple
class Classe1 implements IExemple
{
    public function ajouter(int $a, int $b): int
    {
        return $a + $b + 10;
    }

    public function soustraire(int $a, int $b): int
    {
        return $a - $b + 20;
    }
}

// implémentation 2 de l'interface IExemple
class Classe2 implements IExemple
{
    public function ajouter(int $a, int $b): int
    {
        return $a + $b + 100;
    }

    public function soustraire(int $a, int $b): int
    {
        return $a - $b + 200;
    }
}

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 :

<?php

// respect strict des types déclarés des paramètres de fonctions
declare(strict_types=1);

// inclusion et déclaration des classes et interfaces utilisées par le script
require_once __DIR__ . "/IExemple.php";
use Exemples\Classe1;
use Exemples\Classe2;
use Exemples\IExemple;

// fonction
function calculer(int $a, int $b, IExemple $interface): void
{
    print $interface->ajouter($a, $b) . "\n";
    print $interface->soustraire($a, $b) . "\n";
}

// ------------------- main
// création de 2 objets de type Classe1 et Classe2
$c1 = new Classe1();
$c2 = new Classe2();
// appel de la fonction calculer
calculer(4, 3, $c1);
calculer(14, 13, $c2);
// une interface ne peut pas être instanciée
try {
    new IExemple();
} catch (\Error $e) {
    print "Erreur : " . $e->getMessage() . "\n";
}

Résultats :

1
2
3
4
5
6
> php interface-01.php
17
21
127
201
Erreur : Cannot instantiate interface Exemples\IExemple

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

<?php

// respect strict des types déclarés des paramètres de fonctions
declare(strict_types=1);

// espace de noms
namespace Exemples;

// inclusion de la définition de l'interface IExemple
require_once __DIR__ . "/IExemple.php";

// une interface qui dérive de IExemple
interface IExemple2 extends IExemple
{
    // la méthode ajoutée par l'interface
    public function multiplier(int $i, int $j): int;
}

// implémentation de l'interface IExemple2
class Classe3 extends Classe2 implements IExemple2
{
    // [Override] vérifie que la méthode existe bien dans l'interface
    #[\Override]
    public function multiplier(int $a, int $b): int
    {
        return $a * $b;
    }
}

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 :

<?php

// respect strict des types déclarés des paramètres de fonctions
declare(strict_types=1);

// inclusion et déclaration des classes et interfaces nécessaires au script
require_once __DIR__ . "/IExemple2.php";
use Exemples\IExemple2;
use Exemples\Classe3;

// fonction
function calculer(int $a, int $b, IExemple2 $interface): void
{
    print $interface->ajouter($a, $b) . "\n";
    print $interface->soustraire($a, $b) . "\n";
    print $interface->multiplier($a, $b) . "\n";
}

// ------------------- main
// création d'1 objet de type Classe3 qui implémente IExemple2
$c3 = new Classe3();
// appel de la fonction calculer
calculer(4, 3, $c3);

Résultats :

1
2
3
4
> php interface-02.php
107
201
12

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

<?php

// respect strict des types déclarés des paramètres de fonctions
declare(strict_types=1);

// inclusion et déclaration des classes et interfaces nécessaires au script
require_once __DIR__ . "/IExemple.php";
require_once __DIR__ . "/IExemple2.php";
use Exemples\IExemple;
use Exemples\IExemple2;
use Exemples\Classe1;
use Exemples\Classe3;

// fonction
function calculer(int $a, int $b, IExemple $interface): void
{
    print $interface->ajouter($a, $b) . "\n";
    print $interface->soustraire($a, $b) . "\n";
}

// ------------------- main
// création d'1 objet de type Classe3 qui implémente IExemple2 et donc IExemple
$c3 = new Classe3();
// appel de la fonction calculer
calculer(4, 3, $c3);
// l'opérateur [instanceof] teste si un objet implémente une interface
foreach ([new Classe1(), $c3] as $objet) {
    print $objet::class . " : IExemple=" . var_export($objet instanceof IExemple, true)
        . ", IExemple2=" . var_export($objet instanceof IExemple2, true) . "\n";
}
// les interfaces implémentées par la classe Classe3
print_r(class_implements($c3));

Résultats :

> php interface-03.php
107
201
Exemples\Classe1 : IExemple=true, IExemple2=false
Exemples\Classe3 : IExemple=true, IExemple2=true
Array
(
    [Exemples\IExemple] => Exemples\IExemple
    [Exemples\IExemple2] => Exemples\IExemple2
)

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

<?php

// respect strict des types déclarés des paramètres de fonctions
declare(strict_types=1);

// espace de noms
namespace Exemples;

// une interface peut déclarer des constantes
interface ICalcul
{
    // constante typée (PHP 8.3)
    public const int PRECISION = 2;

    public function calculer(float $a, float $b): float;
}

// une seconde interface
interface INommable
{
    public function getNom(): string;
}

// une classe peut implémenter plusieurs interfaces
final class Addition implements ICalcul, INommable
{
    #[\Override]
    public function calculer(float $a, float $b): float
    {
        return round($a + $b, self::PRECISION);
    }

    #[\Override]
    public function getNom(): string
    {
        return "addition";
    }
}

final class Division implements ICalcul, INommable
{
    #[\Override]
    public function calculer(float $a, float $b): float
    {
        // [ICalcul::PRECISION] et [self::PRECISION] désignent la même constante
        return round($a / $b, ICalcul::PRECISION);
    }

    #[\Override]
    public function getNom(): string
    {
        return "division";
    }
}

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

<?php

// respect strict des types déclarés des paramètres de fonctions
declare(strict_types=1);

// inclusion et déclaration des classes et interfaces nécessaires au script
require_once __DIR__ . "/ICalcul.php";
use Exemples\ICalcul;
use Exemples\INommable;
use Exemples\Addition;
use Exemples\Division;

// le paramètre [$calcul] a une valeur par défaut : un objet créé par [new] (PHP 8.1)
function executer(float $a, float $b, ICalcul $calcul = new Addition()): float
{
    return $calcul->calculer($a, $b);
}

// type intersection (PHP 8.1) : [$operation] doit implémenter les deux interfaces
function afficher(float $a, float $b, ICalcul&INommable $operation): void
{
    print $operation->getNom() . "($a, $b)=" . $operation->calculer($a, $b) . "\n";
}

// ------------------- main
// constante de l'interface
print "précision : " . ICalcul::PRECISION . " décimales\n";
// valeur par défaut du 3e paramètre
print "executer(1.234, 2.345)=" . executer(1.234, 2.345) . "\n";
// paramètre explicite
print "executer(10, 3, new Division())=" . executer(10, 3, new Division()) . "\n";
// type intersection
afficher(10, 4, new Division());
afficher(10, 4, new Addition());
// les classes implémentent les deux interfaces
print_r(class_implements(Division::class));

Résultats :

> php interface-04.php
précision : 2 décimales
executer(1.234, 2.345)=3.58
executer(10, 3, new Division())=3.33
division(10, 4)=2.5
addition(10, 4)=14
Array
(
    [Exemples\ICalcul] => Exemples\ICalcul
    [Exemples\INommable] => Exemples\INommable
)

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.