Si, dans l'une de vos méthodes, on vous passe un objet quelconque, il vous est impossible de savoir si vous pouvez invoquer telle ou telle méthode sur ce dernier pour la simple et bonne raison que vous n'êtes pas totalement sûr que ces méthodes existent. En effet, si vous ne connaissez pas la classe dont l'objet est l'instance, vous ne pouvez pas vérifier l'existence de ces méthodes.
En PHP, il existe un moyen d'imposer une structure à nos classes, c'est-à-dire d'obliger certaines classes à implémenter certaines méthodes. Pour y arriver, nous allons nous servir des interfaces. Une fois toute la théorie posée, nous allons nous servir d'interfaces pré-définies afin de jouer un petit peu avec nos objets.
Présentation et création d'interfaces
Le rôle d'une interface
Techniquement, une interface est une classe entièrement abstraite. Son rôle est de décrire un comportement à notre objet. Les interfaces ne doivent pas être confondues avec l'héritage : l'héritage représente un sous-ensemble (exemple : un magicien est un sous-ensemble d'un personnage). Ainsi, une voiture et un personnage n'ont aucune raison d'hériter d'une même classe. Par contre, une voiture et un personnage peuvent tous les deux se déplacer, donc une interface représentant ce point commun pourra être créée.
Créer une interface
Une interface se déclare avec le mot-cléinterface
, suivi du nom de l'interface, suivi d'une paire d'accolades. C'est entre ces accolades que vous listerez des méthodes. Par exemple, voici une interface pouvant représenter le point commun évoqué ci-dessus :
<?php
interface Movable
{
public function move($dest);
}
Toutes les méthodes présentes dans une interface doivent être publiques.
Une interface ne peut pas lister de méthodes abstraites ou finales.
Une interface ne peut pas avoir le même nom qu'une classe et vice-versa.
Implémenter une interface
Cette interface étant toute seule, elle est un peu inutile. Il va donc falloir implémenter l'interface à notre classe grâce au mot-cléimplements
! La démarche à exécuter est comme quand on faisait hériter une classe d'une autre, à savoir :
<?php
class Personnage implements Movable
{
}
Essayez ce code et... observez le résultat :

Et oui, une erreur fatale est générée car notre classePersonnage
n'a pas implémenté la méthode présente dans l'interfaceMovable
. Pour que ce code ne génère aucune erreur, il faut qu'il y ait au minimum ce code :
<?php
class Personnage implements Movable
{
public function move($dest)
{
}
}
Et là... l'erreur a disparu !
Si vous héritez une classe et que vous implémentez une interface, alors vous devez d'abord spécifier la classe à hériter avec le mot-cléextends
puis les interfaces à implémenter avec le mot-cléimplements
.
Vous pouvez très bien implémenter plus d'une interface par classe, à condition que celles-ci n'aient aucune méthode portant le même nom ! Exemple :
<?php
interface iA
{
public function test1();
}
interface iB
{
public function test2();
}
class A implements iA, iB
{
// Pour ne générer aucune erreur, il va falloir écrire les méthodes de iA et de iB.
public function test1()
{
}
public function test2()
{
}
}
Les constantes d'interfaces
Les constantes d'interfaces fonctionnent exactement comme les constantes de classes. Elles ne peuvent être écrasées par des classes qui implémentent l'interface. Exemple :
<?php
interface iInterface
{
const MA_CONSTANTE = 'Hello !';
}
echo iInterface::MA_CONSTANTE; // Affiche Hello !
class MaClasse implements iInterface
{
}
echo MaClasse::MA_CONSTANTE; // Affiche Hello !
Hériter ses interfaces
Comme pour les classes, vous pouvez hériter vos interfaces grâce à l'opérateurextends
. Vous ne pouvez réécrire ni une méthode ni une constante qui a déjà été listée dans l'interface parente. Exemple :
<?php
interface iA
{
public function test1();
}
interface iB extends iA
{
public function test1 ($param1, $param2); // Erreur fatale : impossible de réécrire cette méthode.
}
interface iC extends iA
{
public function test2();
}
class MaClasse implements iC
{
// Pour ne générer aucune erreur, on doit écrire les méthodes de iC et aussi de iA.
public function test1()
{
}
public function test2()
{
}
}
Contrairement aux classes, les interfaces peuvent hériter de plusieurs interfaces à la fois. Il vous suffit de séparer leur nom par une virgule. Exemple :
<?php
interface iA
{
public function test1();
}
interface iB
{
public function test2();
}
interface iC extends iA, iB
{
public function test3();
}
Dans cet exemple, si on imagine une classe implémentant iC, celle-ci devra implémenter les trois méthodestest1
,test2
ettest3
.
Interfaces prédéfinies
Nous allons maintenant aborder les interfaces prédéfinies. Grâce à certaines, nous allons pouvoir modifier le comportement de nos objets ou réaliser plusieurs choses pratiques. Il y a beaucoup d'interfaces prédéfinies, je ne vous les présenterai pas toutes, seulement quatre d'entre elles. Déjà, avec celles-ci, nous allons pouvoir réaliser de belles choses, et puis vous êtes libres de lire la documentation pour découvrir toutes les interfaces. Nous allons ici créer un « tableau-objet ».
Définition d'un itérateur
Afin de comprendre un peu plus ce que l'on va faire, on va commencer par voir ce qu'est un itérateur. Un itérateur est un objet capable de parcourir un autre objet. Bien entendu, cet objet à parcourir doit pouvoir être parcouru (on dit alors qu'il doit être itératif). Pour ce faire, nous devons imposer un comportement à nos objets afin qu'ils puissent être parcourus. Vous voyez où je veux en venir ? Ce comportement à imposer se fera par le biais d'interfaces ! L'interface la plus basique pour rendre un objet itératif est Iterator
.
L'interfaceIterator
Commençons d'abord par l'interfaceIterator
. Si votre classe implémente cette interface, alors vous pourrez modifier le comportement de votre objet lorsqu'il est parcouru. Cette interface comporte 5 méthodes :
current
: renvoie l'élément courant ;key
: retourne la clé de l'élément courant ;next
: déplace le pointeur sur l'élément suivant ;rewind
: remet le pointeur sur le premier élément ;valid
: vérifie si la position courante est valide.
En écrivant ces méthodes, on pourra renvoyer la valeur qu'on veut, et pas forcément la valeur de l'attribut actuellement lu. Imaginons qu'on ait un attribut qui soit un tableau. On pourrait très bien créer un petit script qui, au lieu de parcourir l'objet, parcourt le tableau ! Je vous laisse essayer. Vous aurez besoin d'un attribut$position
qui stocke la position actuelle.
Correction :
<?php
class MaClasse implements Iterator
{
private $position = 0;
private $tableau = ['Premier élément', 'Deuxième élément', 'Troisième élément', 'Quatrième élément', 'Cinquième élément'];
/**
* Retourne l'élément courant du tableau.
*/
public function current()
{
return $this->tableau[$this->position];
}
/**
* Retourne la clé actuelle (c'est la même que la position dans notre cas).
*/
public function key()
{
return $this->position;
}
/**
* Déplace le curseur vers l'élément suivant.
*/
public function next()
{
$this->position++;
}
/**
* Remet la position du curseur à 0.
*/
public function rewind()
{
$this->position = 0;
}
/**
* Permet de tester si la position actuelle est valide.
*/
public function valid()
{
return isset($this->tableau[$this->position]);
}
}
$objet = new MaClasse;
foreach ($objet as $key => $value)
{
echo $key, ' => ', $value, '<br />';
}
Ce qui affichera :

L'interfaceSeekableIterator
Cette interface hérite de l'interfaceIterator
, on n'aura donc pas besoin d'implémenter les deux à notre classe.
SeekableIterator
ajoute une méthode à la liste des méthodes d'Iterator
: la méthodeseek
. Cette méthode permet de placer le curseur interne à une position précise. Elle demande donc un argument : la position du curseur à laquelle il faut le placer. Je vous déconseille de modifier directement l'attribut$position
afin d'assigner directement la valeur de l'argument à$position
. En effet, qui vous dit que la valeur de l'argument est une position valide ?
Je vous laisse réfléchir quant à l'implémentation de cette méthode. Voici la correction (j'ai repris la dernière classe) :
<?php
class MaClasse implements SeekableIterator
{
private $position = 0;
private $tableau = ['Premier élément', 'Deuxième élément', 'Troisième élément', 'Quatrième élément', 'Cinquième élément'];
/**
* Retourne l'élément courant du tableau.
*/
public function current()
{
return $this->tableau[$this->position];
}
/**
* Retourne la clé actuelle (c'est la même que la position dans notre cas).
*/
public function key()
{
return $this->position;
}
/**
* Déplace le curseur vers l'élément suivant.
*/
public function next()
{
$this->position++;
}
/**
* Remet la position du curseur à 0.
*/
public function rewind()
{
$this->position = 0;
}
/**
* Déplace le curseur interne.
*/
public function seek($position)
{
$anciennePosition = $this->position;
$this->position = $position;
if (!$this->valid())
{
trigger_error('La position spécifiée n\'est pas valide', E_USER_WARNING);
$this->position = $anciennePosition;
}
}
/**
* Permet de tester si la position actuelle est valide.
*/
public function valid()
{
return isset($this->tableau[$this->position]);
}
}
$objet = new MaClasse;
foreach ($objet as $key => $value)
{
echo $key, ' => ', $value, '<br />';
}
$objet->seek(2);
echo '<br />', $objet->current();
Ce qui affichera :

L'interfaceArrayAccess
Nous allons enfin, grâce à cette interface, pouvoir placer des crochets à la suite de notre objet avec la clé à laquelle accéder, comme sur un vrai tableau ! L'interfaceArrayAccess
liste quatre méthodes :
offsetExists
: méthode qui vérifiera l'existence de la clé entre crochets lorsque l'objet est passé à la fonctionisset
ouempty
(cette valeur entre crochet est passée à la méthode en paramètre) ;offsetGet
: méthode appelée lorsqu'on fait un simple$obj['clé']
. La valeur 'clé' est donc passée à la méthodeoffsetGet
;offsetSet
: méthode appelée lorsqu'on assigne une valeur à une entrée. Cette méthode reçoit donc deux arguments, la valeur de la clé et la valeur qu'on veut lui assigner.offsetUnset
: méthode appelée lorsqu'on appelle la fonctionunset
sur l'objet avec une valeur entre crochets. Cette méthode reçoit un argument, la valeur qui est mise entre les crochets.
Maintenant, votre mission est d'implémenter cette interface et de gérer l'attribut$tableau
grâce aux quatre méthodes. C'est parti !
Correction :
<?php
class MaClasse implements SeekableIterator, ArrayAccess
{
private $position = 0;
private $tableau = ['Premier élément', 'Deuxième élément', 'Troisième élément', 'Quatrième élément', 'Cinquième élément'];
/* MÉTHODES DE L'INTERFACE SeekableIterator */
/**
* Retourne l'élément courant du tableau.
*/
public function current()
{
return $this->tableau[$this->position];
}
/**
* Retourne la clé actuelle (c'est la même que la position dans notre cas).
*/
public function key()
{
return $this->position;
}
/**
* Déplace le curseur vers l'élément suivant.
*/
public function next()
{
$this->position++;
}
/**
* Remet la position du curseur à 0.
*/
public function rewind()
{
$this->position = 0;
}
/**
* Déplace le curseur interne.
*/
public function seek($position)
{
$anciennePosition = $this->position;
$this->position = $position;
if (!$this->valid())
{
trigger_error('La position spécifiée n\'est pas valide', E_USER_WARNING);
$this->position = $anciennePosition;
}
}
/**
* Permet de tester si la position actuelle est valide.
*/
public function valid()
{
return isset($this->tableau[$this->position]);
}
/* MÉTHODES DE L'INTERFACE ArrayAccess */
/**
* Vérifie si la clé existe.
*/
public function offsetExists($key)
{
return isset($this->tableau[$key]);
}
/**
* Retourne la valeur de la clé demandée.
* Une notice sera émise si la clé n'existe pas, comme pour les vrais tableaux.
*/
public function offsetGet($key)
{
return $this->tableau[$key];
}
/**
* Assigne une valeur à une entrée.
*/
public function offsetSet($key, $value)
{
$this->tableau[$key] = $value;
}
/**
* Supprime une entrée et émettra une erreur si elle n'existe pas, comme pour les vrais tableaux.
*/
public function offsetUnset($key)
{
unset($this->tableau[$key]);
}
}
$objet = new MaClasse;
echo 'Parcours de l\'objet...<br />';
foreach ($objet as $key => $value)
{
echo $key, ' => ', $value, '<br />';
}
echo '<br />Remise du curseur en troisième position...<br />';
$objet->seek(2);
echo 'Élément courant : ', $objet->current(), '<br />';
echo '<br />Affichage du troisième élément : ', $objet[2], '<br />';
echo 'Modification du troisième élément... ';
$objet[2] = 'Hello world !';
echo 'Nouvelle valeur : ', $objet[2], '<br /><br />';
echo 'Destruction du quatrième élément...<br />';
unset($objet[3]);
if (isset($objet[3]))
{
echo '$objet[3] existe toujours... Bizarre...';
}
else
{
echo 'Tout se passe bien, $objet[3] n\'existe plus !';
}
Ce qui affiche :

Alors, on se rapproche vraiment du comportement d'un tableau, n'est-ce pas ? On peut faire tout ce qu'on veut, comme sur un tableau ! Enfin, il manque juste un petit quelque chose pour que ce soit absolument parfait...
L'interfaceCountable
Et voici la dernière interface que je vous présenterai. Elle contient une méthode : la méthodecount
. Celle-ci doit obligatoirement renvoyer un entier qui sera la valeur renvoyée par la fonctioncount
appelée sur notre objet. Cette méthode n'est pas bien compliquée à implémenter, il suffit juste de retourner le nombre d'entrées de notre tableau.
Correction :
<?php
class MaClasse implements SeekableIterator, ArrayAccess, Countable
{
private $position = 0;
private $tableau = ['Premier élément', 'Deuxième élément', 'Troisième élément', 'Quatrième élément', 'Cinquième élément'];
/* MÉTHODES DE L'INTERFACE SeekableIterator */
/**
* Retourne l'élément courant du tableau.
*/
public function current()
{
return $this->tableau[$this->position];
}
/**
* Retourne la clé actuelle (c'est la même que la position dans notre cas).
*/
public function key()
{
return $this->position;
}
/**
* Déplace le curseur vers l'élément suivant.
*/
public function next()
{
$this->position++;
}
/**
* Remet la position du curseur à 0.
*/
public function rewind()
{
$this->position = 0;
}
/**
* Déplace le curseur interne.
*/
public function seek($position)
{
$anciennePosition = $this->position;
$this->position = $position;
if (!$this->valid())
{
trigger_error('La position spécifiée n\'est pas valide', E_USER_WARNING);
$this->position = $anciennePosition;
}
}
/**
* Permet de tester si la position actuelle est valide.
*/
public function valid()
{
return isset($this->tableau[$this->position]);
}
/* MÉTHODES DE L'INTERFACE ArrayAccess */
/**
* Vérifie si la clé existe.
*/
public function offsetExists($key)
{
return isset($this->tableau[$key]);
}
/**
* Retourne la valeur de la clé demandée.
* Une notice sera émise si la clé n'existe pas, comme pour les vrais tableaux.
*/
public function offsetGet($key)
{
return $this->tableau[$key];
}
/**
* Assigne une valeur à une entrée.
*/
public function offsetSet($key, $value)
{
$this->tableau[$key] = $value;
}
/**
* Supprime une entrée et émettra une erreur si elle n'existe pas, comme pour les vrais tableaux.
*/
public function offsetUnset($key)
{
unset($this->tableau[$key]);
}
/* MÉTHODES DE L'INTERFACE Countable */
/**
* Retourne le nombre d'entrées de notre tableau.
*/
public function count()
{
return count($this->tableau);
}
}
$objet = new MaClasse;
echo 'Parcours de l\'objet...<br />';
foreach ($objet as $key => $value)
{
echo $key, ' => ', $value, '<br />';
}
echo '<br />Remise du curseur en troisième position...<br />';
$objet->seek(2);
echo 'Élément courant : ', $objet->current(), '<br />';
echo '<br />Affichage du troisième élément : ', $objet[2], '<br />';
echo 'Modification du troisième élément... ';
$objet[2] = 'Hello world !';
echo 'Nouvelle valeur : ', $objet[2], '<br /><br />';
echo 'Actuellement, mon tableau comporte ', count($objet), ' entrées<br /><br />';
echo 'Destruction du quatrième élément...<br />';
unset($objet[3]);
if (isset($objet[3]))
{
echo '$objet[3] existe toujours... Bizarre...';
}
else
{
echo 'Tout se passe bien, $objet[3] n\'existe plus !';
}
echo '<br /><br />Maintenant, il n\'en comporte plus que ', count($objet), ' !';
Ce qui affichera :

Bonus : la classeArrayIterator
Je dois vous avouer quelque chose : la classe que nous venons de créer pour pouvoir créer des « objets-tableaux » existe déjà. En effet, PHP possède nativement une classe nomméeArrayIterator
. Comme notre précédente classe, celle-ci implémente les quatre interfaces qu'on a vues.
Mais pourquoi tu nous as fais faire tout ça ?!
Pour vous faire pratiquer, tiens. :)
Sachez que réécrire des classes ou fonctions natives de PHP est un excellent exercice (et c'est valable pour tous les langages de programmation). Je ne vais pas m'attarder sur cette classe, étant donné qu'elle s'utilise exactement comme la nôtre. Elle possède les mêmes méthodes, à une différence près : cette classe implémente un constructeur qui accepte un tableau en guise d'argument. Comme vous l'aurez deviné, c'est ce tableau qui sera « transformé » en objet. Ainsi, si vous faites unecho $monInstanceArrayIterator['cle']
, alors à l'écran s'affichera l'entrée qui a pour clécle
du tableau passé en paramètre.
En résumé
Une interface représente un comportement.
Les interfaces ne sont autre que des classes 100% abstraites.
Une interface s'utilise dans une classe grâce au mot-clé
implements
.Il est possible d'hériter ses interfaces grâce au mot-clé
extends
.Il existe tout un panel d'interfaces pré-définies vous permettant de réaliser tout un tas de fonctionnalités intéressantes, comme la création « d'objets-tableaux ».