Fermer

novembre 18, 2019

Concepts (Partie 1)


À propos de l'auteur

Leonardo Losoviz est un développeur et écrivain indépendant, dont la mission est d'intégrer des paradigmes innovants (PHP sans serveur, composants côté serveur, GraphQL)…
Plus d'informations sur
Leonardo

Rendre notre code CMS aussi agnostique que possible nous permet de porter facilement notre application sur un autre CMS si le besoin s'en faisait sentir. Dans cet article, nous allons apprendre comment fonctionne l'abstraction de code, pourquoi c'est une bonne idée et quels sont les concepts clés pour l'obtenir.

Créer un code agnostique du CMS ou de la structure présente plusieurs avantages. Par exemple, grâce à son nouvel éditeur de contenu, Gutenberg, WordPress permet de coder des composants qui peuvent également être utilisés pour d’autres systèmes de gestion de contenu (CMS) et cadres, tels que pour Drupal et pour Laravel . Cependant, l’accent mis par Gutenberg sur la réutilisation du code est axé sur le code côté client du composant (JavaScript et CSS); en ce qui concerne le code d’arrière-plan du composant (comme la fourniture d’API fournissant des données au composant), il n’ya pas d’évaluation préétablie.

Étant donné que ces CMS et cadres (WordPress, Drupal, Laravel) fonctionnent tous en PHP, rendant leur PHP Du code réutilisable facilitera également l’exécution de nos composants sur toutes ces différentes plates-formes. Autre exemple, si nous décidons de remplacer notre système de gestion de contenu par un autre (comme cela a récemment été le cas, beaucoup de personnes ont décrié WordPress après son introduction de Gutenberg), le code d’application étant agnostique depuis le système de gestion de contenu, cela simplifie les choses: le code de l’application nécessite moins d’efforts pour le porter sur d’autres plates-formes.

À partir du code d’application créé pour un CMS spécifique, le processus de transformation en agnostique du CMS est ce que, dans cet article, nous appellerons “ code d'abstraction ». Plus le code est abstrait, plus il peut être réutilisé pour fonctionner avec n'importe quel système de gestion de contenu.

Rendre l'application complètement agnostique au système de gestion de contenu est très difficile, voire impossible, car tôt ou tard, elle devra dépendre l'opinion de la CMS. Ensuite, au lieu de tenter d’obtenir une réutilisabilité à 100% du code, notre objectif doit simplement être de maximiser la quantité de code agnostique au CMS pour le rendre réutilisable sur différents CMS ou frameworks (pour le contexte de cet article, ces 2 termes seront: utilisé de manière interchangeable). Ensuite, la migration de l'application vers un cadre différent ne sera pas sans peine, mais au moins elle sera aussi simple que possible.

La solution à ce problème réside dans l'architecture de notre application: nous devons garder le cœur de l'application proprement. découplé des spécificités du cadre sous-jacent, en codant sur des interfaces plutôt que sur des implémentations. Cela apportera des avantages supplémentaires à notre base de code: nous pourrons alors concentrer notre attention presque exclusivement sur la logique métier (qui est l'essence même et le but de l'application), ce qui rend le code plus compréhensible et moins confus avec les limitations imposées par le CMS particulier.

Cet article est composé de 2 parties: Dans cette première partie, nous allons conceptualiser et concevoir la solution pour extraire le code d'un site WordPress, et dans la 2e partie, nous allons l'implémenter. L’objectif est de garder le code prêt à être utilisé avec les composants Symfony le cadre Laravel et le October CMS .

Code contre les interfaces, Rely On Composer, bénéficiez d'une injection de dépendance

La conception de notre architecture reposera sur les piliers suivants:

  1. Code contre interfaces et non implémentations
  2. Créez des packages, distribuez-les via Composer.
  3. Dependency Injection to glue toutes les parties ensemble.

Analysons-les une à une.

Code contre interfaces, pas mises en oeuvre

Le codage contre interfaces est la pratique qui consiste à interagir avec un code par contrat. Un contrat, qui est établi via une interface de notre langage de programmation (PHP dans notre cas car nous traitons avec WordPress), établit l’intention de certaines fonctionnalités, en indiquant explicitement quelles fonctions sont disponibles, quelles entrées sont attendues pour chaque fonction, et ce que chaque fonction retournera, sans se préoccuper de la manière dont la fonctionnalité doit être implémentée. Ensuite, notre application peut être découplée proprement d'une implémentation spécifique, sans avoir besoin de savoir comment fonctionnent ses internes, et être capable de passer d'une autre implémentation à tout moment sans avoir à changer radicalement de code. Par exemple, notre application peut stocker des données en interagissant avec une interface appelée DataStoreInterface au lieu de l'une de ses implémentations, telles que des instances de classes DatabaseDataStore ou FilesystemDataStore . [19659005] Dans le contexte de WordPress, cela implique que – d'ici la fin de l'abstraction – aucun code WordPress ne sera référencé directement, et WordPress lui-même sera simplement un fournisseur de services pour toutes les fonctions requises par notre application. En conséquence, nous devons considérer WordPress comme une dépendance de l'application, et non comme l'application elle-même.

Les contrats et leurs implémentations peuvent être ajoutés aux packages distribués via Composer et collés ensemble dans l'application via l'injection de dépendance, qui constituent les éléments. nous analyserons ensuite.

Créez des paquets, distribuez-les par le compositeur

N'oubliez pas ceci: Le compositeur est votre ami! Cet outil, un gestionnaire de paquets pour PHP, permet à n'importe quelle application PHP de récupérer facilement des paquets (code) de n'importe quel référentiel et de les installer en tant que dépendances.

Note : J'ai déjà décrit comment peut utiliser Composer avec WordPress dans un article précédent que j'ai écrit au début de cette année.

Composer est lui-même indépendant du système de gestion de contenu (CMS), de sorte qu'il peut être utilisé pour construire n'importe quelle application PHP. Les packages distribués via Composer, cependant, peuvent être agnostiques vis-à-vis du CMS ou non. Par conséquent, notre application devrait dépendre autant que possible des paquetages agnostiques vis-à-vis des CMS (qui fonctionneront pour tout CMS), et lorsque cela est impossible, du paquet correspondant qui fonctionne pour notre CMS spécifique.

Cette stratégie peut être utilisée pour code contre contrats, comme expliqué précédemment. Les packages de notre application peuvent être divisés en deux types: indépendants de CMS et spécifiques à CMS. Le package indépendant du CMS contiendra tous les contrats et tout le code générique, et l'application interagira exclusivement avec ces packages. Pour chaque package ne contenant pas de contrat CMS, vous devez également créer un package spécifique à CMS contenant la mise en œuvre des contrats pour le CMS requis, qui est défini dans l'application au moyen d'une injection de dépendance (que nous analyserons ci-dessous). 19659005] Par exemple, pour implémenter une API permettant de récupérer des publications, nous créons un paquetage indépendant du système de gestion du contenu appelé «Messages», avec le contrat PostAPIInterface contenant la fonction getPosts ainsi:

 interface PostAPIInterface
{
  fonction publique getPosts ($ args);
} 

Cette fonction peut être résolue pour WordPress via un package appelé «Posts for WordPress», qui résout le contrat via une classe WPPostAPI en implémentant la fonction getPosts pour exécuter simplement la fonction get_posts de WordPress, comme ceci: 19659026] la classe WPPostAPI implémente PostAPIInterface
{
  fonction publique getPosts ($ args) {
    return get_posts ($ args);
  }
}

Si nous devons un jour transférer notre application de WordPress vers un autre CMS, nous devons uniquement implémenter le package spécifique au CMS correspondant pour le nouveau CMS (par exemple, "Posts for October CMS") et mettre à jour les contrats de correspondance de configuration d'injection de dépendance avec

Note : Il est recommandé de créer des packages ne définissant que les contrats et rien d'autre. De cette façon, les développeurs peuvent facilement savoir exactement ce qui doit être implémenté.

Injection de dépendance pour coller toutes les pièces ensemble

L’injection de dépendance est une technique qui permet de déclarer quel objet du package spécifique au CMS (également appelé «service»). fournisseur ") implémente quelle interface du paquetage CMS-agnostic (ou" contrat "), collant ainsi toutes les parties de l'application ensemble de manière faiblement couplée.

Différents CMS ou cadres peuvent déjà être livrés avec leur propre implémentation. d'un composant d'injection de dépendance. Par exemple, alors que WordPress n'en a pas, Symfony et Laravel ont leurs propres solutions: Composant DependencyInjection et Service Container respectivement.

Idéalement, nous devrions laisser notre application libre. choisir une solution d’injection de dépendance spécifique et laisser le soin au CMS de s'en charger. Cependant, l'injection de dépendance doit également être utilisée pour lier des contrats et des services génériques, et pas seulement ceux dépendant du CMS (par exemple, un contrat DataStoreInterface résolu par le fournisseur de services FilesystemDataStore peut être complètement sans rapport avec le CMS sous-jacent). De plus, une application très simple ne nécessitant pas de système de gestion de contenu sous-jacent bénéficiera toujours de l'injection de dépendance. Par conséquent, nous sommes obligés de choisir une solution spécifique pour l'injection de dépendance.

Note : Lors du choix d'un outil ou d'une bibliothèque, donnez la priorité à ceux qui appliquent la recommandation de normes PHP correspondante (dans notre cas, nous sommes intéressés par PSR-11 ), de sorte qu'ils puissent être remplacés sans affecter le code de l'application autant que possible (en pratique, chaque solution aura probablement une initialisation personnalisée, donc une réécriture de l'application

Choix du composant d’injection de dépendance

Pour mon application, j’ai décidé d’utiliser le composant DependencyInjection de Symfony qui peut être configuré via YAML. Les fichiers de configuration XML, qui prennent en charge le câblage automatique résolvent automatiquement la manière dont différents services sont injectés les uns dans les autres, réduisant ainsi considérablement le nombre de configurations nécessaires.

Par exemple, un service C mal exécutant un contrat CacheInterface comme celui-ci:

espace de noms MyPackage  MyProject;
La classe Cache implémente CacheInterface
{
  private $ cacheItemPool;
  private $ hooksAPI;

  fonction publique __construct (
    CacheItemPoolInterface $ cacheItemPool,
    HooksAPIInterface $ hooksAPI
  ) {
    $ this-> cacheItemPool = $ cacheItemPool;
    $ this-> hooksAPI = $ hooksAPI;
  }

  // ...
} 

… peut être défini comme fournisseur de services par défaut via le fichier de configuration suivant: services.yaml :

:
  _defaults:
    lier:
      MyPackage  MyProject  HooksAPIInterface: '@hooks_api'

  hooks_api:
    class:  MyPackage  MyProject  ContractImplementations  HooksAPI

  cache:
    class:  MyPackage  MyProject  Cache
    public: true
    arguments:
      $ cacheItemPool: '@cache_item_pool'

  cache_item_pool:
    class:  Symfony  Component  Cache  Adapter  FilesystemAdapter 

Comme on peut le constater, la classe cache requiert deux paramètres dans son constructeur, qui sont résolus et fournis par le composant d'injection de dépendance basé sur le configuration. Dans ce cas, alors que le paramètre $ cacheItemPool est défini manuellement, le paramètre $ hooksAPI est automatiquement résolu par le biais d'un indice de type (c'est-à-dire qu'il correspond au type du paramètre attendu, avec le service qui le résout). . Le câblage automatique permet ainsi de réduire la quantité de configuration requise pour coller les services et leurs implémentations ensemble.

Rendez vos packages aussi granulaires que possible

Chaque package doit être aussi granulaire que possible, traiter avec un objectif spécifique et ne plus contenir. ou moins de code que nécessaire. Ceci est en soi une bonne pratique pour éviter de créer des paquets gonflés et d'établir une architecture modulaire. Cependant, il est obligatoire lorsque nous ne savons pas sur quel CMS l'application sera exécutée. Cela s'explique par le fait que différents CMS sont basés sur différents modèles et qu'il n'est pas garanti que chaque objectif puisse être atteint par le CMS, ni dans quelles conditions. Garder les paquets petits et objectifs permet alors de remplir progressivement les conditions requises, ou de n'utiliser ce paquet que lorsque les fonctionnalités correspondantes ne peuvent pas être satisfaites par le CMS.

Prenons un exemple: Si nous venons d'un WordPress Dans l’état d’esprit, nous pourrions d’abord supposer que les entités «postent» et «commentent» feront toujours partie du système de gestion de contenu, et nous pourrons les inclure dans un ensemble appelé «noyau CMS». Octobre, le CMS n’est pas livré avec des publications ou des commentaires dans ses fonctionnalités principales, et celles-ci sont implémentées via des plugins. Pour la prochaine itération, nous pouvons décider de créer un package pour fournir à ces deux entités, appelées «Articles et commentaires», ou même «Articles», en supposant que les commentaires dépendent des articles et sont groupés avec eux. Cependant, encore une fois, les plugins du système de gestion d'octobre n'implémentent pas ces deux éléments ensemble: il existe un plugin implémentant des publications et un autre plugin implémentant des commentaires (qui dépend du plugin posts). Enfin, notre seule option est d’implémenter deux packages distincts: “Posts” et “Comments”, et d’affecter une dépendance de l’un à l’autre.

De même, un article dans WordPress contient des méta attributs (des attributs supplémentaires à ceux définis dans le modèle de base de données) et on peut supposer que chaque CMS supportera le même concept. Cependant, nous ne pouvons pas garantir qu'un autre CMS fournira cette fonctionnalité et, même si c'était le cas, sa mise en œuvre pourrait être si différente de celle de WordPress, de sorte que les mêmes opérations ne pourraient pas être appliquées aux méta-attributs.

WordPress et October CMS prennent tous deux en charge les attributs post-méta. Cependant, alors que WordPress stocke chaque méta-valeur de post en tant que ligne dans une table de base de données différente de celle où la post est stockée, October CMS stocke toutes les méta-valeurs de post dans une seule entrée sous forme d'objet JSON sérialisé dans une colonne de la table post. En conséquence, WordPress peut récupérer les publications filtrant les données en fonction de la méta-valeur, contrairement à Octobre CMS. Par conséquent, le paquet «Posts» ne doit pas inclure la fonctionnalité pour post meta, qui doit ensuite être implémentée sur son propre paquet «Post Meta» (satisfiable à la fois par WordPress et October CMS), et ce paquet ne doit pas inclure de fonctionnalité pour interroger la méta. Attributs lors de la récupération de posts, qui doivent ensuite être implémentés dans son propre paquet «Post Meta Query» (satisfiable uniquement par WordPress).

Identifier les éléments à analyser

Nous devons maintenant identifier tous les éléments de code et de concepts. à partir d’une application WordPress qu’il faut extraire pour pouvoir fonctionner avec n’importe quel autre CMS. En fouillant dans une application de la mienne, j'ai identifié les éléments suivants:

  • fonctions d'accès
  • noms de fonction
  • paramètres de fonction
  • états (et autres valeurs constantes)
  • fonctions d'assistance au CMS
  • autorisations utilisateur [19659013] options d'application
  • noms de colonnes de la base de données
  • erreurs
  • hooks
  • acheminement
  • propriétés d'objet
  • modèles d'entité state (19659013) (méta, types de publication, pages de publication et taxonomies – balises et catégories -)
  • traduction
  • média

Cette liste n'est pas complète pour l'instant. Il y a beaucoup d'autres éléments qui nécessitent une abstraction, que je ne couvrirai pas pour l'instant. Ces éléments incluent notamment la gestion de la localisation des ressources (certains frameworks peuvent nécessiter de placer des fichiers image / font / JavaScript / CSS / etc. Dans un répertoire spécifique) et des commandes CLI (WordPress a WP-CLI Symfony a le composant console et Laravel a Artisan et il existe des commandes pour chacune d’elles qui pourraient être unifiées).

Dans la prochaine (et dernière) partie de cette série de articles, nous allons procéder à la mise en oeuvre de l'abstraction pour tous les éléments identifiés ci-dessus.

Évaluer quand il est utile de résumer la demande

Résumer une demande n'est pas difficile, mais, comme on le verra dans l'article suivant, implique beaucoup de travail, nous devons donc examiner attentivement si nous en avons vraiment besoin ou non. Examinons les avantages et les inconvénients de l’abstraction du code de l’application:

Avantages

  • L’effort requis pour porter notre application sur d’autres plates-formes est considérablement réduit.
  • Ce code reflétant notre logique d’affaires et non l’avis du CMS. , il est plus compréhensible.
  • L’application s’organise naturellement en paquets qui améliorent progressivement les fonctionnalités.

Inconvénients

  • Travail en cours supplémentaire.
  • Le code devient plus verbeux.
  • Délai d’exécution plus long à partir de l’ajout couches de code.

Il n’existe aucun moyen magique de déterminer s’il serait préférable de faire abstraction du code de notre application. Toutefois, en règle générale, je propose l’approche suivante:

En ce qui concerne un nouveau projet, il est logique d’établir une architecture agnostique, car les efforts supplémentaires requis sont gérables et les avantages qui en valent la peine; Cependant, en ce qui concerne un projet existant, l’effort ponctuel pour le résumer pourrait être très exigeant, nous devons donc analyser ce qui coûte le plus cher (en termes de temps et d’énergie): l’abstraction ponctuelle ou le maintien de plusieurs bases de code. [19659080] Conclusion

La configuration d’une architecture indépendante du système de gestion de contenu (CMS) pour notre application peut permettre de la porter sur une plate-forme différente avec un minimum d’effort. Les principaux ingrédients de cette architecture sont le codage par rapport aux interfaces, leur distribution dans des packages granulaires, leur implémentation pour un CMS spécifique sur un package séparé, et la liaison de toutes les pièces par injection de dépendance.

Hormis quelques exceptions (telles que la décision choisir la solution de Symfony pour l’injection de dépendance), cette architecture tente de ne pas imposer d’opinion. Le code de l'application peut alors refléter directement la logique métier et non les limitations imposées par le CMS.

Dans la suite de cette série, nous allons implémenter l'abstraction de code pour une application WordPress.

 Smashing Editorial (rb, dm, yk, il)




Source link