Einrichten eines lokalen Spiegels für Composer-Pakete mit Satis

Wenn Sie alle PHP-Bibliotheken mit Composer installieren, sparen Sie viel Zeit. Bei größeren Projekten, die automatisch getestet und bei jedem Commit an Ihr SVC-System (Software Version Control) ausgeführt werden, dauert es lange, bis alle erforderlichen Pakete aus dem Internet installiert sind. Sie möchten Ihre Tests so schnell wie möglich über Ihr Continuous Integration (CI) -System durchführen, um schnelles Feedback und schnelle Reaktionen auf Fehler zu erhalten. In diesem Lernprogramm richten wir einen lokalen Spiegel ein, um alle für Ihr Projekt erforderlichen Pakete zu repräsentieren composer.json Datei. Dadurch wird unser CI schneller arbeiten, die Pakete über das lokale Netzwerk oder sogar auf demselben Computer installieren und sicherstellen, dass immer die spezifischen Versionen der Pakete verfügbar sind.


Was ist Satis??

Satis ist der Name der Anwendung, die wir verwenden werden, um die verschiedenen Repositories für unser Projekt abzubilden. Es befindet sich als Proxy zwischen dem Internet und Ihrem Komponisten. Unsere Lösung erstellt einen lokalen Spiegel von einigen Paketen und weist unseren Komponisten an, ihn anstelle der im Internet gefundenen Quellen zu verwenden.

Hier ist ein Bild, das mehr als tausend Worte sagt.


Unser Projekt wird wie üblich Composer verwenden. Es wird so konfiguriert, dass der lokale Satis-Server als primäre Quelle verwendet wird. Wenn dort ein Paket gefunden wird, wird es von dort installiert. Wenn dies nicht der Fall ist, wird Composer die Standardeinstellung packagist.org verwenden, um das Paket abzurufen.


Satis bekommen

Satis ist über den Composer verfügbar, so dass die Installation sehr einfach ist. Im angehängten Quellcode-Archiv finden Sie Satis im Quellen / Satis Mappe. Zuerst werden wir den Composer selbst installieren.

$ curl -sS https://getcomposer.org/installer | php #! / usr / bin / env php Alle Einstellungen für die Verwendung von Composer Downloading… Composer wurde erfolgreich installiert unter: / home / csaba / Personal / Programmierung / NetTuts / Einrichten eines lokalen Spiegels für Composer-Pakete mit Satis / Sources / Satis / composer .phar Verwenden Sie es: php composer.phar

Dann werden wir Satis installieren.

$ php composer.phar create-project composer / satis --stability = dev --keep-vcs Composer / satis installieren (dev-master eddb78d52e8f7ea772436f2320d6625e18d5daf5) - composer / satis installieren (dev-master master) Cloning master Erstellt Projekt in / home / csaba / Personal / Programmieren / NetTuts / Einrichten eines lokalen Spiegels für Composer-Pakete mit Satis / Sources / Satis / satis Laden von Composer-Repositorys mit Paketinformationen Installieren von Abhängigkeiten (einschließlich required-dev) aus der Sperrdatei - Installieren von symfony / process (dev-master) 27b0fc6) Klonen 27aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaae und vaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaae7kkkdbbbb5b7bb5b3b2b4b4b005de9351ba-aaaaaaaaaaaaaaaaaaaaaaaaaalinlandein- und trageinheit (v2.4) nachlaufende (f4cc) einführung (deva master) / json-schema (1.1.0) Herunterladen: 100% - Composer / Composer installieren (dev-master f8be812) Klonen f8be812a496886c84918d6dd1b50db5c16da3cc3 - twig / twig installieren (v1.14.1) Herunterladen: 100% symfony / console empfiehlt die Installation von symfony / event-dispatcher (). Generieren von Autoload-Dateien

Satis konfigurieren

Satis wird von einer JSON-Datei konfiguriert, die dem Composer sehr ähnlich ist. Sie können einen beliebigen Namen für Ihre Datei verwenden und ihn für die spätere Verwendung angeben. Wir werden verwenden "mirrored-packages.conf".

"name": "NetTuts Composer Mirror", "homepage": "http: // localhost: 4680", "Repositorys": ["type": "vcs", "url": "https: // github. de / SynetoNet / monolog ", " type ":" composer "," url ":" https://packagist.org "]," required ": " monolog / monolog ":" syneto-dev ", "Spott / Spott": "*", "phpunit / phpunit": "*", "required-dependencies": true

Lassen Sie uns diese Konfigurationsdatei analysieren.

  • Name - stellt eine Zeichenfolge dar, die auf der Weboberfläche unseres Spiegels angezeigt wird.
  • Startseite - ist die Webadresse, unter der unsere Pakete aufbewahrt werden. Dies weist unseren Webserver nicht an, diese Adresse und diesen Port zu verwenden, es handelt sich lediglich um Informationen über eine funktionierende Konfiguration. Wir werden den Zugriff auf diese Adresse und diesen Port später einrichten.
  • Repositories - eine Liste der Repositorys, die nach Präferenz geordnet sind. In unserem Beispiel ist das erste Repository ein Github-Zweig der Monolog-Protokollierungsbibliotheken. Es hat einige Modifikationen und wir möchten diese spezielle Gabel bei der Installation von Monolog verwenden. Der Typ dieses Repositorys ist "vcs". Das zweite Repository ist vom Typ"Komponist". Ihre URL ist die standardmäßige Website von packagist.org.
  • benötigen - listet die Pakete auf, die wir spiegeln möchten. Es kann ein bestimmtes Paket mit einer bestimmten Version oder einem bestimmten Zweig oder einer beliebigen Version darstellen. Es verwendet dieselbe Syntax wie Ihr "benötigen" oder "Anforderungs-dev" in deiner composer.json.
  • Anforderungsabhängigkeiten - ist die letzte Option in unserem Beispiel. Es wird Satis angewiesen, nicht nur die Pakete zu spiegeln, die wir in "benötigen"Abschnitt, aber auch alle ihre Abhängigkeiten.

Um unsere Einstellungen schnell auszuprobieren, müssen wir Satis zunächst anweisen, die Spiegel zu erstellen. Führen Sie diesen Befehl in dem Ordner aus, in dem Sie Satis installiert haben.

$ php ./satis/bin/satis build ./mirrored-packages.conf ./packages-mirror Scannen von Paketen Schreiben von Packages.json Schreiben der Webansicht

Während des Prozesses werden Sie sehen, wie Satis die gefundenen Versionen der erforderlichen Pakete spiegelt. Bitte haben Sie etwas Geduld. Es kann etwas dauern, bis alle Pakete erstellt sind.

Satis verlangt das date.timezone in der angegeben werden php.ini stellen Sie sicher, dass es sich um eine lokale Zeitzone handelt. Andernfalls erscheint ein Fehler.

[Twig_Error_Runtime] Beim Rendern einer Vorlage wurde eine Ausnahme ausgelöst ("date_default_timezone_get (): Es ist nicht sicher, sich auf die Zeitzoneneinstellungen des Systems zu verlassen. Sie müssen * die * Einstellung * verwenden *, um die Einstellung date.timezone oder date_default_timezone_set zu verwenden).

Dann können wir in unserer Konsole eine PHP-Serverinstanz ausführen, die auf das kürzlich erstellte Repository verweist. PHP 5.4 oder neuer ist erforderlich.

$ php -S localhost: 4680 -t ./packages-mirror/ PHP 5.4.22-pl0-gentoo Der Entwicklungs-Server wurde am 8. Dezember 2012 um 14:47:48 Uhr 2013 gestartet. Hören auf http: // localhost: 4680 Das Stammverzeichnis ist / home / csaba / Personal / Programmieren / NetTuts / Lokale Spiegelung für Composer-Pakete mit Satis / Sources / Satis / packages-mirror einrichten Drücken Sie zum Beenden die Tastenkombination Strg-C. [So 8. Dezember 14:48:09 2013] 127.0.0.1:56999 [200]: / [So 8. Dezember 14:48:09 2013] 127.0.0.1:57000 [404]: /favicon.ico - Keine solche Datei oder Verzeichnis

Wir können jetzt unsere gespiegelten Pakete durchsuchen und sogar nach bestimmten Paketen suchen, indem Sie auf unseren Webbrowser zeigen http: // localhost: 4680:



Lassen Sie uns es auf Apache hosten

Wenn Sie Apache zur Hand haben, können Sie einen virtuellen Host für Satis erstellen.

Hören Sie 4680  Optionen -Indexes FollowSymLinks AllowOverride all Order zulassen, Alle zulassen   DocumentRoot "/ path / to / your / packages-mirror" Servername 127.0.0.1:4680 ServerAdmin [email protected] ErrorLog syslog: Benutzer 

Wir benutzen einfach eine .conf Datei wie diese, in Apache conf.d Ordner normalerweise /etc/apache2/conf.d. Es erstellt einen virtuellen Host am Port 4680 und verweist ihn auf unseren Ordner. Natürlich können Sie jeden beliebigen Port verwenden.


Aktualisieren unserer Spiegel

Satis kann die Spiegel nicht automatisch aktualisieren, es sei denn, wir informieren es. Der einfachste Weg, auf einem UNIX-ähnlichen System, besteht darin, einfach einen Cron-Job zu Ihrem System hinzuzufügen. Das wäre sehr einfach und nur ein einfaches Skript, um unseren Aktualisierungsbefehl auszuführen.

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

Der Nachteil dieser Lösung ist, dass sie statisch ist. Wir müssen das manuell aktualisieren mirrored-packages.conf Jedes Mal fügen wir unserem Projekt ein weiteres Paket hinzu composer.json. Wenn Sie Teil eines Teams in einem Unternehmen mit einem großen Projekt und einem Server mit kontinuierlicher Integration sind, können Sie sich nicht darauf verlassen, dass sich die Benutzer daran erinnern, die Pakete auf dem Server hinzuzufügen. Sie haben möglicherweise nicht einmal die Berechtigung zum Zugriff auf die CI-Infrastruktur.


Satis-Konfiguration dynamisch aktualisieren

Es ist Zeit für eine PHP-TDD-Übung. Wenn Sie nur möchten, dass Ihr Code betriebsbereit ist, lesen Sie den mit diesem Tutorial verbundenen Quellcode.

required_once __DIR__. '/… /… /… /… /Vendor/autoload.php'; class SatisUpdaterTest erweitert PHPUnit_Framework_TestCase function testBehavior () $ this-> assertTrue (true); 

Wie üblich beginnen wir mit einem degenerativen Test, gerade genug, um sicherzustellen, dass wir ein funktionierendes Test-Framework haben. Möglicherweise stellen Sie fest, dass ich eine ziemlich seltsame aussehende Require_once-Zeile habe. Dies liegt daran, dass Sie PHPUnit und Mockery nicht für jedes kleine Projekt neu installieren müssen. Also habe ich sie in einer Verkäufer Ordner in meinem NetTuts' Wurzel. Sie sollten sie einfach mit Composer installieren und die einmalig benötigt Linie insgesamt.

Klasse SatisUpdaterTest erweitert PHPUnit_Framework_TestCase Funktion testDefaultConfigFile () $ expected = '"name": "NetTuts Composer Mirror", "Homepage": "http: // localhost: 4680", "repositories": ["type": " vcs "," url ":" https://github.com/SynetoNet/monolog ", " type ":" composer "," url ":" https://packagist.org "]," required " : , "required-dependencies": true '; $ actual = $ this-> parseComposerConf ("); $ this-> assertEquals ($ erwartet, $ actual);

Das sieht gut aus. Alle Felder außer "benötigen"sind statisch. Wir müssen nur die Pakete generieren. Die Repositorys verweisen auf unsere privaten Git-Klone und auf Packagist, wenn dies erforderlich ist. Das Verwalten dieser Pakete ist eher ein Sysadmin-Job als ein Software-Entwickler.

Natürlich scheitert das bei:

PHP-Schwerwiegender Fehler: Aufruf der nicht definierten Methode SatisUpdaterTest :: parseComposerConf ()

Das zu beheben ist einfach.

private Funktion parseComposerConf ($ string) 

Ich habe gerade eine leere Methode mit dem erforderlichen Namen als privat zu unserer Testklasse hinzugefügt. Cool, aber jetzt haben wir einen anderen Fehler.

PHPUnit_Framework_ExpectationFailedException: Fehler beim Feststellen, dass der Nullwert mit dem erwarteten '…' übereinstimmt

Daher stimmt null nicht mit unserer Zeichenfolge überein, die alle diese Standardkonfiguration enthält.

private Funktion parseComposerConf ($ string) return '"name": "NetTuts Composer Mirror", "Homepage": "http: // localhost: 4680", "Repositorys": ["type": "vcs", " url ":" https://github.com/SynetoNet/monolog ", " type ":" composer "," url ":" https://packagist.org "]," required ": , "Anforderungsabhängigkeiten": true '; 

OK, das geht. Alle Tests sind bestanden.

PHPUnit 3.7.28 von Sebastian Bergmann. Zeit: 15 ms, Speicher: 2,50 MB OK (1 Test, 1 Assertion)

Aber wir haben eine schreckliche Vervielfältigung eingeführt. All diesen statischen Text an zwei Stellen, Zeichen für Zeichen an zwei verschiedenen Stellen. Lass uns das regeln:

Klasse SatisUpdaterTest erweitert PHPUnit_Framework_TestCase static $ DEFAULT_CONFIG = '"name": "NetTuts Composer Mirror", "homepage": "http: // localhost: 4680", "repositories": ["type": "vcs", " url ":" https://github.com/SynetoNet/monolog ", " type ":" composer "," url ":" https://packagist.org "]," required ": , "Anforderungsabhängigkeiten": true '; function testDefaultConfigFile () $ erwartet = self :: $ DEFAULT_CONFIG; $ actual = $ this-> parseComposerConf ("); $ this-> assertEquals ($ erwartet, $ actual); private Funktion parseComposerConf ($ string) return self :: $ DEFAULT_CONFIG;

Ähhh! Das ist besser.

 Funktion testEmptyRequiredPackagesInComposerJsonWillProduceDefaultConfiguration () $ erwartet = self :: $ DEFAULT_CONFIG; $ actual = $ this-> parseComposerConf ('"required": '); $ this-> assertEquals ($ erwartet, $ actual); 

Gut. Das geht auch. Es zeigt aber auch einige Verdopplungen und nutzlose Zuweisungen.

 Funktion testDefaultConfigFile () $ actual = $ this-> parseComposerConf ("); $ this-> assertEquals (self :: $ DEFAULT_CONFIG, $ actual); Funktion testEmptyRequiredPackagesInComposerJsonWillProduceDefaultConfiguration () $ actual = $ this-achl. "required":  '); $ this-> assertEquals (self :: $ DEFAULT_CONFIG, $ actual);

Wir haben die $ erwartet Variable. $ Ist könnte auch inline sein, aber ich mag es besser so. Es behält den Fokus auf dem, was getestet wird.

Jetzt haben wir ein anderes Problem. Der nächste Test, den ich schreiben möchte, würde folgendermaßen aussehen:

Funktion testARequiredPackageInComposerWillBeInSatisAlso () $ actual = $ this-> parseComposerConf ('"required": "Mockery / Mockery": "> = 0.7.2"'); $ this-> assertContains ('"Spott / Spott": "> = 0.7.2"', $ actual); 

Aber nachdem wir die einfache Implementierung geschrieben haben, werden wir feststellen, dass dies erforderlich ist json_decode () und json_encode (). Und natürlich formatieren diese Funktionen unsere Zeichenfolge neu, und passende Zeichenfolgen sind im besten Fall schwierig. Wir müssen einen Schritt zurücktreten.

function testDefaultConfigFile () $ actual = $ this-> parseComposerConf ("); $ this-> assertJsonStringEqualsJsonString ($ this-> jsonRecode (selbst :: $ DEFAULT_CONFIG), $ actual); this-> parseComposerConf ('"required": '); $ this-> assertJsonStringEqualsJsonString ($ this-> jsonRecode (self :: $ DEFAULT_CONFIG), $ actual); private Funktion parseComposerConf ($ jsonConfig) return $ this-> jsonRecode (self :: $ DEFAULT_CONFIG); private Funktion jsonRecode ($ json) return json_encode (json_decode ($ json, true));

Wir haben unsere Assertionsmethode geändert, um JSON-Strings zu vergleichen, und haben unsere auch neu codiert $ Ist Variable. ParseComposerConf () wurde auch modifiziert, um diese Methode zu verwenden. Sie werden gleich sehen, wie es uns hilft. Unser nächster Test wird spezifischer für JSON.

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

Und diesen Testdurchlauf zusammen mit den restlichen Tests zu machen, ist wieder ziemlich einfach.

private Funktion parseComposerConf ($ jsonConfig) $ addedConfig = json_decode ($ jsonConfig, true); $ config = json_decode (self :: $ DEFAULT_CONFIG, true); if (isset ($ addedConfig ['Requires'])) $ config ['Requires'] = $ addedConfig ['Requires'];  Rückgabe von json_encode ($ config); 

Wir nehmen die Eingabe-JSON-Zeichenfolge, dekodieren sie und wenn sie ein "enthält"benötigen"Das Feld verwenden wir stattdessen in unserer Satis-Konfigurationsdatei. Wir möchten jedoch möglicherweise alle Versionen eines Pakets spiegeln, nicht nur die letzte. Daher möchten wir vielleicht unseren Test ändern, um zu prüfen, ob die Version in Satis" * "ist. unabhängig von der genauen Version composer.json.

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

Das versagt offensichtlich mit einer coolen Nachricht:

PHPUnit_Framework_ExpectationFailedException: Fehler beim Bestätigen, dass zwei Zeichenfolgen gleich sind. Erwartet: * Tatsächlich:> = 0.7.2

Nun müssen wir unseren JSON tatsächlich bearbeiten, bevor er neu codiert wird.

private Funktion parseComposerConf ($ jsonConfig) $ addedConfig = json_decode ($ jsonConfig, true); $ config = json_decode (self :: $ DEFAULT_CONFIG, true); $ config = $ this-> addNewRequires ($ addedConfig, $ config); return json_encode ($ config);  private Funktion toAllVersions ($ config) foreach ($ config ['required'] als $ package => $ version) $ config ['required'] [$ package] = '*';  Rückgabe von $ config;  private Funktion addNewRequires ($ addedConfig, $ config) if (isset ($ addedConfig ['required'])) $ config ['required'] = $ addedConfig ['required']; $ config = $ this-> toAllVersions ($ config);  Rückgabe von $ config; 

Um den Test zu bestehen, müssen wir jedes Element des erforderlichen Paket-Arrays durchlaufen und die Version auf '*' setzen. Siehe Methode toAllVersion () für mehr Details. Um dieses Tutorial etwas zu beschleunigen, haben wir im gleichen Schritt auch einige private Methoden extrahiert. Diesen Weg, parseComoserConf () wird sehr anschaulich und leicht verständlich. Wir könnten auch inline gehen $ config in die Argumente von addNewRequires (), aber aus ästhetischen Gründen habe ich es auf zwei Zeilen gelassen.

Aber was ist mit "Anforderungs-dev" im composer.json?

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

Das scheitert offensichtlich. Wir können es passieren lassen, indem Sie einfach unsere if-Bedingung in kopieren / einfügen addNewRequires ():

private Funktion addNewRequires ($ addedConfig, $ config) if (isset ($ addedConfig ['required'])) $ config ['required'] = $ addedConfig ['required']; $ config = $ this-> toAllVersions ($ config);  if (isset ($ addedConfig ['required-dev'])) $ config ['required'] = $ addedConfig ['requir-dev']; $ config = $ this-> toAllVersions ($ config);  Rückgabe von $ config; 

Ja, das lässt es passieren, aber die doppelten, wenn Anweisungen aussagen, sehen unangenehm aus. Lass uns mit ihnen umgehen.

private Funktion addNewRequires ($ addedConfig, $ config) $ config = $ this-> addRequire ($ addedConfig, 'required', $ config); $ config = $ this-> addRequire ($ addedConfig, 'required-dev', $ config); return $ config;  private Funktion addRequire ($ addedConfig, $ string, $ config) if (isset ($ addedConfig [$ string])) $ config ['required'] = $ addedConfig [$ string]; $ config = $ this-> toAllVersions ($ config);  Rückgabe von $ config; 

Wir können wieder glücklich sein, die Tests sind grün und wir haben unseren Code überarbeitet. Ich denke, dass nur noch ein Test geschrieben werden muss. Was ist, wenn wir beide haben "benötigen" und "Anforderungs-dev"Abschnitte in composer.json?

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

Das schlägt fehl, weil die Pakete durch "Anforderungs-dev"wird die von" überschreibenbenötigen"und wir werden einen Fehler haben:

Undefinierter Index: Spott / Spott

Fügen Sie einfach ein Pluszeichen hinzu, um die Arrays zusammenzuführen, und wir sind fertig.

private Funktion addRequire ($ addedConfig, $ string, $ config) if (isset ($ addedConfig [$ string])) $ config ['required'] + = $ addedConfig [$ string]; $ config = $ this-> toAllVersions ($ config);  Rückgabe von $ config; 

Tests sind bestanden. Unsere Logik ist fertig. Wir müssen nur noch die Methoden in ihre eigene Datei und Klasse extrahieren. Die endgültige Version der Tests und der SatisUpdater Klasse kann im beigefügten Quellcode gefunden werden.

Wir können jetzt unser cron-Skript modifizieren, um unseren Parser zu laden und auf unserem auszuführen composer.json. Dies ist spezifisch für die jeweiligen Ordner Ihres Projekts. Hier ist ein Beispiel, das Sie an Ihr System anpassen können.

#! / usr / local / bin / php parseComposerConf (file_get_contents ($ composerJsonFile)); file_put_contents ($ satisConf, $ conf); system (sprintf ('/ pfad / nach / satis / bin / satis build% s% s', $ satisConf, $ outputDir), $ retval); exit ($ retval);

Projekt für den Spiegel verwenden

Wir haben in diesem Artikel über viele Dinge gesprochen, aber wir haben nicht erwähnt, wie wir unser Projekt anweisen sollen, den Spiegel anstelle des Internets zu verwenden. Wissen Sie, der Standard ist packagist.org? Es sei denn, wir machen so etwas:

 "Repositorys": ["Typ": "Komponist", "URL": "http: // Ihr Spiegelserver: 4680"],

Damit wird Ihr Spiegel zur ersten Wahl für den Komponisten. Aber genau das in die composer.json Ihres Projekts wird der Zugriff auf packagist.org nicht deaktiviert. Wenn ein Paket nicht auf dem lokalen Spiegel gefunden werden kann, wird es aus dem Internet heruntergeladen. Wenn Sie diese Funktion blockieren möchten, können Sie auch die folgende Zeile zum obigen Repository-Abschnitt hinzufügen:

"packagist": falsch

Abschließende Gedanken

Das ist es. Lokale Spiegelung mit automatischer Anpassung und Aktualisierung von Paketen. Ihre Kollegen müssen nie lange warten, bis sie oder der CI-Server alle Anforderungen an Ihre Projekte installiert. Habe Spaß.