Installera alla dina PHP-bibliotek med Kompositör är ett bra sätt att spara tid. Men större projekt som automatiskt testas och körs vid varje förpliktelse till ditt SVC-system (Software Version Control) kommer att ta lång tid att installera alla nödvändiga paket från Internet. Du vill köra dina test så fort som möjligt genom ditt kontinuerliga integrationssystem (CI) så att du har snabb feedback och snabba reaktioner vid misslyckande. I denna handledning kommer vi att ställa in en lokal spegel till proxy alla dina paket som krävs i ditt projekt composer.json
fil. Detta kommer att göra vårt CI-arbete mycket snabbare, installera paketen över det lokala nätverket eller ens värd på samma maskin och se till att vi har de specifika versionerna av paketen alltid tillgängliga.
Satis är namnet på den ansökan vi ska använda för att spegla de olika arkiverna för vårt projekt. Det sitter som en proxy mellan Internet och din kompositör. Vår lösning kommer att skapa en lokal spegel av några paket och instruera vår kompositör att använda den istället för de källor som finns på Internet.
Här är en bild som säger mer än tusen ord.
Vårt projekt kommer att använda kompositören som vanligt. Det kommer att konfigureras att använda den lokala Satis-servern som den primära källan. Om ett paket finns där kommer det att installeras därifrån. Om inte, låter vi kompositören använda standardpaketet.se för att hämta paketet.
Satis är tillgänglig via kompositör, så installationen är väldigt enkel. I det bifogade källkodsarkivet hittar du Satis installerat i Källor / Satis
mapp. Först installerar vi kompositören själv.
$ curl -sS https://getcomposer.org/installer | php #! / usr / bin / env php Alla inställningar är korrekta för att använda Composer Hämtar ... Kompositören har installerats till: / home / csaba / Personal / Programming / NetTuts / Konfigurera en lokal spegel för kompositörspaket med Satis / Källor / Satis / kompositör .phar Använd det: php composer.phar
Sedan installerar vi Satis.
$ php composer.phar create-project komponist / satis -stability = dev -keep-vcs Installera kompositör / satis (dev-master eddb78d52e8f7ea772436f2320d6625e18d5daf5) - Installera kompositör / satis (dev master master) Kloning master Skapat projekt i / hem / csaba / Personal / Programming / NetTuts / Sätta upp en lokal spegel för kompositörspaket med Satis / Källor / Satis / satis Laddar kompositregister med paketinformation Installera beroenden (inklusive krav-dev) från låsfilen - Installera symfony / process 27b0fc6) Cloning 27b0fc645a557b2fc7bc7735cfb05505de9351be - Installera sfm / jsonlint (1.1.1) Nedladdning / json-schema (1.1.0) Nedladdning: 100% - Installera kompositör / kompositör (dev-master f8be812) Kloning f8be812a496886c84918d6dd1b50db5c16da3cc3 - Installera kvist / twig (v1.14.1) Nedladdning: 100% symfoni / konsol föreslår att du installerar symfony / event-dispatcher () Generera autoload-filer
Satis är konfigurerad av en JSON-fil som liknar kompositören. Du kan använda vilket namn du vill ha för din fil och ange den för användning senare. Vi kommer använda "speglad-packages.conf
".
"namn": "NetTuts Composer Mirror", "hemsida": "http: // localhost: 4680", "repositories": ["typ": "vcs", "url": "https: // github. com / SynetoNet / monolog "," "typ": "kompositör", "url": "https://packagist.org"], "kräver": "monolog / monolog": "syneto-dev" "mockery / mockery": "*", "phpunit / phpunit": "*", "kräver-beroenden": true
Låt oss analysera den här konfigurationsfilen.
namn
- representerar en sträng som kommer att visas på webbgränssnittet i vår spegel.hemsida
- är webbadressen där våra paket kommer att hållas. Detta berättar inte att vår webbserver använder den adressen och porten, det är bara information om en fungerande konfiguration. Vi kommer att ställa upp tillgången till den på den adressen och porten senare.förråd
- en förteckning över förråd som beställts genom preferens I vårt exempel är det första förvaret en Github-gaffel av monologloggningsbiblioteken. Det har några ändringar och vi vill använda den specifika gaffeln när du installerar monolog. Typen av detta förråd är "vcs
". Det andra förvaret är av typen"kompositör
". Dess URL är standardpaketet.se.fordra
- listar de paket vi vill spegla. Det kan representera ett specifikt paket med en viss version eller filial eller någon version för den delen. Den använder samma syntax som din "fordra
"eller"kräva-dev
"i din composer.json
.kräva-beroenden
- är det sista alternativet i vårt exempel. Det kommer att berätta för Satis att spegla inte bara de paket som vi angav i "fordra
"men också alla deras beroende.För att snabbt prova våra inställningar måste vi först tala om för Satis att skapa speglarna. Kör det här kommandot i mappen där du installerade Satis.
$ php ./satis/bin/satis build ./mirrored-packages.conf ./packages-mirror Skanna paket Skriva paket.json Skriva webbvy
Medan processen sker, ser du hur Satis speglar varje hittad version av de nödvändiga paketen. Var tålamod det kan ta ett tag att bygga alla dessa paket.
Satis kräver det date.timezone
att specificeras i php.ini
filen, så se till att den är och ställa in din lokala tidszon. Annars visas ett fel.
[Twig_Error_Runtime] Ett undantag har kastats under rendering av en mall ("date_default_timezone_get (): Det är inte säkert att förlita sig på systemets tidszon inställningar. Du är * obligatorisk * för att använda inställningen date.timezone eller date_default_timezone_set).
Då kan vi köra en PHP-server förekomst i vår konsol som pekar på det nyligen skapade förvaret. PHP 5,4 eller senare krävs.
$ php -S localhost: 4680 -t ./packages-mirror/ PHP 5.4.22-pl0-gentoo Utvecklingsserver startade på Sun Dec 8 14:47:48 2013 Lyssna på http: // localhost: 4680 Dokumentroten är / home / csaba / Personal / Programmering / NetTuts / Ställ in en lokal spegel för kompositörspaket med Satis / Källor / Satis / paket-spegel Tryck på Ctrl-C för att avsluta. [Sun Dec 8 14:48:09 2013] 127.0.0.1:56999 [200]: / [Sun Dec 8 14:48:09 2013] 127.0.0.1:57000 [404]: /favicon.ico - Ingen sådan fil eller katalog
Och vi kan nu bläddra i våra speglade paket och även leta efter specifika genom att peka vår webbläsare till http: // localhost: 4680
:
Om du har en löpande Apache till hands kommer det att vara ganska enkelt att skapa en virtuell värd för Satis.
Lyssna 4680Alternativ -Indexes FollowSymLinks TillåtOverride all Order tillåta, neka Tillåt från alla DocumentRoot "/ path / to / your / packages-mirror" ServerName 127.0.0.1:4680 ServerAdmin [email protected] ErrorLog syslog: användare
Vi använder bara a .conf
Fil som denna, lägg i Apache conf.d
mapp, vanligtvis /etc/apache2/conf.d
. Den skapar en virtuell värd på 4680-porten och pekar den på vår mapp. Naturligtvis kan du använda vilken port du vill ha.
Satis kan inte uppdatera speglarna automatiskt om vi inte säger det. Så det enklaste sättet, på något UNIX-liknande system, är att bara lägga till ett cron-jobb i ditt system. Det skulle vara väldigt enkelt, och bara ett enkelt skript för att utföra vårt uppdateringskommando.
#! / bin / bash php / full / bana / till / satis / bin / satis build \ /full/path/to/mirrored-packages.conf \ / full / path / to / packages-mirror
Nackdelen med denna lösning är att den är statisk. Vi måste manuellt uppdatera speglad-packages.conf
varje gång vi lägger till ett annat paket till vårt projekt composer.json
. Om du ingår i ett team i ett företag med ett stort projekt och en kontinuerlig integrationsserver, kan du inte lita på att människor kommer ihåg att lägga till paket på servern. De får inte ens ha behörigheter att komma åt CI-infrastrukturen.
Det är dags för en PHP TDD-övning. Om du bara vill ha din kod klar och köra, kolla källkoden som bifogas denna handledning.
require_once __DIR__. "/ ... / ... / ... / ... /vendor/autoload.php"; klass SatisUpdaterTest utökar PHPUnit_Framework_TestCase funktionstestBehavior () $ this-> assertTrue (true);
Som vanligt börjar vi med ett degenerativt test, tillräckligt för att vi ska ha en arbetsprovningsram. Du kanske märker att jag har en ganska konstig look requisition line, det beror på att jag vill undvika att behöva installera om PHPUnit och Mockery för varje litet projekt. Så jag har dem i en Säljare
mapp i min NetTuts
"rot. Du ska bara installera dem med kompositör och släppa require_once
linje helt och hållet.
klass SatisUpdaterTest utökar PHPUnit_Framework_TestCase function testDefaultConfigFile () $ expected = '"name": "NetTuts Composer Mirror", "hemsida": "http: // localhost: 4680", "repositories": ["type" vcs "," url ":" https://github.com/SynetoNet/monolog ", " typ ":" kompositör "," url ":" https://packagist.org "]," kräver " : , "kräver-beroenden": true '; $ actual = $ this-> parseComposerConf ("); $ this-> assertEquals ($ förväntat, $ actual);
Det ser ungefär rätt ut. Alla fält utom "fordra
"är statiska. Vi behöver bara skapa paketen. Förvaringarna pekar på våra privata gitkloner och förpackare efter behov. Hantering av dem är mer av ett sysadminjobb än en mjukvaruutvecklare.
Naturligtvis misslyckas det med:
PHP Felaktigt fel: Ring till odefinierad metod SatisUpdaterTest :: parseComposerConf ()
Fixing det är enkelt.
privat funktion parseComposerConf ($ string)
Jag har bara lagt till en tom metod med det önskade namnet, som privat, till vår testklass. Cool, men nu har vi ett annat fel.
PHPUnit_Framework_ExpectationFailedException: Misslyckades hävdar att noll matchningar förväntas "..."
Så, null matchar inte vår sträng som innehåller all standardkonfiguration.
privat funktion parseComposerConf ($ string) return "" name ":" NetTuts Composer Mirror "," hemsida ":" http: // localhost: 4680 "," repositories ": [" type ":" vcs " url ":" https://github.com/SynetoNet/monolog ", " typ ":" kompositör "," url ":" https://packagist.org "]," kräver ": , "kräver-beroenden": true ';
OK, det fungerar. Alla tester passerar.
PHPUnit 3.7.28 av Sebastian Bergmann. Tid: 15 ms, Minne: 2,50Mb OK (1 test, 1 påstående)
Men vi introducerade en hemsk dubbelarbete. Allt det statiska text på två ställen, skrivet tecken efter tecken på två olika ställen. Låt oss fixa det:
klass SatisUpdaterTest utökar PHPUnit_Framework_TestCase static $ DEFAULT_CONFIG = '"namn": "NetTuts Composer Mirror", "hemsida": "http: // localhost: 4680", "repositories": ["type": "vcs" url ":" https://github.com/SynetoNet/monolog ", " typ ":" kompositör "," url ":" https://packagist.org "]," kräver ": , "kräver-beroenden": true '; funktionstestDefaultConfigFile () $ expected = self :: $ DEFAULT_CONFIG; $ actual- $ this-> parseComposerConf ("); $ this-> assertEquals ($ expected, $ actual); privat funktion parseComposerConf ($ string) returnera själv :: $ DEFAULT_CONFIG;
Ahhh! Det är bättre.
funktionstestEmptyRequiredPackagesInComposerJsonWillProduceDefaultConfiguration () $ expected = self :: $ DEFAULT_CONFIG; $ actual = $ this-> parseComposerConf ('"kräver": '); $ this-> assertEquals ($ expected, $ actual);
Väl. Det passerar också. Men det framhäver också en del dubbelarbete och värdelös uppgift.
funktionstestDefaultConfigFile () $ actual = $ this-> parseComposerConf ("); $ this-> assertEquals (self :: $ DEFAULT_CONFIG, $ actual); funktionstestEmptyRequiredPackagesInComposerJsonWillProduceDefaultConfiguration () $ actual = $ this-> parseComposerConf (' "kräver": '); $ this-> assertEquals (själv :: $ DEFAULT_CONFIG, $ actual);
Vi inledde $ väntat
variabel. $ faktiska
kan också inline, men jag gillar det bättre på det här sättet. Det håller fokus på det som testas.
Nu har vi ett annat problem. Nästa test jag vill skriva skulle se ut så här:
funktionstestAvfrågadPackageInComposerWillBeInSatisAlso () $ actual = $ this-> parseComposerConf ('"kräver": "Mockery / Mockery": "> = 0.7.2"'); $ this-> assertContains ('"Mockery / Mockery": "> = 0.7.2"', $ actual);
Men efter att ha skrivit den enkla implementeringen kommer vi att märka att det krävs json_decode ()
och json_encode ()
. Och naturligtvis formaterar dessa funktioner våra strängar och matchande strängar kommer att vara svåra i bästa fall. Vi måste ta ett steg tillbaka.
funktionstestDefaultConfigFile () $ actual = $ this-> parseComposerConf ("); $ this-> assertJsonStringEqualsJsonString ($ this-> jsonRecode (self :: $ DEFAULT_CONFIG), $ actual); funktionstestEmptyRequiredPackagesInComposerJsonWillProduceDefaultConfiguration () $ actual = $ this-> parseComposerConf ('' require ': '); $ this-> assertJsonStringEqualsJsonString ($ this-> jsonRecode (self :: $ DEFAULT_CONFIG), $ actual); privat funktion parseComposerConf ($ jsonConfig) $ this-> jsonRecode (self :: $ DEFAULT_CONFIG); privat funktion jsonRecode ($ json) return json_encode (json_decode ($ json, true));
Vi ändrade vår påstående metod att jämföra JSON strängar och vi recoded också vår $ faktiska
variabel. ParseComposerConf ()
Modifierades också för att använda denna metod. Du ser på ett ögonblick hur det hjälper oss. Vårt nästa test blir mer JSON-specifikt.
funktionstestAvfrågadPackageInComposerWillBeInSatisAlso () $ actual = $ this-> parseComposerConf ('"kräver": "Mockery / Mockery": "> = 0.7.2"'); $ this-> assertEquals ('> = 0.7.2', json_decode ($ actual, true) ['kräver'] ['Mockery / Mockery']);
Och att göra det här provet, tillsammans med resten av testerna, är ganska enkelt igen.
privat funktion parseComposerConf ($ jsonConfig) $ addedConfig = json_decode ($ jsonConfig, true); $ config = json_decode (själv :: $ DEFAULT_CONFIG, true); om (isset ($ addedConfig ['require'])) $ config ['require'] = $ addedConfig ['require']; returnera json_encode ($ config);
Vi tar den inmatade JSON-strängen, avkodar den och om den innehåller en "fordra
"vi använder det i vår Satis-konfigurationsfil istället. Men vi kanske vill spegla alla versioner av ett paket, inte bara den sista. Så kanske vi vill ändra vårt test för att kontrollera att versionen är" * "i Satis, oavsett vilken exakt version som finns i composer.json
.
funktionstestAvfrågadPackageInComposerWillBeInSatisAlso () $ actual = $ this-> parseComposerConf ('"kräver": "Mockery / Mockery": "> = 0.7.2"'); $ this-> assertEquals ('*', json_decode ($ actual, true) ['kräver'] ['Mockery / Mockery']);
Det uppenbarligen misslyckas med ett coolt meddelande:
PHPUnit_Framework_ExpectationFailedException: Misslyckades hävdar att två strängar är lika. Förväntad: * Faktisk:> = 0.7.2
Nu måste vi faktiskt redigera vår JSON innan du kodar om den igen.
privat funktion parseComposerConf ($ jsonConfig) $ addedConfig = json_decode ($ jsonConfig, true); $ config = json_decode (själv :: $ DEFAULT_CONFIG, true); $ config = $ this-> addNewRequires ($ addedConfig, $ config); returnera json_encode ($ config); privat funktion toAllVersions ($ config) foreach ($ config ['require'] som $ package => $ version) $ config ['require'] [$ package] = '*'; returnera $ config; privat funktion addNewRequires ($ addedConfig, $ config) om (isset ($ addedConfig ['require']))) $ config ['require'] = $ addedConfig ['require']; $ config = $ this-> toAllVersions ($ config); returnera $ config;
För att göra testpasset måste vi iterera över varje element i erforderliga paketmatris och sätta sin version till "*". Se metod toAllVersion ()
för mer detaljer. Och för att påskynda denna handledning lite tog vi också ut några privata metoder i samma steg. Den här vägen, parseComoserConf ()
blir mycket beskrivande och lätt att förstå. Vi kunde också inline $ config
in i argumenten till addNewRequires ()
, men av estetiska skäl lämnade jag den på två linjer.
Men vad sägs om "kräva-dev
" i composer.json
?
funktionen testARquiredDevPackageInComposerWillBeInSatisAlso () $ actual = $ this-> parseComposerConf ('"require-dev": "Mockery / Mockery": "> = 0.7.2", "phpunit / phpunit": "3.7.28" '); $ this-> assertEquals ('*', json_decode ($ actual, true) ['kräver'] ['Mockery / Mockery']); $ this-> assertEquals ('*', json_decode ($ actual, true) ['kräver'] ['phpunit / phpunit']);
Det misslyckas uppenbarligen. Vi kan få det att passera med bara kopiera / klistra in om villkoret är i addNewRequires ()
:
($ addedConfig, $ config) if $ config = $ this-> toAllVersions ($ config); om (isset ($ addedConfig ['require-dev'])) $ config ['require'] = $ addedConfig ['require-dev']; $ config = $ this-> toAllVersions ($ config); returnera $ config;
Ja, det gör det förbi, men de dupliceras om uttalanden är otäcka. Låt oss ta itu med dem.
privat funktion addNewRequires ($ addedConfig, $ config) $ config = $ this-> addRequire ($ addedConfig, "require", $ config); $ config = $ this-> addRequire ($ addedConfig, 'require-dev', $ config); returnera $ config; privat funktion addRequire ($ addedConfig, $ string, $ config) om (isset ($ addedConfig [$ string])) $ config ['require'] = $ addedConfig [$ string]; $ config = $ this-> toAllVersions ($ config); returnera $ config;
Vi kan vara glada igen, testen är grön och vi refactored vår kod. Jag tror att endast ett test kvarstår att skrivas. Vad händer om vi har båda "fordra
"och"kräva-dev
"avsnitt i composer.json
?
funktionen testItCanParseComposerJsonWithBothSections () $ actual = $ this-> parseComposerConf ('"kräver": "Mockery / Mockery": "> = 0.7.2", "kräver-dev": "phpunit / phpunit" 3.7.28 " '); $ this-> assertEquals ('*', json_decode ($ actual, true) ['kräver'] ['Mockery / Mockery']); $ this-> assertEquals ('*', json_decode ($ actual, true) ['kräver'] ['phpunit / phpunit']);
Det misslyckas eftersom paketen som "kräva-dev
"kommer att skriva över"fordra
"och vi kommer att ha ett fel:
Odefinierat index: Mockery / Mockery
Lägg bara till ett plustecken för att slå samman arraysna, och vi är färdiga.
privata funktion addRequire ($ addedConfig, $ string, $ config) om (isset ($ addedConfig [$ string])) $ config ['require'] + = $ addedConfig [$ string]; $ config = $ this-> toAllVersions ($ config); returnera $ config;
Tester passerar. Vår logik är klar. Allt vi kvar att göra är att extrahera metoderna i sin egen fil och klass. Den slutliga versionen av testen och SatisUpdater
klass finns i den bifogade källkoden.
Vi kan nu ändra vårt cron-skript för att ladda vår parser och köra den på vår composer.json
. Detta kommer att vara specifikt för dina projekts specifika mappar. Här är ett exempel som du kan anpassa till ditt system.
#! / Usr / local / bin / php parseComposerConf (file_get_contents ($ composerJsonFile)); file_put_contents ($ satisConf, $ conf); system (sprintf ('/ path / to / satis / bin / satis build% s% s', $ satisConf, $ outputDir), $ retval); exit ($ retval);
Vi pratade om en massa saker i den här artikeln, men vi nämnde inte hur vi kommer att instruera vårt projekt att använda spegeln istället för Internet. Du vet, standard är packagist.org? Om vi inte gör något så här:
"repositories": ["typ": "kompositör", "url": "http: // din spegelserver: 4680"],
Det gör din spegel till den första valet för kompositören. Men lägger bara till det i composer.json
av ditt projekt kommer inte att inaktivera åtkomst till packagist.org. Om ett paket inte finns på den lokala spegeln, kommer den att hämtas från Internet. Om du vill blockera den här funktionen kan du också lägga till följande rad i arkivdelen ovan:
"packagist": falskt
Det är allt. Lokal spegel, med automatisk anpassning och uppdatering av paket. Dina kollegor kommer aldrig att behöva vänta länge medan de eller CI-servern installerar alla krav på dina projekt. Ha så kul.