Sette opp en lokal speil for komponentpakker med Satis

Installere alle PHP-bibliotekene dine med Composer er en fin måte å spare tid på. Men større prosjekter som automatisk testes og kjøres ved hver forpliktelse til systemversjonskontrollen (SVC), vil ta lang tid å installere alle nødvendige pakker fra Internett. Du vil kjøre testene dine så raskt som mulig gjennom ditt kontinuerlige integrasjonssystem (CI) slik at du har rask tilbakemelding og raske reaksjoner ved feil. I denne opplæringen vil vi sette opp et lokalt speil til proxy alle pakkene som kreves i prosjektet ditt composer.json fil. Dette vil gjøre CI-arbeidet mye raskere, installere pakkene over det lokale nettverket eller til og med vert på samme maskin, og sørg for at vi har de spesifikke versjonene av pakkene alltid tilgjengelige.


Hva er Satis?

Satis er navnet på søknaden vi skal bruke til å speile de ulike repositoriene for prosjektet vårt. Den sitter som en proxy mellom Internett og din komponist. Vår løsning vil skape et lokalt speil av noen få pakker og instruere vår komponist å bruke den i stedet for kildene som finnes på Internett.

Her er et bilde som sier mer enn tusen ord.


Vårt prosjekt vil bruke komponist som vanlig. Det vil bli konfigurert til å bruke den lokale Satis-serveren som den primære kilden. Hvis en pakke er funnet der, blir den installert derfra. Hvis ikke, lar vi komponisten bruke standard packagist.org for å hente pakken.


Å få Satis

Satis er tilgjengelig gjennom komponist, så det er veldig enkelt å installere det. I vedlagte kildekoden arkiv finner du Satis installert i Kilder / Satis mappe. Først vil vi installere komponisten selv.

$ curl -sS https://getcomposer.org/installer | php #! / usr / bin / env php Alle innstillinger korrigert for å bruke Composer Downloading ... Komponist installert til: / home / csaba / Personal / Programming / NetTuts / Sette opp et lokalt speil for Composer-pakker med Satis / Kilder / Satis / komponist .phar Bruk den: php composer.phar

Så installerer vi Satis.

$ php composer.phar create-project komponist / satis -stability = dev -keep-vcs Installere komponist / satis (dev-master eddb78d52e8f7ea772436f2320d6625e18d5daf5) - Installere komponist / satis (dev master master) Kloning master Opprettet prosjekt i / hjem / csaba / Personal / Programming / NetTuts / Sette opp et lokalt speil for Composer-pakker med Satis / Kilder / Satis / satis Laster kompositorbeholdninger med pakkeinformasjon Installere avhengigheter (inkludert krav-dev) fra låsfil - Installere symfony / prosess 27b0fc6) Cloning 27b0fc645a557b2fc7bc7735cfb05505de9351be - Installering av symfony / finder (v2.4.0-BETA1) Nedlasting: 100% - Installering av symfoni / konsoll (dev-master f44cc6f) Kloning f44cc6fabdaa853335d7f54f1b86c99622db518a - Installering av seld / jsonlint (1.1.1) Nedlasting: 100% - Installering av justinrainbow / json-skjema (1.1.0) Nedlasting: 100% - Installering av komponist / komponist (dev-master f8be812) Cloning f8be812a496886c84918d6dd1b50db5c16da3cc3 - Installere twig / twig (v1.14.1) Last ned: 100% symfoni / konsoll foreslår installering av symfony / event-dispatcher () Generering av autoload-filer

Konfigurere Satis

Satis er konfigurert av en JSON-fil som ligner komponist. Du kan bruke hvilket som helst navn du vil ha for filen din og spesifisere den for bruk senere. Vi vil bruke "speilet-packages.conf".

"navn": "NetTuts Composer Mirror", "hjemmeside": "http: // localhost: 4680", "repositories": ["type": "vcs", "url": "https: // github. com / SynetoNet / monolog "," "type": "komponist", "url": "https://packagist.org"], "krever": "monolog / monolog": "syneto-dev" "mockery / mockery": "*", "phpunit / phpunit": "*", "krav-avhengighet": true

La oss analysere denne konfigurasjonsfilen.

  • Navn - representerer en streng som vil bli vist på webgrensesnittet til speilet vårt.
  • hjemmeside - er nettadressen der pakkene våre blir holdt. Dette forteller ikke vår webserver å bruke den adressen og porten, det er bare bare informasjon om en arbeidskonfigurasjon. Vi vil sette opp tilgangen til den på den adressen og porten senere.
  • repositories - en liste over repositories bestilt av preferanse. I vårt eksempel er det første depotet en Github-gaffel av monologloggingsbiblioteker. Det har noen modifikasjoner, og vi vil bruke den spesifikke gaffelen når du installerer monolog. Typen av dette depotet er "VCS". Det andre depotet er av typen"komponist". Nettadressen er standard packagist.org-siden.
  • krever - lister pakkene vi vil speile. Det kan representere en bestemt pakke med en bestemt versjon eller filial eller en hvilken som helst versjon for den saks skyld. Den bruker samme syntaks som din "krever"eller"require-dev"i din composer.json.
  • krever-avhengig - er det endelige alternativet i vårt eksempel. Det vil fortelle Satis å speile ikke bare pakkene vi spesifiserte i "krever"delen, men også alle deres avhengigheter.

For å raskt prøve ut våre innstillinger må vi først fortelle Satis å lage speilene. Kjør denne kommandoen i mappen der du installerte Satis.

$ php ./satis/bin/satis build ./mirrored-packages.conf ./packages-mirror Skannepakker Skrive packages.json Skrive webvisning

Mens prosessen finner sted, vil du se hvordan Satis speiler hver funnet versjon av de nødvendige pakkene. Vær tålmodig, det kan ta litt tid å bygge alle disse pakkene.

Satis krever det date.timezone å bli spesifisert i php.ini fil, så sørg for at den er og sett til din lokale tidssone. Ellers vises en feil.

[Twig_Error_Runtime] Et unntak er kastet under gjengivelsen av en mal ("date_default_timezone_get (): Det er ikke trygt å stole på systemets tidszoneinnstillinger. Du er * påkrevd * for å bruke date.timezone innstillingen eller date_default_timezone_set) funksjonen.

Da kan vi kjøre en PHP-server forekomst i konsollen som peker på det nylig opprettede lagringsstedet. PHP 5.4 eller nyere er nødvendig.

$ php -S localhost: 4680 -t ./packages-mirror/ PHP 5.4.22-pl0-gentoo Development Server startet på søndag 8 desember 14:47:48 2013 Lytt på http: // localhost: 4680 Dokumentrot er / hjem / csaba / Personal / Programming / NetTuts / Sette opp et lokalt speil for Composer-pakker med Satis / Kilder / Satis / packages-mirror Trykk Ctrl-C for å avslutte. [Sun Dec 8 14:48:09 2013] 127.0.0.1:56999 [200]: / [søndag 8 desember 14:48:09 2013] 127.0.0.1:57000 [404]: /favicon.ico - Ingen slik fil eller katalog

Og vi kan nå bla gjennom våre speilpakker og til og med søke etter bestemte ved å peke vår nettleser til http: // localhost: 4680:



La oss være vert på Apache

Hvis du har en løpende Apache for hånden, vil det være ganske enkelt å lage en virtuell vert for Satis.

Lytt 4680  Alternativer -Indexes FollowSymLinks Tillat Override all Order allow, deny Tillat fra alle   DocumentRoot "/ path / to / your / packages-mirror" Servernavn 127.0.0.1:4680 ServerAdmin [email protected] ErrorLog syslog: bruker 

Vi bruker bare a .conf Fil som dette, legg inn Apache conf.d mappe, vanligvis /etc/apache2/conf.d. Den skaper en virtuell vert på 4680-porten og peker den til mappen vår. Selvfølgelig kan du bruke hvilken port du vil ha.


Oppdaterer våre speil

Satis kan ikke automatisk oppdatere speilene med mindre vi forteller det. Så den enkleste måten, på et hvilket som helst UNIX-lignende system, er å bare legge til en cron-jobb i systemet ditt. Det ville være veldig enkelt, og bare et enkelt skript for å utføre vår oppdateringskommando.

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

Ulempen med denne løsningen er at den er statisk. Vi må manuelt oppdatere speilet-packages.conf hver gang vi legger til en annen pakke til prosjektet vårt composer.json. Hvis du er en del av et team i et selskap med et stort prosjekt og en kontinuerlig integrasjonsserver, kan du ikke stole på at folk husker å legge til pakkene på serveren. De kan ikke engang ha tillatelser for å få tilgang til CI-infrastrukturen.


Dynamisk oppdatering av Satis Configuration

Det er på tide for en PHP TDD-øvelse. Hvis du bare vil at koden er klar og kjører, sjekk kildekoden som er vedlagt denne opplæringen.

require_once __DIR__. '/ ... / ... / ... / ... /vendor/autoload.php'; klasse SatisUpdaterTest utvider PHPUnit_Framework_TestCase funksjonstestBehavior () $ this-> assertTrue (true); 

Som vanlig starter vi med en degenerativ test, akkurat nok til å sikre at vi har et arbeidstestrammebilde. Du kan legge merke til at jeg har en ganske merkelig utseende, ettersom jeg ønsker å unngå å måtte installere PHPUnit og Mockery for hvert lite prosjekt. Så jeg har dem i a selger mappe i min NetTuts'rot. Du bør bare installere dem med komponisten og slippe require_once linje helt og holdent.

klassen SatisUpdaterTest utvider PHPUnit_Framework_TestCase function testDefaultConfigFile () $ expected = '"navn": "NetTuts Composer Mirror", "hjemmeside": "http: // localhost: 4680", "repositories": ["type" vcs "," url ":" https://github.com/SynetoNet/monolog ", " type ":" komponist "," url ":" https://packagist.org "]," krever " : , "krav-avhengigheter": true '; $ actual = $ this-> parseComposerConf ("); $ this-> assertEquals ($ forventet, $ faktisk);

Det ser omtrent rett ut. Alle feltene unntatt "krever"er statiske. Vi trenger bare å generere pakkene. Lagringene peker på våre private git kloner og pakker etter behov. Administrere disse er mer en sysadmin jobb enn en programvareutvikler.

Selvfølgelig mislykkes dette med:

PHP Fatal feil: Ring til udefinert metode SatisUpdaterTest :: parseComposerConf ()

Å fikse det er enkelt.

privat funksjon parseComposerConf ($ streng) 

Jeg har nettopp lagt til en tom metode med det nødvendige navnet, som privat, til vår testklasse. Kult, men nå har vi en annen feil.

PHPUnit_Framework_ExpectationFailedException: Feilet hevdet at null-kamper ventet '...'

Så, null stemmer ikke overens med vår streng som inneholder all den standardkonfigurasjonen.

privat funksjon parseComposerConf ($ string) return "" name ":" NetTuts Composer Mirror "," hjemmeside ":" http: // localhost: 4680 "," repositories ": [" type ":" vcs " url ":" https://github.com/SynetoNet/monolog ", " type ":" komponist "," url ":" https://packagist.org "]," krever ": , "krav-avhengigheter": true '; 

OK, det fungerer. Alle tester går forbi.

PHPUnit 3.7.28 av Sebastian Bergmann. Tid: 15 ms, Minne: 2,50Mb OK (1 test, 1 påstand)

Men vi introduserte en fryktelig duplisering. Alt det statiske tekst på to steder, skrevet karakter etter tegn på to forskjellige steder. La oss fikse det:

klasse SatisUpdaterTest utvider PHPUnit_Framework_TestCase static $ DEFAULT_CONFIG = '"navn": "NetTuts Composer Mirror", "hjemmeside": "http: // localhost: 4680", "repositories": ["type": "vcs" url ":" https://github.com/SynetoNet/monolog ", " type ":" komponist "," url ":" https://packagist.org "]," krever ": , "krav-avhengigheter": true '; funksjon testDefaultConfigFile () $ expected = self :: $ DEFAULT_CONFIG; $ actual- $ this-> parseComposerConf ("); $ this-> assertEquals ($ forventet, $ actual); privat funksjon parseComposerConf ($ string) return selv: $ DEFAULT_CONFIG;

Ahhh! Det er bedre.

 funksjonstestEmptyRequiredPackagesInComposerJsonWillProduceDefaultConfiguration () $ expected = self :: $ DEFAULT_CONFIG; $ actual = $ this-> parseComposerConf ('"require": '); $ this-> assertEquals ($ forventet, $ faktisk); 

Vi vil. Det går også. Men det fremhever også noen duplisering og ubrukelig oppgave.

 funksjonen testDefaultConfigFile () $ actual = $ this-> parseComposerConf ("); $ this-> assertEquals (self :: $ DEFAULT_CONFIG, $ actual); funksjonstestEmptyRequiredPackagesInComposerJsonWillProduceDefaultConfiguration () $ actual = $ this-> parseComposerConf (' "krever":  '); $ this-> assertEquals (selv: $ DEFAULT_CONFIG, $ actual);

Vi insisterte på $ forventet variabel. $ faktiske kan også være innstilt, men jeg liker det bedre på denne måten. Det holder fokus på det som er testet.

Nå har vi et annet problem. Neste test jeg vil skrive vil se slik ut:

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

Men etter å ha skrevet den enkle implementeringen, vil vi legge merke til at den krever json_decode () og json_encode (). Og selvfølgelig disse funksjonene omformaterer vår streng og matchende strenger vil være vanskelig i beste fall. Vi må ta et skritt tilbake.

funksjonstestDefaultConfigFile () $ actual = $ this-> parseComposerConf ("); $ this-> assertJsonStringEqualsJsonString ($ this-> jsonRecode (self :: $ DEFAULT_CONFIG), $ actual); funksjonstestEmptyRequiredPackagesInComposerJsonWillProduceDefaultConfiguration () $ actual = $ this-> parseComposerConf ('' require ': '); $ this-> assertJsonStringEqualsJsonString ($ this-> jsonRecode (self :: $ DEFAULT_CONFIG), $ actual); privat funksjon parseComposerConf ($ jsonConfig) $ this-> jsonRecode (self :: $ DEFAULT_CONFIG); privat funksjon jsonRecode ($ json) return json_encode (json_decode ($ json, true));

Vi endret vår påstandsmetode for å sammenligne JSON-strenge og vi har også omkodet vår $ faktiske variabel. ParseComposerConf () ble også modifisert for å bruke denne metoden. Du vil se et øyeblikk hvordan det hjelper oss. Vår neste test blir mer JSON-spesifikk.

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

Og å gjøre denne testen, sammen med resten av testene, er ganske enkelt igjen.

privat funksjon parseComposerConf ($ jsonConfig) $ addedConfig = json_decode ($ jsonConfig, true); $ config = json_decode (selv :: $ DEFAULT_CONFIG, true); hvis (isset ($ addedConfig ['require'])) $ config ['require'] = $ addedConfig ['require'];  returner json_encode ($ config); 

Vi tar inn JSON-strenget, dekoder det, og hvis det inneholder en "krever"-feltet bruker vi det i Satis-konfigurasjonsfilen i stedet. Men vi vil kanskje gjenspeile alle versjoner av en pakke, ikke bare den siste. Så kanskje vi vil endre testen vår for å sjekke at versjonen er" * "i Satis, uavhengig av hvilken eksakt versjon som finnes composer.json.

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

Det mislykkes tydeligvis ikke med en kul melding:

PHPUnit_Framework_ExpectationFailedException: Feilet hevdet at to strenger er like. Forventet: * Faktisk:> = 0.7.2

Nå må vi faktisk redigere vår JSON før du re-koder den.

privat funksjon parseComposerConf ($ jsonConfig) $ addedConfig = json_decode ($ jsonConfig, true); $ config = json_decode (selv :: $ DEFAULT_CONFIG, true); $ config = $ this-> addNewRequires ($ addedConfig, $ config); return json_encode ($ config);  privat funksjon toAllVersions ($ config) foreach ($ config ['require'] som $ package => $ versjon) $ config ['require'] [$ package] = '*';  returner $ config;  privat funksjon addNewRequires ($ addedConfig, $ config) hvis (isset ($ addedConfig ['require'])) $ config ['require'] = $ addedConfig ['require']; $ config = $ this-> toAllVersions ($ config);  returner $ config; 

For å gjøre testpasset må vi iterere over hvert element av nødvendig pakkeoppsett og sette versjonen til '*'. Se metode toAllVersion () for flere detaljer. Og for å fremskynde denne opplæringen litt, tok vi også ut noen private metoder i samme trinn. Denne måten, parseComoserConf () blir veldig beskrivende og lett å forstå. Vi kunne også inline $ config inn i argumentene til addNewRequires (), men av estetiske årsaker forlot jeg den på to linjer.

Men hva med "require-dev"i composer.json?

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

Det mislykkes tydeligvis ikke. Vi kan få det til å passere med bare å kopiere / lime inn om tilstanden er i addNewRequires ():

privat funksjon addNewRequires ($ addedConfig, $ config) hvis (isset ($ addedConfig ['require']))) $ config ['require'] = $ addedConfig ['require']; $ config = $ this-> toAllVersions ($ config);  hvis (isset ($ addedConfig ['require-dev'])) $ config ['require'] = $ addedConfig ['require-dev']; $ config = $ this-> toAllVersions ($ config);  returner $ config; 

Yep, det gjør det forbi, men de dupliseres hvis uttalelser er ekkel utseende. La oss håndtere dem.

privat funksjon addNewRequires ($ addedConfig, $ config) $ config = $ this-> addRequire ($ addedConfig, 'require', $ config); $ config = $ this-> addRequire ($ addedConfig, 'require-dev', $ config); returner $ config;  privat funksjon addRequire ($ addedConfig, $ string, $ config) hvis (isset ($ addedConfig [$ string])) $ config ['require'] = $ addedConfig [$ string]; $ config = $ this-> toAllVersions ($ config);  returner $ config; 

Vi kan være lykkelige igjen, tester er grønne og vi refactored vår kode. Jeg tror bare en test er igjen for å bli skrevet. Hva om vi har begge "krever"og"require-dev"seksjoner i composer.json?

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

Det mislykkes fordi pakkene satt av "require-dev"vil overskrive de av"krever"og vi vil få en feil:

Udefinert indeks: Mockery / Mockery

Bare legg til et plustegn for å slå sammen arrayene, og vi er ferdige.

privat funksjon addRequire ($ addedConfig, $ string, $ config) hvis (isset ($ addedConfig [$ string])) $ config ['require'] + = $ addedConfig [$ string]; $ config = $ this-> toAllVersions ($ config);  returner $ config; 

Tester går forbi. Vår logikk er ferdig. Alt vi igjen å gjøre er å trekke ut metodene i egen fil og klasse. Den endelige versjonen av testene og SatisUpdater klassen finnes i vedlagte kildekoden.

Vi kan nå endre vårt cron-skript for å laste inn vår parser og kjøre den på vår composer.json. Dette vil være spesifikt for dine prosjekteres spesielle mapper. Her er et eksempel du kan tilpasse seg systemet.

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

Gjør ditt prosjekt Bruk speilet

Vi snakket om mange ting i denne artikkelen, men vi nevnte ikke hvordan vi vil instruere prosjektet vårt om å bruke speilet i stedet for Internett. Du vet, standard er packagist.org? Med mindre vi gjør noe slikt:

 "repositories": ["type": "komponist", "url": "http: // din-speil-server: 4680"],

Det vil gjøre speilet ditt første valg for komponist. Men bare å legge til det i composer.json av prosjektet ditt vil ikke deaktivere tilgang til packagist.org. Hvis det ikke finnes en pakke på det lokale speilet, lastes det ned fra Internett. Hvis du vil blokkere denne funksjonen, kan du også legge til følgende linje i repositorieseksjonen ovenfor:

"pakker": falsk

Siste tanker

Det er det. Lokalt speil, med automatisk tilpasning og oppdatering av pakker. Dine kolleger må aldri vente lenge mens de eller CI-serveren installerer alle kravene til prosjektene dine. Ha det gøy.