The 2009-10-27 at 09:42 by Loïc d'Anterroches filed under Pluf - Framework en PHP5.
Si vous faites du type hinting, c’est qu’à priori vous faites très attention à la structure de vos projets PHP. Si vous avez un code du type :
function maFonction(MaClass $objet) {
// corps de la fonction
$objet->faitTruc();
echo $objet->bidule;
}
vous n’acceptez que des objets de type MaClass en entrée. Supposons que maintenant, une personne veuille utiliser votre fonction avec un objet de la class AutreClass en sachant très bien que son objet fourni les méthodes et propriétés nécessaires à maFonction. Il ne peut pas car AutreClass n’est pas MaClass.
Une approche élégante si vous voulez donner de la flexibilité à vos utilisateurs finaux est de dire : "J’accepte tous les objets qui implémentent la bonne interface.".
function maFonction(IMaClass $objet) {
// corps de la fonction
$objet->faitTruc();
echo $objet->bidule;
}
Maintenant, il suffit que AutreClass implémente l’interface IMaClass pour satisfaire maFonction. Cela donne de la flexibilité tout en forçant un peu de contrôle car PHP au niveau du langage va forcer le respect de ce contrat entre maFonction et le paramètre. Si vous avez une utilisation parcimonieuse des interfaces dans vos logiques métier (histoire d’éviter d’avoir ensuite des class qui implémentent 5 interfaces ou plus), cela vous donne une certaine assurance, surtout si votre code est ensuite utilisé par d’autres entités dans votre société. Vous êtes certain que la personne a été obligée d’implémenter l’interface avant d’utiliser votre fonction.
Une autre approche est de demander de manière implicite l’interface sans jamais le déclarer réellement dans le code. C’est à dire que la fonction va accepter n’importe quoi :
function maFonction($objet) {
// corps de la fonction
$objet->faitTruc();
echo $objet->bidule;
}
C’est alors au moment de l’exécution dans la fonction que PHP va retourner une erreur si l’interface implicite n’est pas respectée. Ceci peut être très mauvais dans certains cas. Si on suppose que maFonction fait une série d’opérations sur des fichiers et que l’objet fournit les chemins nécessaires :
function maFonction($objet) {
grosse_copie($objet->source(), $objet->destination());
nettoyage($objet->sourceExcludeBackup());
echo $objet->bidule;
}
On va supposer que grosse_copie fait une grosse copie de fichiers de la source vers la destination et que nettoyage va ensuite nettoyer la source mais garder le backup. Dans ce cas là, si l’objet ne fournit pas la méthode sourceExcludeBackup, PHP va s’arrêter après la grosse copie et laisser votre système dans un mauvais état. En ayant utiliser le type hinting, PHP n’aurait même pas commencé l’exécution de maFonction, le système aurait été protégé.
Le type hinting et les interfaces ont un impact au niveau des performances et complexifie le code, donc ces outils doivent être utilisés avec soin, particulièrement dans le cœur d’exécution de votre programme mais ces outils apportent une sécurité indéniable dans certains cas particuliers, surtout dans la logique métier, là où le non respect d’une interface explicite ou implicite peut faire des dégâts.
Un endroit où je n’utiliserais pas le type hinting et les interfaces est par exemple la boucle de dispatch dans une application web. De toute façon si vos objets de requête et de réponses n’implémentent pas les bonnes méthodes, vous allez vite vous en rendre compte et les dégâts seront une requête plantée. L’erreur ne prendra pas longtemps pour être trouvée et corrigée, et dans tous les autres cas vous payerez le prix au niveau des performances, ce qui serait dommage.
Bien utilisée, la combinaison type hinting et interfaces peut donc se montrer être un outil très puissant de contrôle de la qualité.
Comments from readers
Fr. said:
N'ayant jamais eu à développer de choses atteignant ce "niveau de complexité" en PHP, je n'ai jamais vraiment réalisé de tels contrôles. Je me demandais donc :
Quid des assertions dans ce cas ? Est-ce que c'est plus/moins efficace/adapté ?
Loïc said:
Pour votre remarque sur le "niveau de complexité", je pense que dans la majorité des cas cela correspond plus au choix de la liberté donnée aux utilisateurs du code en aval.
Pour les assertions, c'est un outils supplémentaire qui permet de faire un test. L'intérêt des assertions PHP est qu'on peut les désactiver et donc théoriquement avoir du code avec les tests de qualité supprimés en production sans changer le code et donc minimiser les pertes de performance. Il faudrait que je creuse cela un peu plus et que je fasse quelques benchmarks.
Je n'utilise personnellement pas les assertions PHP (mais beaucoup en C/C++) donc il faudrait que j'explore plus pour pouvoir donner un meilleur avis.
Loïc said:
François, j'ai fait une série de test de performance et cela donne des résultats intéressants. Je vais creuser plus pour voir si quelque chose de valable peut en sortir. Merci pour la piqûre de rappel de l'existence des assertions.
Fr. said:
Merci pour ces précisions et tes tests.
Mon but n'était vraiment pas de faire "une piqûre de rappel", c'était vraiment par curiosité et pour comprendre les différences fondamentales entre les deux méthodes, savoir si l'une est plus "juste" que l'autre et surtout pourquoi :)
Merci en tout cas de prendre le temps pour creuser le sujet. Je lirai les conclusions avec intérêt.
Renaud said:
Sinon, il suffit que AutreClass étende MaClass pour que ca fonctionne... étant donné que MaClass et AutreClass sont censé apporté le même fonctionnement... ce n'est pas déconnant... même si l'utilisation de l'interface reste le plus sexy, on est pas non plus bloqué si on tombe sur un cas comme ca...
Loïc said:
Renaud, oui on peut, mais par moments on ne veut pas "traîner" tout le contenu de la classe "parente" juste pour 2 ou 3 méthodes. D'où le "duck typing" de Python ou une interface qui n'apporte pas de fonctionnalités mais juste un contrat.
Si on peut se permettre d'étendre alors oui, le problème ne se pose pas.