
L'écriture de vos packages pour MODX n'est pas facile pour un débutant, et un développeur expérimenté passe parfois un bon moment. Mais le débutant a peur et l'expérimenté comprend :).
Cet article explique comment vous pouvez écrire et créer un package de composants pour MODX sans installer et configurer MODX lui-même. Le niveau est supérieur à la moyenne, vous devrez donc peut-être vous creuser la tête dans certains cas, mais cela en vaut la peine.
Je demande des détails sous chat.
Une fois, lorsque MODX Revolution est apparu, c'était dans la première version bêta, les développeurs ne savaient pas encore comment travailler avec lui et comment écrire des plugins pour cela. Eh bien, sauf pour l'équipe qui s'est penchée sur le CMS. Et l'équipe, je dois dire, a partiellement réussi et a fourni au système la possibilité de collecter facilement les packages qui peuvent ensuite être installés via le référentiel, ce qui semble logique. Mais depuis lors, de nombreuses années se sont écoulées et les exigences relatives aux emballages et à leur assemblage ont quelque peu changé.
Le copier-coller est mauvais, mais pas toujours
Au cours des derniers mois, j'ai été hanté par la pensée de savoir pourquoi, pour construire un package pour MODX, vous devez l'installer, créer une base de données, créer un administrateur, etc. Tant d'actions supplémentaires. Non, il n'y a rien de mal à cela si vous le configurez une fois puis l'utilisez. Beaucoup le font. Mais qu'en est-il lorsque vous voulez confier l'assemblage au scénario et aller prendre un café vous-même?
Il se trouve que les créateurs de MODX étaient habitués à travailler avec MODX lui-même et ont ajouté des classes au package directement dans le noyau. Ils ont également écrit les premiers composants, les premiers scripts de construction, qui ont ensuite été utilisés comme exemples par d'autres développeurs qui ont simplement copié la solution, ne plongeant pas toujours dans l'essence de ce qui se passait. Et je l'ai fait.
Mais la tâche est d'automatiser l'assemblage du package, de préférence sur le serveur, toujours avec un ensemble minimum de logiciels requis, avec un minimum de ressources et donc avec une plus grande vitesse. La tâche a été fixée et après avoir étudié la source, Jason freinant dans le chat a trouvé une solution.
Et lequel?
La première chose que j'ai découverte est que le code responsable de la construction du paquet se trouve directement dans la bibliothèque xPDO, et dans MODX il n'y a que des classes wrapper qui fournissent une API plus pratique et sont un peu plus faciles à travailler, mais seulement si MODX est installé. Par conséquent, seul xPDO peut probablement être utilisé d'une manière ou d'une autre, mais dans le code, le constructeur de l'objet xPDO vous oblige à spécifier des données pour la connexion à la base de données.
public function __construct( $dsn, $username = '', $password = '', $options = [], $driverOptions= null );
Après avoir interrogé Jason, il est devenu clair que bien que des paramètres doivent être définis, la véritable connexion physique à la base de données se produit exactement au moment où elle est nécessaire. Charge paresseuse dans toute sa splendeur. Le deuxième problème a été résolu.
Le troisième problème était celui de la connexion de xPDO au projet. Composer m'est immédiatement venu à l'esprit, mais la version 2.x sur laquelle le MODX actuel s'exécute ne prend pas en charge Composer, et la branche 3.x utilise des espaces de noms et les noms de classe sont écrits différemment que dans 2.x, ce qui entraîne des conflits et des erreurs. En général, incompatible. Ensuite, j'ai dû utiliser des outils git et connecter xPDO en tant que sous-module.
Comment utiliser les sous-modules
Tout d'abord, lisez leur documentation .
Ensuite, s'il s'agit d'un nouveau projet, vous devez ajouter un sous-module:
$ git submodule add https://github.com/username/reponame
Cette commande clonera et installera un sous-module dans votre projet. Ensuite, vous devrez ajouter le dossier du sous-module à votre référentiel avec la commande git add. Il n'ajoutera pas le dossier entier avec le sous-module, mais ajoutera à git uniquement un lien vers le dernier commit du sous-module.
Pour qu'un autre développeur puisse cloner le projet avec toutes les dépendances, vous devez créer une configuration .gitmodules pour les sous-modules. Dans le projet Slackify, c'est comme ça:
[submodule "_build/xpdo"] path = _build/xpdo url = https://github.com/modxcms/xpdo.git branch = 2.x
Après cela, lors du clonage, spécifiez simplement l'indicateur récursif et git téléchargera tous les référentiels dépendants.
En conséquence, nous avons xPDO, xPDO peut être utilisé sans se connecter à la base de données, si ce n'est pas nécessaire, xPDO peut être connecté au code du composant en tant que dépendance externe (sous-module git). Maintenant l'implémentation du script de construction.
Comprenons
Je décrirai le
script de construction de l' add-on
Slackify récemment publié par moi. Ce composant est gratuit et accessible au public sur GitHub, ce qui facilitera l'auto-apprentissage.
Connecter xPDO
Nous omettons la tâche des constantes avec le nom du package et les autres appels nécessaires et connectons xPDO.
require_once 'xpdo/xpdo/xpdo.class.php'; require_once 'xpdo/xpdo/transport/xpdotransport.class.php'; $xpdo = xPDO::getInstance('db', [ xPDO::OPT_CACHE_PATH => __DIR__ . '/../cache/', xPDO::OPT_HYDRATE_FIELDS => true, xPDO::OPT_HYDRATE_RELATED_OBJECTS => true, xPDO::OPT_HYDRATE_ADHOC_FIELDS => true, xPDO::OPT_CONNECTIONS => [ [ 'dsn' => 'mysql:host=localhost;dbname=xpdotest;charset=utf8', 'username' => 'test', 'password' => 'test', 'options' => [xPDO::OPT_CONN_MUTABLE => true], 'driverOptions' => [], ] ] ]);
J'ai ajouté le sous-module xPDO dans le dossier _build, dont nous n'avons besoin qu'au stade de développement et d'assemblage du package et qui n'entrera pas dans l'archive principale du composant. Nous n'avons pas besoin de la deuxième copie de xPDO sur le site avec MODX en direct.
Dans les paramètres de connexion xPDO, j'ai défini le nom de la base de données dans
dsn
, mais il ne joue aucun rôle. Il est important que le dossier de cache dans xPDO soit accessible en écriture. Voilà, xPDO est initialisé.
Faire un hack délicat avec les classes
Lorsque vous utilisez le MODX installé lors de la création du package, tout est simple, nous prenons et créons un objet de la classe dont nous avons besoin. MODX trouve en fait la classe requise, trouve l'implémentation nécessaire pour cette classe (la classe avec le suffixe _mysql), qui dépend de la base de données et crée ensuite l'objet souhaité (en raison de cette fonctionnalité, vous pouvez obtenir des erreurs lors de la construction du package que la classe * _mysql introuvable, ce n'est pas effrayant). Cependant, nous n'avons ni base ni implémentation. Nous devons en quelque sorte remplacer la classe souhaitée, ce que nous faisons.
class modNamespace extends xPDOObject {} class modSystemSetting extends xPDOObject {}
Nous créons une classe factice (stub), qui est nécessaire pour créer l'objet souhaité. Cela ne devrait pas être fait si xPDO ne vérifiait pas spécifiquement à quelle classe l'objet appartient. Mais il vérifie.
Mais il existe des cas particuliers où vous devez faire un peu plus que simplement définir une classe. Ce sont des cas de dépendances entre classes. Par exemple, nous devons ajouter un plugin à la catégorie. Dans le code, juste
$category->addOne($plugin);
mais dans notre cas, cela ne fonctionnera pas.
Si vous avez déjà regardé
le schéma de la base de données MODX , vous avez probablement vu des éléments comme l'agrégat et le composite. Il est écrit à leur sujet dans la
documentation , mais s'ils sont simples, ils décrivent la relation entre les classes.
Dans notre cas, il peut y avoir plusieurs plugins dans une catégorie, dont l'élément d'agrégation est responsable de la classe
modCategory
. Par conséquent, puisque nous avons une classe sans implémentation concrète, nous devons indiquer cette connexion à la main. Il est plus facile de le faire en
getFKDefinition
méthode
getFKDefinition
:
class modCategory extends xPDOObject { public function getFKDefinition($alias) { $aggregates = [ 'Plugins' => [ 'class' => 'modPlugin', 'local' => 'id', 'foreign' => 'category', 'cardinality' => 'many', 'owner' => 'local', ] ]; return isset($aggregates[$alias]) ? $aggregates[$alias] : []; } }
Dans notre composant, seuls les plugins sont utilisés, nous ajoutons donc des liens uniquement pour eux. Après cela, la méthode addMany de la classe modCategory peut facilement ajouter les plugins nécessaires à la catégorie, puis au package.
Créer un package
$package = new xPDOTransport($xpdo, $signature, $directory);
Comme vous pouvez le voir, tout est très, très simple. Ici, nous devions passer le paramètre
$xpdo
, que nous avons initialisé au tout début. Sinon pour l'instant, il n'y aurait pas de problème 2.
$signature
- le nom du paquet, y compris la version, le
$directory
- l'endroit où le paquet sera soigneusement placé. D'où viennent ces variables, voyez par vous-même dans la source.
Créez un espace de noms et ajoutez-le au package
Nous avons besoin d'un espace de noms pour lui lier des lexiques et des paramètres système. Dans notre cas, juste pour cela, d'autres ne sont pas encore pris en compte.
$namespace = new modNamespace($xpdo); $namespace->fromArray([ 'id' => PKG_NAME_LOWER, 'name' => PKG_NAME_LOWER, 'path' => '{core_path}components/' . PKG_NAME_LOWER . '/', ]); $package->put($namespace, [ xPDOTransport::UNIQUE_KEY => 'name', xPDOTransport::PRESERVE_KEYS => true, xPDOTransport::UPDATE_OBJECT => true, xPDOTransport::RESOLVE_FILES => true, xPDOTransport::RESOLVE_PHP => true, xPDOTransport::NATIVE_KEY => PKG_NAME_LOWER, 'namespace' => PKG_NAME_LOWER, 'package' => 'modx', 'resolve' => null, 'validate' => null ]);
La première partie est claire pour quiconque a déjà écrit du code pour MODX. Le second, avec l'ajout au package, est un peu plus compliqué. La méthode
put
prend 2 paramètres: l'objet lui-même et un tableau de paramètres qui décrivent cet objet et son comportement possible au moment de l'installation du package. Par exemple,
xPDOTransport::UNIQUE_KEY => 'name'
signifie que pour l'espace de noms, le champ de
name
avec le nom de l'espace de noms lui-même comme valeur sera utilisé comme clé unique dans la base de données. Vous pouvez en savoir plus sur les paramètres
dans la documentation xPDO , et mieux en étudiant le code source.
De la même manière, vous pouvez ajouter d'autres objets, tels que les paramètres système.
$package->put($setting, [ xPDOTransport::UNIQUE_KEY => 'key', xPDOTransport::PRESERVE_KEYS => true, xPDOTransport::UPDATE_OBJECT => true, 'class' => 'modSystemSetting', 'resolve' => null, 'validate' => null, 'package' => 'modx', ]);
Créer une catégorie
Avec l'ajout d'une catégorie, j'ai eu le plus grand bâillon quand j'ai tout compris. Les éléments placés dans une catégorie dans le modèle xPDO doivent tous deux appartenir à cette catégorie, c'est-à-dire être imbriqué dedans, et alors seulement la catégorie elle-même doit être imbriquée dans le package. Et en même temps, vous devez prendre en compte les relations entre les classes, que j'ai déjà décrites ci-dessus. Il a fallu beaucoup de temps pour le comprendre, le réaliser et l'appliquer correctement.
$package->put($category, [ xPDOTransport::UNIQUE_KEY => 'category', xPDOTransport::PRESERVE_KEYS => false, xPDOTransport::UPDATE_OBJECT => true, xPDOTransport::ABORT_INSTALL_ON_VEHICLE_FAIL => true, xPDOTransport::RELATED_OBJECTS => true, xPDOTransport::RELATED_OBJECT_ATTRIBUTES => [ 'Plugins' => [ xPDOTransport::UNIQUE_KEY => 'name', xPDOTransport::PRESERVE_KEYS => false, xPDOTransport::UPDATE_OBJECT => false, xPDOTransport::RELATED_OBJECTS => true ], 'PluginEvents' => [ xPDOTransport::UNIQUE_KEY => ['pluginid', 'event'], xPDOTransport::PRESERVE_KEYS => true, xPDOTransport::UPDATE_OBJECT => false, xPDOTransport::RELATED_OBJECTS => true ] ], xPDOTransport::NATIVE_KEY => true, 'package' => 'modx', 'validate' => $validators, 'resolve' => $resolvers ]);
Cela a l'air monstrueux, mais pas si vu. Un paramètre important est
xPDOTransport::RELATED_OBJECTS => true
, qui indique que la catégorie a des éléments imbriqués qui doivent également être empaquetés puis installés.
Étant donné que la plupart des modules contiennent divers éléments (morceaux, extraits, plugins), la catégorie avec les éléments est l'élément le plus important du package de transport. C'est donc ici que sont spécifiés les valideurs et résolveurs, qui sont effectués lors de l'installation du package.
Les validateurs sont effectués avant l'installation, les résolveurs - après.
J'ai presque oublié, avant d'emballer la catégorie, nous devons y ajouter nos éléments. Comme ça:
$plugins = include $sources['data'] . 'transport.plugins.php'; if (is_array($plugins)) { $category->addMany($plugins, 'Plugins'); }
Ajoutez d'autres données au package.
Dans le package, vous devez ajouter un autre fichier avec une licence, un fichier avec un journal des modifications et un fichier avec une description du composant. Si nécessaire, vous pouvez ajouter un autre script spécial via l'attribut
setup-options
, qui affichera la fenêtre avant d'installer le package. C'est alors qu'au lieu de "Installer" le bouton "Options d'installation". Et à partir de la version de MODX 2.4, il est devenu possible de spécifier des dépendances entre les packages en utilisant l'attribut require, et vous pouvez également y spécifier la version de PHP et MODX.
$package->setAttribute('changelog', file_get_contents($sources['docs'] . 'changelog.txt')); $package->setAttribute('license', file_get_contents($sources['docs'] . 'license.txt')); $package->setAttribute('readme', file_get_contents($sources['docs'] . 'readme.txt')); $package->setAttribute('requires', ['php' => '>=5.4']); $package->setAttribute('setup-options', ['source' => $sources['build'] . 'setup.options.php']);
Nous emballons
if ($package->pack()) { $xpdo->log(xPDO::LOG_LEVEL_INFO, "Package built"); }
Voilà, récupérez le package fini dans
_packages
, ou à partir de l'endroit où vous avez configuré l'assembly.
Quel est le résultat?
Le résultat a dépassé mes attentes, puisque cette approche, bien qu'elle impose certaines limites et ajoute à certains endroits quelques inconvénients, mais gagne en termes de possibilités d'application.
Pour construire le package, il suffit d'exécuter 2 commandes:
git clone --recursive git@github.com:Alroniks/modx-slackify.git cd modx-slackify/_build && php build.transport.php
Le premier est le clonage du référentiel et de ses sous-modules. Un paramètre important est
--recursive
, grâce à cela git téléchargera et installera, en plus du code du composant lui-même, toutes les dépendances décrites comme des sous-modules.
La seconde consiste à construire le package directement. Après cela, vous pouvez récupérer le
package-1.0.0-pl.transport.zip
dans le dossier
_packages
et le charger, par exemple, dans le référentiel.
Les perspectives sont larges. Par exemple, vous pouvez configurer un hook dans GitHub, qui, après s'être engagé dans une branche, exécutera un script sur votre serveur qui collectera le package et le placera dans tous les sites que vous avez. Ou téléchargez la nouvelle version dans un référentiel, et à ce moment-là, vous ferez du café pour vous-même, comme je l'ai dit au début. Ou vous pouvez créer et écrire des tests pour le module et exécuter le test et construire via Jenkins ou Travis. Oui, un tas de scénarios que vous pouvez imaginer. Avec cette approche, cela est désormais beaucoup plus facile.
Posez des questions, essayez de répondre.
PS Ne passez pas,
mettez une étoile Slackify sur GitHub , s'il vous plaît.