{"id":6381,"date":"2022-04-01T16:15:42","date_gmt":"2022-04-01T14:15:42","guid":{"rendered":"https:\/\/www.luklagroup.com\/sem-categoria\/le-design-pattern-strategy-et-le-compiler-pass-de-symfony\/"},"modified":"2022-04-01T16:15:42","modified_gmt":"2022-04-01T14:15:42","slug":"le-design-pattern-strategy-et-le-compiler-pass-de-symfony","status":"publish","type":"post","link":"https:\/\/www.luklagroup.com\/pt-pt\/apps-data-pt-pt\/le-design-pattern-strategy-et-le-compiler-pass-de-symfony\/","title":{"rendered":"Comment optimiser un code existant gr\u00e2ce au Design Pattern Strategy et Compiler Pass de Symfony ?"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><strong><em>Lors du d\u00e9veloppement d&#8217;un projet, on voit souvent une collection de if les uns \u00e0 la suite des autres, ou encore un \u00e9norme switch \/ case permettant d\u2019ex\u00e9cuter du code en fonction d&#8217;un \u00e9tat. Ce code est bien souvent illisible et difficilement maintenable.<\/em><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong><em>Mais savez-vous qu&#8217;il existe un patron de conception permettant d&#8217;\u00e9viter \u00e7a ? Et \u00e7a tombe bien, Symfony nous permet de l&#8217;int\u00e9grer facilement.<\/em><\/strong><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Pourquoi nous utilisons un Design Pattern Strategy ?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Lors du d\u00e9veloppement d&#8217;une application dans le domaine de l&#8217;immobilier, nous avions \u00e0 impl\u00e9menter une fonctionnalit\u00e9 permettant d&#8217;<strong>obtenir et d&#8217;afficher les consommations de son logement<\/strong>. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Au d\u00e9part, il n&#8217;\u00e9tait possible que de saisir manuellement le relev\u00e9 de son compteur (\u00e9lectrique, gaz ou autre). Les consommations \u00e9taient ensuite calcul\u00e9es selon la saisie de ces relev\u00e9s. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nous avons voulu ensuite exploiter les capacit\u00e9s d&#8217;interop\u00e9rabilit\u00e9 des fournisseurs d&#8217;\u00e9nergie, en <strong>r\u00e9cup\u00e9rant automatiquement les consommations depuis les compteurs Linky et Gazpar<\/strong>. Mais comment le faire en minimisant l&#8217;impact sur le code d\u00e9j\u00e0 fonctionnel ?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La solution : <strong>Le Design Pattern Strategy<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">D\u00e9finition du Design Pattern Strategy<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Le Design Pattern Strategy est un patron de conception permettant la s\u00e9lection et l&#8217;ex\u00e9cution d&#8217;algorithmes selon certaines conditions, le tout en respectant l&#8217;un des principes <strong>SOLID<\/strong>, le Single Responsability (la responsabilit\u00e9 unique).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nous aurons d&#8217;abord un service qui sera charg\u00e9 d&#8217;ex\u00e9cuter ces diff\u00e9rents algorithmes. Nous nommerons ce service <em>ExternalConsommationProvider<\/em>. Chaque algorithme sera repr\u00e9sent\u00e9 par un service que nous appellerons provider. Un provider aura pour objectif de nous fournir les consommations d&#8217;une certaine cat\u00e9gorie.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dans notre exemple, nous aurons <strong>2 providers<\/strong> :<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><em><code>EnedisDataConnectProvider<\/code>&nbsp;: Qui nous retournera les consommations de notre compteur Linky.<\/em><\/li><li><em><code>GrdfGazparProvider<\/code>&nbsp;: Qui nous retournera les consommations de notre compteur Gazpar.<\/em><\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Voici un sch\u00e9ma pour mieux comprendre :<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img alt=\"\" decoding=\"async\" src=\"https:\/\/digiwin.fr\/wp-content\/uploads\/2022\/04\/MicrosoftTeams-image-14.png\" alt=\"\" class=\"wp-image-4073\"\/><\/figure><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Sur ce diagramme, nous retrouvons notre service&nbsp;<code>ExternalConsommationProvider<\/code>&nbsp;(Le bloc jaune tout en haut). Celui-ci contiendra une liste de providers (la propri\u00e9t\u00e9 visible via le petit carr\u00e9 rouge). <strong>Chaque provider sera une instance<\/strong> de&nbsp;<code>ConsommationProviderInterface<\/code>&nbsp;(le bloc jaune au milieu). <strong>Ce qui obligera chaque provider \u00e0 d\u00e9velopper 2 m\u00e9thodes<\/strong> (<code>support()<\/code>&nbsp;et&nbsp;<code>findByRange()<\/code>). <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Enfin, nous retrouvons nos 2 providers&nbsp;<code>EnedisDataConnectProvider<\/code>&nbsp;et&nbsp;<code>GrdfGazparProvider<\/code>&nbsp;(chacun impl\u00e9mentant l&#8217;interface en question, symbolis\u00e9e par la fl\u00e8che en pointill\u00e9).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;objectif de notre service&nbsp;<code>ExternalConsommationProvider<\/code>&nbsp;est, via sa m\u00e9thode&nbsp;<code>findByRange<\/code>, de <strong>nous retourner les consommations<\/strong>. Sans m\u00eame avoir d\u00e9velopper nos 2 providers, nous pouvons d\u00e9j\u00e0 d\u00e9velopper cette m\u00e9thode :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public function findByRange(Logement $logement, DateTime $start, DateTime $end, User $user): array\n{\n    $consommations = &#91;];\n\n    foreach ($this-&gt;getProviders() as $provider) {\n        if ($provider-&gt;support($logement, $user)) {\n            $consommations = array_merge(\n                $consommations,\n                $provider-&gt;findByRange($logement, $start, $end, $user)\n            );\n        }\n    }\n\n    return $consommations;\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Voyons ce code : on boucle sur les providers de notre service. Si chaque provider est support\u00e9 (pour r\u00e9sumer, si l&#8217;utilisateur souhaite obtenir les consommations depuis ce provider), on ajoute dans notre tableau&nbsp;<code>$consommations<\/code>&nbsp;les consommations r\u00e9cup\u00e9r\u00e9es gr\u00e2ce au provider.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En ce qui concerne nos providers, il suffit de d\u00e9velopper les m\u00e9thodes de notre interface :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class EnedisDataConnectProvider implements ConsommationProviderInterface\n{\n    public function support(Logement $logement, User $user): bool\n    {\n        \/\/ On v\u00e9rifie si notre logement est bien connect\u00e9 au compteur Linky\n    }\n\n    public function findByRange(Logement $logement, DateTime $start, DateTime $end, User $user): array\n    {\n        \/\/ On retourne les consommations de notre compteur Linky gr\u00e2ce \u00e0 l'API Data Connect d'Enedis\n    }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Maintenant que nous avons impl\u00e9ment\u00e9 notre service&nbsp;<code>ExternalConsommationProvider<\/code>&nbsp;ainsi que nos 2 providers, <strong>comment int\u00e9grer ces providers dans notre service ?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">C&#8217;est l\u00e0 qu&#8217;intervient le <strong>Compiler Pass de Symfony<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">D\u00e9finition du Compiler Pass<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Pour ajouter un provider \u00e0 notre service, nous disposons de la <strong>m\u00e9thode&nbsp;<code>addProvider()<\/code><\/strong>, qui attends en param\u00e8tre une instance de&nbsp;<code>ConsommationProviderInterface<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Avec Symfony, nous avons la possibilit\u00e9 de tagger nos services, afin de les identifier plus facilement dans notre application.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nous allons donc <strong>ajouter un tag<\/strong> sur tous nos services impl\u00e9mentant l&#8217;interface&nbsp;<code>ConsommationProviderInterface<\/code>. Pour cela, il suffit d&#8217;ajouter les lignes suivantes dans notre fichier&nbsp;<code>config\/services.yaml<\/code>&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>_instanceof:\n  App\\Service\\ConsommationProviderInterface:\n    tags: &#91; app.rt2012.external_consommation_provider ] \n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ainsi, nos 2 providers auront le tags&nbsp;<code>app.rt2012.external_consommation_provider<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Maintenant que nous pouvons <strong>identifier nos providers<\/strong>, nous pouvons cr\u00e9er un CompilerPass. Pour cela, Cr\u00e9ons la class&nbsp;<code>ExternalConsommationProviderPass<\/code>&nbsp;comme ceci :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class ExternalConsommationProviderPass implements CompilerPassInterface\n{\n    public function process(ContainerBuilder $container)\n    {\n        if (!$container-&gt;has(ExternalConsommationProvider::class)) {\n            return;\n        }\n\n        $definition = $container-&gt;findDefinition(ExternalConsommationProvider::class);\n\n        $taggedServices = $container-&gt;findTaggedServiceIds('app.rt2012.external_consommation_provider');\n\n        foreach ($taggedServices as $id =&gt; $tags) {\n            $definition-&gt;addMethodCall('addProvider', &#91;new Reference($id)]);\n        }\n    }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Dans notre m\u00e9thode&nbsp;<code>process()<\/code>, nous v\u00e9rifions si le service&nbsp;<code>ExternalConsommationProvider<\/code>&nbsp;existe. Ensuite, nous r\u00e9cup\u00e9rons tous les services tagu\u00e9s sous le nom&nbsp;<code>app.rt2012.external_consommation_provider<\/code>, soit nos 2 providers. Enfin, nous bouclons sur ces services pour les ajouter dans notre service&nbsp;<code>ExternalConsommationProvider<\/code>&nbsp;gr\u00e2ce \u00e0 la m\u00e9thode&nbsp;<code>addProvider()<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pour terminer, il faut que notre application prenne en compte ce CompilerPass. Pour cela, il suffit de l&#8217;ajouter dans le container, via la m\u00e9thode&nbsp;<code>build<\/code>&nbsp;de la class&nbsp;<code>App\\Kernel<\/code>&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class Kernel extends BaseKernel\n{\n    public function build(ContainerBuilder $container)\n    {\n        $container-&gt;addCompilerPass(new ExternalConsommationProviderPass());\n    }\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">L&#8217;int\u00e9r\u00eat d&#8217;un Design Pattern Strategy<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ce n&#8217;est pas la seule et unique fois o\u00f9 nous avons utilis\u00e9 cette technique. Nous avons pu en exploiter ses avantages dans le contexte de simples <strong>importations de fichiers<\/strong>. Nous avons d\u00e9velopp\u00e9 un service nomm\u00e9\u00a0<code>FileImporter<\/code>\u00a0permettant d&#8217;importer deux types de fichier diff\u00e9rents, comme un fichier\u00a0<code>.xml<\/code>\u00a0et un fichier\u00a0<code>.yaml<\/code>. Ce qui nous donne deux services : <code>XmlFileImporter<\/code>\u00a0et\u00a0<code>YamlFileImporter<\/code>. Chacun contient une m\u00e9thode\u00a0<code>support<\/code>\u00a0permettant de v\u00e9rifier si le fichier est conforme et une m\u00e9thode\u00a0<code>execute<\/code>\u00a0pour importer le fichier. Notre service principal saura d\u00e9terminer lequel choisir en fonction du fichier \u00e0 importer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>L&#8217;int\u00e9r\u00eat principal du Design Pattern Strategy est de nous permettre d\u2019ex\u00e9cuter diff\u00e9rents algorithme selon un \u00e9tat. <\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Imaginons que, demain, nous avions aussi \u00e0 afficher les consommations d&#8217;un \u00e9quipement d&#8217;autoconsommation solaire, nous n&#8217;aurons qu&#8217;\u00e0 cr\u00e9er un provider impl\u00e9mentant l&#8217;interface\u00a0<code>ConsommationProviderInterface<\/code>. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">De m\u00eame, si nous devons traiter l&#8217;import d&#8217;un autre type de fichier (au format\u00a0<code>.json<\/code> par exemple), nous n&#8217;aurons qu&#8217;a cr\u00e9er un autre service (par exemple\u00a0<code>JsonFileImporter<\/code>). <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nous serons donc pr\u00eats pour de futures am\u00e9liorations \ud83d\ude09<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Lors du d\u00e9veloppement d&#8217;un projet, on voit souvent une collection de if les uns \u00e0 la suite des autres, ou encore un \u00e9norme switch \/ case permettant d\u2019ex\u00e9cuter du code en fonction d&#8217;un \u00e9tat. Ce code est bien souvent illisible et difficilement maintenable. Mais savez-vous qu&#8217;il existe un patron de conception permettant d&#8217;\u00e9viter \u00e7a ? [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":6382,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_seopress_robots_primary_cat":"","_seopress_titles_title":"","_seopress_titles_desc":"","_seopress_robots_index":"","footnotes":""},"categories":[183,184],"tags":[220,221,222,223],"class_list":["post-6381","post","type-post","status-publish","format-standard","has-post-thumbnail","category-apps-data-pt-pt","category-change-adopt-pt-pt","tag-compiler-pt-pt","tag-pattern-pt-pt","tag-provider-pt-pt","tag-symfony-pt-pt"],"_links":{"self":[{"href":"https:\/\/www.luklagroup.com\/pt-pt\/wp-json\/wp\/v2\/posts\/6381","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.luklagroup.com\/pt-pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.luklagroup.com\/pt-pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.luklagroup.com\/pt-pt\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.luklagroup.com\/pt-pt\/wp-json\/wp\/v2\/comments?post=6381"}],"version-history":[{"count":0,"href":"https:\/\/www.luklagroup.com\/pt-pt\/wp-json\/wp\/v2\/posts\/6381\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.luklagroup.com\/pt-pt\/wp-json\/wp\/v2\/media\/6382"}],"wp:attachment":[{"href":"https:\/\/www.luklagroup.com\/pt-pt\/wp-json\/wp\/v2\/media?parent=6381"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.luklagroup.com\/pt-pt\/wp-json\/wp\/v2\/categories?post=6381"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.luklagroup.com\/pt-pt\/wp-json\/wp\/v2\/tags?post=6381"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}