Configuration d'un paquet miroir local pour Composer avec Satis

L'installation de toutes vos bibliothèques PHP avec Composer est un excellent moyen de gagner du temps. Toutefois, les projets plus volumineux testés et exécutés automatiquement à chaque validation sur votre système de contrôle de version de logiciel (SVC) prendront beaucoup de temps pour installer tous les packages requis à partir d'Internet. Vous souhaitez exécuter vos tests le plus rapidement possible via votre système d'intégration continue (CI) afin de pouvoir réagir rapidement en cas d'échec. Dans ce didacticiel, nous allons configurer un miroir local pour proxy tous vos paquets requis dans votre projet. composer.json fichier. Cela permettra à notre CI de fonctionner beaucoup plus rapidement, d’installer les paquetages sur le réseau local ou même d’être hébergés sur le même ordinateur et de s’assurer que les versions spécifiques des paquetages sont toujours disponibles..


Qu'est-ce que Satis??

Satis est le nom de l'application que nous allons utiliser pour refléter les différents référentiels de notre projet. Il fait office de proxy entre Internet et votre compositeur. Notre solution créera un miroir local de quelques paquets et demandera à notre compositeur de l'utiliser à la place des sources trouvées sur Internet..

Voici une image qui en dit plus que mille mots.


Notre projet utilisera le compositeur comme d'habitude. Il sera configuré pour utiliser le serveur Satis local en tant que source principale. Si un paquet y est trouvé, il sera installé à partir de là. Sinon, nous laisserons le compositeur utiliser le packagist.org par défaut pour récupérer le paquet.


Se Satis

Satis est disponible par le biais du compositeur, son installation est donc très simple. Dans l’archive du code source ci-jointe, vous trouverez Satis installé dans le répertoire Sources / Satis dossier. Nous allons d'abord installer le compositeur lui-même.

$ curl -sS https://getcomposer.org/installer | php #! / usr / bin / env php Tous les paramètres corrects pour utiliser Composer Downloading… Composer a été installé avec succès sur: / home / csaba / Personnel / Programmation / NetTuts / Configuration d'un miroir local pour les packages Composer avec Satis / Sources / Satis / composer .phar Utilisez-le: composeur php.phar

Ensuite, nous installerons Satis.

$ php composer.phar create-project composer / satis --stability = dev --keep-vcs Installation de composer / satis (maître de dev eddb78d52e8f7ea772436f2320d6625e18d5daf5) - Installation de composer / satis (maître de dev) master de clonage Projet créé dans / home / csaba / Personnel / Programmation / NetTuts / Configuration d'un miroir local pour les paquetages Composer avec Satis / Sources / Satis / satis Chargement de référentiels de compositeur avec les informations sur le paquet Installation de dépendances (y compris require-dev) à partir d'un fichier de verrouillage - Installation de symfony / process (dev-master 27b0fc6) Clonage 27b0fc645a557b2fc7bc7735cfb05505de9351be - Installation de symfony / viseur (v2.4.0-beta1) Téléchargement: 100% - Installation de symfony / console (f44cc6f dev-maître) Clonage f44cc6fabdaa853335d7f54f1b86c99622db518a - Installation de SELd / jsonlint (1.1.1) Téléchargement: 100% - Installation justinrainbow / json-schema (1.1.0) Téléchargement: 100% - Installation du composeur / compositeur (dev-master f8be812) Clonage du f8be812a496886c84918d6dd1b50db5c16da3cc3 - Installation de twig / twig (v1.14.1) 100% symfony / console suggère d'installer symfony / event-dispatcher () Générer des fichiers à chargement automatique

Configurer Satis

Satis est configuré par un fichier JSON très similaire à Composer. Vous pouvez utiliser le nom de votre choix pour votre fichier et le spécifier pour une utilisation ultérieure. Nous utiliserons "miroir-packages.conf".

"name": "Miroir NetTuts Composer", "homepage": "http: // localhost: 4680", "référentiels": ["type": "vcs", "url": "https: // github. com / SynetoNet / monolog ", " type ":" composer "," url ":" https://packagist.org "]," require ": " monolog / monolog ":" syneto-dev ", "moquerie / moquerie": "*", "phpunit / phpunit": "*", "require-dependencies": true

Analysons ce fichier de configuration.

  • prénom - représente une chaîne qui sera affichée sur l'interface Web de notre miroir.
  • page d'accueil - est l'adresse Web où nos paquets seront conservés. Cela n'indique pas à notre serveur Web d'utiliser cette adresse et ce port, il s'agit simplement d'informations sur une configuration opérationnelle. Nous allons configurer l'accès à cette adresse et à ce port ultérieurement.
  • les dépôts - une liste de référentiels classés par préférence. Dans notre exemple, le premier référentiel est un fork Github des bibliothèques de journalisation monolog. Il a quelques modifications et nous souhaitons utiliser ce fork pour l’installation de monolog. Le type de ce référentiel est "vcs". Le deuxième référentiel est de type"compositeur". Son URL est le site par défaut de packagist.org.
  • exiger - liste les paquets que nous voulons mettre en miroir. Il peut représenter un package spécifique avec une version ou une branche spécifique, ou n'importe quelle version à ce sujet. Il utilise la même syntaxe que votre "exiger" ou "require-dev" dans ton composer.json.
  • dépendances - est la dernière option dans notre exemple. Cela dira à Satis de refléter non seulement les paquets que nous avons spécifiés dans le "exiger"section mais aussi toutes leurs dépendances.

Pour essayer rapidement nos paramètres, nous devons d’abord demander à Satis de créer les miroirs. Exécutez cette commande dans le dossier où vous avez installé Satis..

$ php ./satis/bin/satis build ./mirrored-packages.conf ./packages-mirror Analyse des packages Ecriture de packages.json Ecriture de la vue Web

Pendant le processus, vous verrez comment Satis reflète chaque version trouvée des packages requis. Soyez patient, cela peut prendre un certain temps pour construire tous ces paquets.

Satis exige que date.timezone à préciser dans le php.ini fichier, assurez-vous qu’il est bien et qu’il est réglé sur votre fuseau horaire local. Sinon, une erreur apparaîtra.

[Twig_Error_Runtime] Une exception a été émise lors du rendu d'un modèle ("date_default_timezone_get (): il n'est pas prudent de s'appuyer sur les paramètres de fuseau horaire du système. Vous êtes * obligé d'utiliser le paramètre date.timezone ou la date_default_timezone_set).

Nous pouvons ensuite exécuter une instance de serveur PHP dans notre console pointant vers le référentiel créé récemment. PHP 5.4 ou plus récent est requis.

$ php -S localhost: 4680 -t ./packages-mirror/ Serveur de développement PHP 5.4.22-pl0-gentoo démarré à Sun 8 déc 14:47:48 2013 Écoute sur http: // localhost: 4680 La racine du document est / home / csaba / Personnel / Programmation / NetTuts / Configuration d’un miroir local pour les packages Composer avec Satis / Sources / Satis / packages-mirror Appuyez sur Ctrl-C pour quitter. [Dim 8 déc 14:48:09 2013] 127.0.0.1:56999 [200]: / [dim déc 8 14:48:09 2013] 127.0.0.1:57000 [404]: /favicon.ico - Aucun fichier de ce type ni annuaire

Et nous pouvons maintenant parcourir nos packages en miroir et même en rechercher des spécifiques en pointant notre navigateur Web sur http: // localhost: 4680:



Accueillons-le sur Apache

Si vous avez un Apache en marche sous la main, créer un hôte virtuel pour Satis sera assez simple.

Écouter 4680  Options -Indexes FollowSymLinks AllowOverride all Ordre autorisé, refusé Autorisé de tous   DocumentRoot "/ chemin / à / votre / packages-miroir" NomServeur 127.0.0.1:4680 ServerAdmin [email protected] ErrorLog syslog: utilisateur 

Nous venons d'utiliser un .conf fichier comme celui-ci, mis dans Apache conf.d dossier, généralement /etc/apache2/conf.d. Il crée un hôte virtuel sur le port 4680 et le pointe vers notre dossier. Bien sûr, vous pouvez utiliser le port que vous voulez.


Mise à jour de nos miroirs

Satis ne peut pas mettre à jour automatiquement les miroirs à moins de le lui dire. Ainsi, le moyen le plus simple, sur tout système de type UNIX, consiste simplement à ajouter un travail cron à votre système. Ce serait très facile, et juste un script simple pour exécuter notre commande de mise à jour.

#! / bin / bash php / full / path / to / satis / bin / satis build \ /full/path/to/mirrored-packages.conf \ / full / path / to / packages-mirror

L'inconvénient de cette solution est qu'elle est statique. Nous devons mettre à jour manuellement le miroir-packages.conf chaque fois que nous ajoutons un autre paquet à notre projet composer.json. Si vous faites partie d'une équipe dans une entreprise avec un grand projet et un serveur d'intégration continue, vous ne pouvez pas compter sur les personnes qui se souviennent d'ajouter les packages sur le serveur. Ils peuvent même ne pas avoir les autorisations nécessaires pour accéder à l'infrastructure de CI.


Mise à jour dynamique de la configuration Satis

Il est temps de faire un exercice PHP TDD. Si vous voulez juste que votre code soit prêt et actif, consultez le code source joint à ce tutoriel..

require_once __DIR__. '/… /… /… /… /Vendor/autoload.php'; La classe SatisUpdaterTest étend PHPUnit_Framework_TestCase function testBehavior () $ this-> assertTrue (true); 

Comme d'habitude, nous commençons par un test dégénératif, juste assez pour nous assurer que nous disposons d'un cadre de test fonctionnel. Vous remarquerez peut-être que j'ai une étrange ligne require_once, car je veux éviter de réinstaller PHPUnit et Mockery pour chaque petit projet. Donc je les ai dans un vendeur dossier dans mon NetTuts' racine. Vous devriez juste les installer avec composer et lâcher le Demandez une fois ligne tout à fait.

La classe SatisUpdaterTest étend PHPUnit_Framework_TestCase function testDefaultConfigFile () $ attend = '"name": "NetTuts Composer Mirror", "homepage": "http: // localhost: 4680", "repositories": ["type": " vcs "," url ":" https://github.com/SynetoNet/monolog ", " type ":" composer "," url ":" https://packagist.org "]," require " : , "require-dependencies": true '; $ actual = $ this-> parseComposerConf ("); $ this-> assertEquals ($ attendu, $ actual);

Cela semble à peu près correct. Tous les champs sauf "exiger"sont statiques. Nous ne devons générer que les packages. Les référentiels pointent vers nos clones git privés et vers les packages au besoin. Leur gestion est davantage un travail sysadmin que celui d’un développeur de logiciels..

Bien sûr cela échoue avec:

Erreur irrécupérable PHP: Appel de la méthode non définie SatisUpdaterTest :: parseComposerConf ()

Réparer c'est facile.

fonction privée parseComposerConf ($ string) 

Je viens d'ajouter une méthode vide avec le nom requis, privé, à notre classe de test. Cool, mais maintenant nous avons une autre erreur.

PHPUnit_Framework_ExpectationFailedException: Échec de l'affirmation que null correspond à la valeur attendue '…'

Donc, null ne correspond pas à notre chaîne contenant toute cette configuration par défaut.

fonction privée parseComposerConf ($ string) return '"name": "NetTuts Composer Mirror", "homepage": "http: // localhost: 4680", "référentiels": ["type": "vcs", " url ":" https://github.com/SynetoNet/monolog ", " type ":" composer "," url ":" https://packagist.org "]," require ": , "require-dependencies": true '; 

OK ça marche. Tous les tests sont réussis.

PHPUnit 3.7.28 de Sebastian Bergmann. Temps: 15 ms, mémoire: 2,50 Mo OK (1 test, 1 assertion)

Mais nous avons introduit une duplication horrible. Tout ce texte statique à deux endroits, écrit caractère par caractère à deux endroits différents. Réglons le:

La classe SatisUpdaterTest étend PHPUnit_Framework_TestCase static $ DEFAULT_CONFIG = '"name": "Miroir NetTuts Composer", "homepage": "http: // localhost: 4680", "repositories": ["type": "vcs", " url ":" https://github.com/SynetoNet/monolog ", " type ":" composer "," url ":" https://packagist.org "]," require ": , "require-dependencies": true '; fonction testDefaultConfigFile () $ attendue = self :: $ DEFAULT_CONFIG; $ actual = $ this-> parseComposerConf ("); $ this-> assertEquals ($ attendu, $ actual); fonction privée parseComposerConf ($ string) return self :: $ DEFAULT_CONFIG;

Ahhh! C'est mieux.

 fonction testEmptyRequiredPackagesInComposerJsonWillProduceDefaultConfiguration () $ attendue = self :: $ DEFAULT_CONFIG; $ actual = $ this-> parseComposerConf ('"require": '); $ this-> assertEquals ($ prévu, $ réel); 

Bien. Cela passe aussi. Mais il met également en évidence des doubles emplois et des tâches inutiles.

 function testDefaultConfigFile () $ actual = $ this-> parseComposerConf ("); $ this-> assertEquals (self :: $ DEFAULT_CONFIG, $ real); fonction_donne-garde sur le point de parole. "require":  '); $ this-> assertEquals (self :: $ DEFAULT_CONFIG, $ actual);

Nous avons en ligne le $ attendu variable. $ réel pourrait également être en ligne, mais je l’aime mieux de cette façon. Il garde le focus sur ce qui est testé.

Maintenant nous avons un autre problème. Le prochain test que je veux écrire ressemblerait à ceci:

fonction testARequiredPackageInComposerWillBeInSatisAlso () $ actual = $ this-> parseComposerConf ('"require": "Mockery / Mockery": "> = 0.7.2"'); $ this-> assertContains ('"Mockery / Mockery": "> = 0.7.2"', $ actual); 

Mais après avoir écrit la mise en œuvre simple, nous remarquerons que cela nécessite json_decode () et json_encode (). Et bien sûr, ces fonctions reformatent notre chaîne et les chaînes correspondantes seront difficiles au mieux. Nous devons prendre du recul.

fonction testDefaultConfigFile () $ actual = $ this-> parseComposerConf ("); $ this-> assertJsonStringEqualsJsonString ($ this-> jsonRecode (self :: $ DEFAULT_CONFIG), $ actual); this-> parseComposerConf ('"require": '); $ this-> assertJsonStringEqualsJsonString ($ this-> jsonRecode (self :: $ DEFAULT_CONFIG), $ actual); fonction privée parseComposerConf ($ jsonConfig) $ $ this-> jsonRecode (self :: $ DEFAULT_CONFIG); fonction privée jsonRecode ($ json) return json_encode (json_decode ($ json, true));

Nous avons changé notre méthode d’assertion pour comparer les chaînes JSON et nous avons également recodé notre $ réel variable. ParseComposerConf () a également été modifié pour utiliser cette méthode. Vous verrez dans un instant comment cela nous aide. Notre prochain test devient plus spécifique à JSON.

fonction testARequiredPackageInComposerWillBeInSatisAlso () $ actual = $ this-> parseComposerConf ('"require": "Mockery / Mockery": "> = 0.7.2"'); $ this-> assertEquals ('> = 0.7.2', json_decode ($ actual, true) ['require'] ['Mockery / Mockery']); 

Et faire passer ce test, avec le reste des tests, est assez facile, encore une fois.

fonction privée parseComposerConf ($ jsonConfig) $ addedConfig = json_decode ($ jsonConfig, true); $ config = json_decode (self :: $ DEFAULT_CONFIG, true); if (isset ($ addedConfig ['require']))) $ config ['require'] = $ addedConfig ['require'];  return json_encode ($ config); 

Nous prenons la chaîne JSON d'entrée, la décodons et, si elle contient un "exiger"field, nous l’utilisons dans notre fichier de configuration Satis. Mais nous voudrons peut-être mettre en miroir toutes les versions d’un paquet, pas seulement la dernière. Alors peut-être que nous voudrons modifier notre test pour vérifier que la version est" * "dans Satis, indépendamment de quelle version exacte est en composer.json.

fonction testARequiredPackageInComposerWillBeInSatisAlso () $ actual = $ this-> parseComposerConf ('"require": "Mockery / Mockery": "> = 0.7.2"'); $ this-> assertEquals ('*', json_decode ($ actual, true) ['require'] ['Mockery / Mockery']); 

Cela échoue évidemment avec un message cool:

PHPUnit_Framework_ExpectationFailedException: Échec de l'affirmation que deux chaînes sont égales. Attendu: * Réel:> = 0.7.2

Maintenant, nous devons réellement éditer notre JSON avant de le ré-encoder.

fonction privée parseComposerConf ($ jsonConfig) $ addedConfig = json_decode ($ jsonConfig, true); $ config = json_decode (self :: $ DEFAULT_CONFIG, true); $ config = $ this-> addNewRequires ($ addedConfig, $ config); return json_encode ($ config);  fonction privée toAllVersions ($ config) foreach ($ config ['require'] en tant que $ package => $ version) $ config ['require'] [$ package] = '*';  return $ config;  fonction privée addNewRequires ($ addedConfig, $ config) if (isset ($ addedConfig ['require'])) $ config ['require'] = $ addedConfig ['require']; $ config = $ this-> toAllVersions ($ config);  return $ config; 

Pour réussir le test, nous devons parcourir chaque élément du tableau de packages requis et définir leur version sur '*'. Voir méthode toAllVersion () pour plus de détails. Et pour accélérer un peu ce tutoriel, nous avons également extrait des méthodes privées dans la même étape. Par ici, parseComoserConf () devient très descriptif et facile à comprendre. Nous pourrions aussi inline $ config dans les arguments de addNewRequires (), mais pour des raisons esthétiques je l'ai laissé sur deux lignes.

Mais qu'en est-il "require-dev" dans composer.json?

fonction testARquiredDevPackageInComposerWillBeInSatisAlso () $ actual = $ this-> parseComposerConf ('"require-dev": "Mockery / Mockery": "> = 0.7.2", "phpunit / phpunit": "3.7.28" '); $ this-> assertEquals ('*', json_decode ($ actual, true) ['require'] ['Mockery / Mockery']); $ this-> assertEquals ('*', json_decode ($ actual, true) ['require'] ['phpunit / phpunit']); 

Cela échoue évidemment. Nous pouvons le faire passer avec juste copier / coller notre si condition dans addNewRequires ():

fonction privée addNewRequires ($ addedConfig, $ config) if (isset ($ addedConfig ['require'])) $ config ['require'] = $ addedConfig ['require']; $ config = $ this-> toAllVersions ($ config);  if (isset ($ addedConfig ['require-dev']))) $ config ['require'] = $ addedConfig ['require-dev']; $ config = $ this-> toAllVersions ($ config);  return $ config; 

Oui, cela le fait passer, mais ceux dupliqués si les déclarations sont vilaines. Traitons avec eux.

fonction privée addNewRequires ($ addedConfig, $ config) $ config = $ this-> addRequire ($ addedConfig, 'require', $ config); $ config = $ this-> addRequire ($ addedConfig, 'require-dev', $ config); return $ config;  fonction privée addRequire ($ addedConfig, $ chaîne, $ config) if (isset ($ addedConfig [$ chaîne])) $ config ['require'] = $ addedConfig [$ chaîne]; $ config = $ this-> toAllVersions ($ config);  return $ config; 

Nous pouvons être heureux à nouveau, les tests sont verts et nous avons remanié notre code. Je pense qu'il ne reste qu'un test à écrire. Et si nous avons les deux "exiger" et "require-dev"sections dans composer.json?

fonction testItCanParseComposerJsonWithBothSections () $ actual = $ this-> parseComposerConf ('"require": "Mockery / Mockery": "> = 0.7.2", "require-dev": "phpunit / phpunit": " 3.7.28 " '); $ this-> assertEquals ('*', json_decode ($ actual, true) ['require'] ['Mockery / Mockery']); $ this-> assertEquals ('*', json_decode ($ actual, true) ['require'] ['phpunit / phpunit']); 

Cela échoue parce que les paquets définis par "require-dev"écrasera ceux de"exiger"et nous aurons une erreur:

Index non défini: Mockery / Mockery

Ajoutez simplement un signe plus pour fusionner les tableaux, et nous avons terminé.

fonction privée addRequire ($ addedConfig, $ chaine, $ config) if (isset ($ addedConfig [$ chaîne])) $ config ['require'] + = $ addedConfig [$ chaîne]; $ config = $ this-> toAllVersions ($ config);  return $ config; 

Les tests passent. Notre logique est finie. Il ne reste plus qu'à extraire les méthodes dans leurs propres fichiers et classes. La version finale des tests et la SatisUpdater la classe peut être trouvée dans le code source ci-joint.

Nous pouvons maintenant modifier notre script cron pour charger notre analyseur et l’exécuter sur notre composer.json. Ce sera spécifique aux dossiers particuliers de vos projets. Voici un exemple que vous pouvez adapter à votre système.

#! / usr / local / bin / php parseComposerConf (file_get_contents ($ composerJsonFile)); fichier_put_contents ($ satisConf, $ conf); system (sprintf ('/ path / to / satis / bin / satis construire% s% s', $ satisConf, $ outputDir), $ retval); exit ($ retval);

Faire de votre projet utiliser le miroir

Nous avons parlé de beaucoup de choses dans cet article, mais nous n’avons pas précisé comment nous allons demander à notre projet d’utiliser le miroir au lieu d’Internet. Vous savez, le défaut est packagist.org? Sauf si nous faisons quelque chose comme ça:

 "référentiels": ["type": "composer", "url": "http: // votre serveur miroir: 4680"],

Cela fera de votre miroir le premier choix du compositeur. Mais en ajoutant juste cela dans le composer.json de votre projet ne désactivera pas l'accès à packagist.org. Si un package ne peut pas être trouvé sur le miroir local, il sera téléchargé à partir d'Internet. Si vous souhaitez bloquer cette fonctionnalité, vous pouvez également ajouter la ligne suivante à la section référentiels ci-dessus:

"emballeur": faux

Dernières pensées

C'est tout. Miroir local, avec adaptation et mise à jour automatiques des packages. Vos collègues n'auront jamais à attendre longtemps avant qu'eux-mêmes ou le serveur CI installe toutes les exigences de vos projets. S'amuser.