Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.

Réutilisabilité du code PHP

1.330 Aufrufe

Veröffentlicht am

Conférence donnée au PHP Tour Nantes 2012 : Réutilisabilité du code au sein d'un contexte multi-technos basé sur une application concrète des principes de conception SOLID

Veröffentlicht in: Technologie
  • Als Erste(r) kommentieren

Réutilisabilité du code PHP

  1. 1. Réutilisabilité du codeIT&L@bsCO PMMVersion 1.01, le 29 novembre 2012 Nicolas Le Nardou Architecte / Expert Technique PHP nicolas.lenardou@orange.com Réutilisabilité du code Page 1
  2. 2. Introduction : contexte projet> Centre de services d’un grand groupe de presse> 20-30 sites en PHP - eZ Publish, Symfony 2, WordPress, from scratch, …> Forte audience : environ 10M de pages vues / jour> Périmètres fonctionnels très proches> Pression sur les coûts de développement Réutilisabilité du code Page 2
  3. 3. Introduction : contexte projet> A son écriture, le partage du code entre plusieurs sites est : - Soit déjà acté - Soit déjà en cours de discussion> Le partage avec les autres sites du CDS est toujours de l’ordre du possible Réutilisabilité du code Page 3
  4. 4. Introduction : problématique> Comment se construire un référentiel de code commun à tous nos sites ? - Quel que soit le socle technique - Sans renoncer aux apports de ces différents socles (pas de politique du plus petit dénominateur commun)> Objectifs : - Mutualiser la maintenance du code - Réduire les temps de développement Réutilisabilité du code Page 4
  5. 5. sommaire> 1 – Rappel des principes de conception SOLID> 2 – Etude de cas concrets> 3 – Point sur les tests unitaires Réutilisabilité du code Page 5
  6. 6. Conception SOLIDRappel Réutilisabilité du code
  7. 7. conception SOLID> Single Responsibility> Open / Closed> Liskov substitution> Interface segregation> Dependency injection Réutilisabilité du code Page 7
  8. 8. SOLID vs STUPID> Singleton> Tight coupling> Untestability> Premature Optimization> Indescriptive Naming> Duplication Réutilisabilité du code Page 8
  9. 9. SOLID : single responsibility> Principe de responsabilité unique :> « Une classe ne fait qu’une et une seule chose »> Envie de rajouter une fonctionnalité ? Il est temps de créer une nouvelle classe.> Une classe au fonctionnement clairement défini et borné sera plus facilement réutilisable> Une classe aux multiples responsabilités sera fatalement dupliquée pour être adaptée au nouveau besoin Réutilisabilité du code Page 9
  10. 10. SOLID : open / closed> « Une classe doit être fermée à la modification et ouverte à l’extension »> Une évolution ne devrait pas vous faire casser du code, juste en ajouter !> Une classe doit prévoir de pouvoir être étendue sans être réécrite. Réutilisabilité du code Page 10
  11. 11. SOLID : Liskov substitution> « On doit pouvoir substituer à un objet d’une classe X, tout objet d’une sous classe de X »> Corolaire : Une classe utilisant un objet de classe X ne doit pas avoir connaissance des sous classes de X (sous peine de violer le principe open/closed) Réutilisabilité du code Page 11
  12. 12. SOLID : Interface segregation> « Un objet ne devra pas dépendre d’un autre objet mais de son interface »> Il faut expliciter la dépendance réelle au travers d’une interface Réutilisabilité du code Page 12
  13. 13. SOLID : Dependency Injection> « Un objet ne doit pas instancier un autre objet, il doit le recevoir de l’extérieur »> Inversion de contrôle> Pas d’utilisation du mot clé new dans une classe> Injection par constructeur ou mutateur Réutilisabilité du code Page 13
  14. 14. Cas concret #1 Réutilisabilité du code
  15. 15. cas concret #1> Besoin : Injecter dans nos pages des tags javascript (tracking, pub, …)> Implémentation : Une classe TagServer qui calcule la valeur d’un tag en fonction d’un contexte en entrée (url, contenu, …) et d’un jeu de règles - Moteur de règles - Jeu de règles en configuration> Contrainte : A déployer sur : 1. Un site eZ Publish 2. Un site Symfony 2.x Réutilisabilité du code Page 15
  16. 16. cas concret #1> Les mauvaises solutions : - Faire 2 développements distincts - Dupliquer la classe et la modifier - Nombreux paramètres dans le constructeur - Ou toute autre abomination … Réutilisabilité du code Page 16
  17. 17. cas concret #1 : implémentation eZ Publishclass TagServer{ private $rules; public function __construct() { $this->rules = array(); $ini = eZINI::instance(tagserver.ini); $ini->assign(Tags, Rules, $this->rules); } // ...} Réutilisabilité du code Page 17
  18. 18. cas concret #1 : problèmespublic function __construct(){ $this->rules = array(); $ini = eZINI::instance(tagserver.ini); $ini->assign(Tags, Rules, $this->rules);}> Couplage fort : TagServer dépend de eZINI La classe n’est réutilisable que sur un autre site eZ Publish Réutilisabilité du code Page 18
  19. 19. cas concret #1 : problèmes> Solution : injecter l’objet eZINI dans le constructeur Injection de dépendances (SOLID)> On pourra ainsi substituer à une occurrence d’eZINI, un objet d’une sous classe d’eZINI Réutilisabilité du code Page 19
  20. 20. cas concret #1 : eZINI injectéclass TagServer{ private $rules; public function __construct(eZINI $ini) { $this->rules = array(); $ini->assign(Tags, Rules, $this->rules); } // ...} Réutilisabilité du code Page 20
  21. 21. cas concret #1 : eZINI injecté> La construction du serveur :$ini = eZINI::instance(tagserver.ini);$server = new TagServer($ini); Réutilisabilité du code Page 21
  22. 22. cas concret #1 : eZINI injecté> Couplage désormais faible> Mais problème de sémantique : conceptuellement nous n’avons pas besoin d’un eZINI, nous avons plutôt besoin de la configuration. Il nous faut une interface « Configuration » Séparation d’interfaces (SOLID) Réutilisabilité du code Page 22
  23. 23. cas concret #1 : interface Configurationinterface Configuration{ const SEPARATOR = /; /** * Read configuration if exists. Returns default value * otherwise. * * @param string $variableName fully qualified variable name * @param mixed $defaultValue */ public function read($variableName, $defaultValue);} Réutilisabilité du code Page 23
  24. 24. cas concret #1 : interface Configurationclass TagServer{ private $rules; public function __construct(Configuration $config) { $this->rules = $configuration->read( tagserver/Tags/Rules, array() ); }} Réutilisabilité du code Page 24
  25. 25. cas concret #1 : interface Configuration> La dépendance avec le framework d’eZ Publish est rompue …> … mais notre code ne fonctionne plus pour eZ Publish> Il nous faut une implémentation de Configuration reposant sur eZINI Substitution de Liskov (SOLID) Réutilisabilité du code Page 25
  26. 26. cas concret #1 : eZConfigurationclass eZConfiguration implements Configuration{ public function read($variableName, $defaultValue) { list($file, $group, $variable) = explode(self::SEPARATOR, $variableName); $ini = eZINI::instance($file . .ini); $ini->assign($group, $variable, $defaultValue); return $defaultValue; }} Réutilisabilité du code Page 26
  27. 27. cas concret #1 : eZConfiguration> Appel$configuration = new eZConfiguration();$server = new TagServer($configuration);> Fonctionne à nouveau pour eZ Publish - Sans modification de la classe TagServer Open / Closed (SOLID) Réutilisabilité du code Page 27
  28. 28. cas concret #1 : site Symfony> Etape suivante : réutiliser notre classe TagServer sur un site reposant sur Symfony Réutilisabilité du code Page 28
  29. 29. cas concret #1 : site Symfony> Bien sûr, la classe eZConfiguration ne fonctionnera pas> Il nous faut une classe YamlConfiguration Réutilisabilité du code Page 29
  30. 30. cas concret #1 : site Symfonyclass YamlConfiguration implements Configuration{ public function read($variableName, $defaultValue) { list($file, $group, $variable) = explode(self::SEPARATOR, $variableName); $loader = Yaml::parse($file); if(array_key_exists($loader[$group][$variable])) { return $loader[$group][$variable]; } return $defaultValue; }} Réutilisabilité du code Page 30
  31. 31. cas concret #1 : site Symfony> Construction du serveur :$configuration = new YamlConfiguration();$server = new TagServer($configuration);> Et …. c’est tout ! Réutilisabilité du code Page 31
  32. 32. cas concret #1 : bilan> Coût du déploiement de notre classe TagServer sur un autre framework PHP ≈ Coût de développement d’une classe d’adaptation pour accéder à la configuration> Aucune modification de notre classe TagServer n’a été nécessaire> Les classes de la couche d’adaptation sont elles-mêmes réutilisables constitution d’une boîte à outils très rapidement Réutilisabilité du code Page 32
  33. 33. Cas concret #2 Réutilisabilité du code
  34. 34. cas concret #2> Nous voulons ajouter des logs à notre classe TagServer> Contraintes : - Possibilité de les activer / désactiver - Possibilité de se reposer sur le système de log du socle technique utilisé Réutilisabilité du code Page 34
  35. 35. cas concret #2class TagServer{ private $logger; public function __construct(Logger $logger) { $this->logger = $logger; }} Réutilisabilité du code Page 35
  36. 36. cas concret #2 : empilement de paramètresclass TagServer{ private $rules, $logger; public function __construct(Configuration $configuration, Logger $logger) { $this->logger = $logger; $this->rules = $configuration->read( tagserver/Tags/Rules, array() ); }} Réutilisabilité du code Page 36
  37. 37. cas concret #2 : dépendance faible> Contrairement à la configuration, le logger est une dépendance faible> Un logger n’est pas requis pour le fonctionnement de notre classe Injection par mutateur Réutilisabilité du code Page 37
  38. 38. cas concret #2 : injection par mutateurclass TagServer{ private $logger; public function __construct(Configuration $configuration) { $this->logger = null; /* ... */ } public function setLogger(Logger $logger) { $this->logger = $logger; return $this; }} Réutilisabilité du code Page 38
  39. 39. cas concret #2 : injection par mutateur private function writeLog($message) { if($this->logger !== null) { $this->logger->write($message); } }> Et l’appel :$server = new TagServer(new eZConfiguration());$server->setLogger(new eZLogger()); Réutilisabilité du code Page 39
  40. 40. cas concret #2 : overhead> Les cas présentés sont simples et petits> A dimension d’un projet réel, les overheads de code pour construire les objets peuvent devenir pénibles à gérer.> Par exemple, il a fort à parier que le logger soit nécessaire sur de nombreuses classes. Réutilisabilité du code Page 40
  41. 41. cas concret #2 : conteneur d’injection> Solution : recours à un conteneur d’injection> Pimple (Sensio Labs)> DI Component de Symfony (Sensio Labs)> Objet en charge de l’instanciation des autres objets Réutilisabilité du code Page 41
  42. 42. cas concret #2 : conteneur communabstract class Container extends Pimple{ public function __construct() { $this[tagServer] = function ($container){ $server = new TagServer($container[configuration]); $server->setLogger($container[logger]); return $server; }; }} Réutilisabilité du code Page 42
  43. 43. cas concret #2 : conteneur d’injection Conteneur commun à tous les socles techniques Conteneurs spécifiques Réutilisabilité du code Page 43
  44. 44. cas concret #2 : conteneur spécifique (eZ Publish)class eZContainer extends Container{ public function __construct() { parent::__construct(); $this[configuration] = function ($container){ return new eZConfiguration(); }; $this[logger] = $this->share(function ($container){ return new eZLogger(); }); }} Réutilisabilité du code Page 44
  45. 45. cas concret #2 : conteneur d’injection> Et la construction de notre classe : $container = new eZContainer(); $server = $container[tagServer]; Réutilisabilité du code Page 45
  46. 46. cas concret #2 : conteneur d’injection> Quelques remarques : - Le conteneur peut s’appuyer sur de la configuration (ex: Symfony) - Risque de dépendance au conteneur + global state - Dépendances masquées : quid des outils d’analyse ? Réutilisabilité du code Page 46
  47. 47. Et si on testait ? Réutilisabilité du code
  48. 48. testabilité : souvenez-vousclass TagServer{ private $rules; public function __construct() { $this->rules = array(); $ini = eZINI::instance(tagserver.ini); $ini->assign(Tags, Rules, $this->rules); } // ...} Réutilisabilité du code Page 48
  49. 49. testabilité : problématique> Une instance eZ Publish est nécessaire Problème de performances des tests> Un fichier eZINI est également nécessaire Eparpillement du code de test Maintenabilité affaiblie> Et si eZINI était un service à bouchonner ? (comme la db, un webservice ou le filesystem) Réutilisabilité du code Page 49
  50. 50. testabilité : ArrayConfiguration> Il faut mocker la configuration ArrayConfiguration ! Réutilisabilité du code Page 50
  51. 51. testabilité : ArrayConfigurationclass ArrayConfiguration implements Configuration{ private $values; public function __construct(array $values) { $this->values = $values; } public function read($variableName, $defaultValue) { if(array_key_exists($variableName, $this->values)) { return $this->values[$variableName]; } return $defaultValue; }} Réutilisabilité du code Page 51
  52. 52. testabilité : le test unitaireclass TagServerTest extends PHPUnit_Framework_TestCase{ private $tagServer; public function setUp() { $configuration = new ArrayConfiguration(array( tagserver/Tags/Rules => array(/* ... */) )); $this->tagServer = new TagServer($configuration); }} Réutilisabilité du code Page 52
  53. 53. testabilité : bilan> C’est testable !> C’est performant !> Le test est facile à maintenir !> Possibilité de tester aussi les cas à la marge : - Configuration manquante - Configuration erronée - Configuration non consistante - … Réutilisabilité du code Page 53
  54. 54. merci!Réutilisabilité du code Page 54
  55. 55. si vous avez des questions ? Nicolas Le Nardou Architecte / Expert Technique PHP nicolas.lenardou@orange.comRéutilisabilité du code Page 55

×