Függőségek bűvöletében

Attila | 2022. 06. 16. 18:51 | Olvasási idő: 5 perc

Címkék: #composer #Integráció (Integration) #Laravel #Laravel 9 #npm #PHP #Telepítés (Installation)

Nem kell persze semmi rosszra gondolni, csak azt szeretném bemutatni, hogy miket kell megtenni, amikor egy korábban használt projektünket készülünk újra elővenni, és emiatt felfrissítjük azt.
dependency-handling

Mivel az egyéb kötelességeim miatt régebben tudtam csak bejegyzést írni, ezért kicsit elavultak már azok a projektek, amiken dolgoztam korábban. Emiatt mindenképpen érdemes felfrissíteni ezeket a Laravel projektjeinket. Mit is értek ezalatt? Azt már tudjuk, hogy a webes világban szerencsére nem kell mindig "újra és újra feltalálni a kereket", ezért dolgozhatunk úgynevezett harmadik féltől származó osztálykönyvtárakkal, csomagokkal (3rd party libraries).

Emlékeztetőül: a weben mindig (legalább) két oldallal kell foglalkoznunk: a kliens és a szerver oldali kódjainkkal. Jó ezt feleleveníteni, mert a következő blogbejegyzésem a validációról fog szólni és ott is majd (legalább) ezt a két oldalt kell ellenőrizni... na de most még maradjunk a projekt felfrissítésénél.


Szerver oldal

A szerver oldali csomagjaink a projekt gyökerének vendor mappájában vannak, ezeket ugye manuálisan jobb nem piszkálni, ezért van nekünk a composer csomagkezelőnk, amely a frissítés megkezdésekor a composer.json fájlt veszi alapul akkor, amikor futtatjuk a composer update parancsot a terminal-ban. Ez azonban még csak a kiindulási pontja a műveletnek: először megnézi, hogy milyen harmadik féltől származó csomagokat használunk, majd megpróbálja letölteni az összes olyan csomagverziót, amiből talál frissebbet (csak akkor persze, ha a verziószám előtt ott van a ^ karakter, ami azt mondja meg a composer-nek, hogy "legalább ilyen verziószámú csomagot telepíts nekem"), a letöltés (downloading) után pedig következik a csomagok tényleges felfrissítése (upgrading).

  • Szerencsés esetben nem volt még túl régi a Laravel projektünk, és nem kell nagyobb csomagokat frissítenünk, vagy túl nagy verziószámot ugrani előre.
  • De minél nagyobb és szerteágazóbb a projekt és a függőségi struktúra, annál nagyobb a hibalehetőség (megjegyzés: valami hasonlóról szól a SOLID elvek közül a D-re utaló, függőség megfordítási elv vagy függőséginverzió alapelve: Dependency Inversion Principle. Ezt azért is érdemes alkalmazni, hogy ne történhessen meg, hogy egy olyan programcsomagra építjük a saját alkalmazásunkat, amit aztán később a készítője eltávolít, használatát letiltja stb.). Előfordulhat az is, hogy csak egy csomagot kell frissíteni, de az például 20-50-100 másik csomagtól is függ, amelyeket a projektünkben szintén frissíteni kell... mondanom sem kell, hogy minél több ilyen egymásra épülés van, annál nagyobb az esélye annak, hogy valaki, valahol hibázott és emiatt nem tudjuk mi frissíteni a csomagokat.
  • Ha mondjuk a fő keretrendszerünk, jelen esetben a Laravel fő verzióját is frissíteni szeretnénk, akkor az még több galibához vezethet. Például, amikor a 8-asról a 9-es frissítettünk korábban, akkor a PHP verzióját is frissíteni kellett, ami még további problémákat generálhat a számunkra. Azért a fő verziószám váltáskor a Laravel dokumentációja is szokott segíteni, hogy miket kell megváltoztatni a rendszerben, például itt van egy részletes útmutató az említett fő verziószám váltásról: https://laravel.com/docs/9.x/upgrade Itt pedig egy kis vidámság a PHP és a kapcsolódó Laravel keretrendszer gyönyörűségeiről, továbbá a verzióváltásaikkal kapcsolatos "gyötrelmekről":

  • A Laravel fő verzióváltásáról (8 --> 9) én is írtam már korábban itt.
  • A napokban belefutottam abba is, hogy a Microsoft Azure a PHP verziók közül a 8.0-t támogatja még csak (a legfrissebb 8.1-es helyett), a 8.0 pedig néhány csomag legfrissebb verziójának már kevés, vagy alacsony. Így ezeket a csomagverziókat a composer.json fájlban csökkenteni kell (downgrade), kisebbre kell átírni. Konkrétan nálam ez a symfony csomagok verziószámának csökkentését igényelte, a symfony/finder például a legfrissebb 6.1 helyett csak a 6.0-s fut a PHP 8.0-s verzióján.
  • Nevezhetjük ezeket a problémás eseteket is "függőségi pokol"-nak, főleg ha ide-oda kell ugrálni a verziószámok között, amíg megtaláljuk az arany középutat, ami már mindennek és mindenkinek megfelelő lesz... Szerencsére van egy weboldal, ami segít könnyedén keresni a csomagok között, verziószámokat jelöl nekünk és megmondja, hogy milyen más csomagoktól is függ az az adott csomag és verziója...

Itt van például a nesbot/carbon csomag, amely jelenleg 281 különböző nyelven támogatja a dátumok és idők kezelését, úgyhogy ezt aránylag sokszor alkalmazhatjuk a projektjeinkben (esetleg nem is tudunk róla, hogy benne van már a Laravel projektünkben): https://packagist.org/packages/nesbot/carbon

Látható, hogy a legfrissebb stabil (!) verzió az a 2.58.0-s, de emellett a hármas verzióból van már fejlesztői változat is. Az oszlopokban bal oldalon látható, hogy milyen futtatókörnyezetet (PHP verziót) és csomagokat igényel mindenképpen éles környezetben, utána lévő oszlopban pedig a fejlesztői környezetes igényelt csomagjait is láthatjuk. A jobb oldali oszlopban, ha esetleg "el kell indulnunk visszafelé" (downgrading) a csomag verziószámaiban, kattinthatunk, válthatunk és akkor szintén mutatja nekünk, hogy épp az a kiválasztott verzió milyen más dolgokat igényel. Szóval ez packagist weboldal rettentő hasznos tud lenni a munka során.


Kliens oldal

A kliens oldali párjai mindezeknek a node_modules mappában találhatók meg. Itt az npm csomagkezelő lesz a segítségünkre, aminek a kiindulási pontja a package.json fájlunk lesz. npm update utasítás kiadásával szépen felfrissülnek a kliens oldalt támogató csomagjaink... ha minden szép és jó az életben. De azért ez elég ritka, úgyhogy bőven előfordulhat, hogy belefutunk olyan verziókülönbségekből adódó problémákba, amelyek megnehezíthetik a dolgunkat. Ilyenkor a package.json fájlban kezdünk el praktikusan turkálni (egy nagyon javasolt probléma- és megoldáskeresés futtatás után), és a verziókat próbáljuk meg megfelelően beállítani azért, hogy hiba nélkül lefuthasson a csomagok frissítése. Na de milyen verziókat is állítsunk be azért, hogy menjen minden, mint a karikacsapás? Ebben nagy segítségünkre van egy weboldal, amivel könnyedén tudunk keresni csomagokra és a kiválasztás után látszódik, hogy milyen verziószámmal dolgoznak aktuálisan, emellett ő maguknak milyen egyéb függőségeik vannak... jó mélyre is áshatunk, attól függően, hogy mekkora galibába kerültünk bele, de én mindenkinek a legjobbakat kívánom és persze azt, hogy sikerüljön minden ütközést feloldani. A kliens oldali függőségek rengetegében is van egy segítő weboldalunk: https://www.npmjs.com/

Itt is, ha ki szeretnék emelni egy példát, akkor a React osztálykönyvtárra hívnám fel a figyelmet: https://www.npmjs.com/package/react Az oldal röviden bemutatja nekünk a csomagot, megmondja, hogy hol található a repo-ja, weboldala, mi a legfrissebb verziószáma és még egy alap működtetést is bemutat nekünk. Amire fel szeretném hívni a figyelmet ennek kapcsán, az a felső tab-os navigációban látható: jelenleg 1 csomagtól függ a React, ellenben tőle 88 127 egyéb másik csomag függ, amelyeket mind böngészhetünk... jó nagy szám! Általában nekem az volt mindig a benyomásom, hogy a kliens oldali függőségek sokkal mélyebbek, szerteágazóbbak, mint a szerver oldaliak, de lehet ez csak egy amolyan megérzés a részemről... ha valakinek van cáfolata, azt szívesen fogadom.

Ha pedig már mindent IS frissítettünk (webszerver, PHP, MySQL, PHPMyAdmin, Laravel, külső csomagok stb.), akkor esetleg elveszhetünk az összes korábbi, ámde majd további "újraélesztést" igénylő projektünk felfrissítésébe... ez tényleg egy függőségi pokol már.

De aki itt elveszne, azt csak biztatni tudom, hogy legalább nem kell még React Native-ot használnia (mobil alkalmazásfejlesztéshez), aminél aztán tényleg elszabadul minden, és ami egy nappal korábban még működött, az másnapra már nem biztos, hogy fog, pedig nem változtattunk a saját kódunkon semmit, csak épp a függőségek már mást és másképp igényelnek...


Friss, ropogós, valós életbeli példa - avagy hogyan érjük el és módosítsuk az adatbázist kívülről, ha csak a programkódon keresztül van hozzáférésünk...?

Egy kis "nindzsázást" tartogattam a bejegyzés végére, ugyanis a blog oldalam új tárhelyéül szolgáló szerveren is frissült a PHP verziószáma, aminek okán már tudtam frissíteni a Laravel-t is a legfrissebb verzióra, ezért is kapcsolódik ez a mostani blogbejegyzéshez.

A frissítés során, természetesen akadt egy olyan probléma, hogy sajnos az adatbáziskezelő szerver elérése kívülről még nem biztosított, ami alapból nem is lenne probléma, viszont a sok csomagfrissítés miatt bekerült egy olyan bővítés is a blog projektembe, hogy egy plusz oszlopot is igényel az egyik tábla, aminek a kitöltése kötelező, anélkül nem megy... mit lehet ilyenkor tenni, ha sehogy sem tudjuk elérni az adatbázisunkat? Erre mutatok egy megoldási javaslatot a továbbiakban.

Nyilván a migrációs fájlok lehetnek a segítségünkre, azonban, amikor azt mondtam, hogy sehogy sem érjük el a távoli szerveren lévő adatbázist még, akkor azt tényleg komolyan gondoltam (se PHPMyAdmin, se Workbench kapcsolat, se terminal-os / CLI-s elérés, semmi...). Tehát hiába hozzuk létre mi szuperül a migrációs fájlunkat, nem fogjuk tudni futtatni a távoli szerver termináljában a php artisan migrate parancsot. Nekem egy QueryException-t dob a rendszer, mivel azt írja, hogy a driver nem található...

Ki kellett tehát találni, hogy hogyan lehet Artisan parancsot futtatni a PHP kódunkból. Erre szerencsére van megoldás, amit már használtam is korábban egy másik munkám során, amikor a beállítási paramétereket kellett frissíteni és egyben el is tárolni a cache-ben, ami így nézett ki:

use Illuminate\Support\Facades\Artisan;
...
Artisan::call('config:cache');

Egy importálás után a "php artisan" részt leszámítva kapja meg az Artisan call statikus metódusa paraméteréül a végrehajtandó utasítást. A kérdés csak az, hogy melyik fájlba is tegyük mindezt ahhoz, hogy végrehajtódjon...? Én azt javaslom, hogy az app / Providers / AppServiceProvider.php boot metódusába helyezzük el az Artisan call utasítást, mert az ott biztosan végre fog hajtódni, amikor beindítjuk az alkalmazást (de egy másik, teljesen jó módszer lehet az is, hogy a web.php-ban egy útvonalhoz rendeljük a hívást, és azzal indítjuk be az artisan parancs lefutását).

Ha a kapcsolódó migrációs fájlom a helyén van, akkor azt gondoltam, hogy már csak a fenti példa alapján végrehajtandó migrate parancsot kell "futtatnom" (például oldallekéréssel végrehajtatnom), azonban ez nem teljesen volt így... mivel ugye élesben dolgozok, tehát "production" a környezet, ezért amikor kiadnám terminal-ból ezt a php artisan migrate parancsot, akkor a rendszer visszakérdezne, hogy biztosan végre akarom-e hajtani a migrálást éles környezetben, amire válaszolhatnék igennel (yes) és nemmel (no) is. Itt viszont most nincsen esélyem ilyen "párbeszéd lefolytatására". Úgyhogy egy kicsit trükközni kell: hozzá kell adni egy --force kapcsolót, ami a visszakérdezést megelőzi és rögtön végre is hajtja a migrációt. Azonban ez még mindig nem elég, mivel két további beállítást is át kell adni az Artisan call metódushívásnak: 1. meg kell adni neki az útvonalat, hogy milyen útvonalon hajtsa végre a kód lefutását (alapból a terminálból egyértelmű lenne, azonban itt fel kell paraméterezni, hogy a database mappa migrations almappájában tegye ezt meg a --path kapcsoló segítségével), illetve 2. azt is meg kell adni neki, hogy melyik adatkapcsolatot (--database kapcsoló) használja a migráláshoz, nálam ez a "mysql" nevű kapcsolat lesz. Végül tehát így néz ki az a kód, amit az AppServiceProvider osztály boot metódusába elhelyezek:

Artisan::call('migrate', array('--force' => true , '--path' => 'database/migrations', '--database' => 'mysql'));

A paraméterek sorrendje nem lényeges a tömbben. További információ itt található az artisan parancsok programozott végrehajtásáról és paraméterezéséről: https://laravel.com/docs/9.x/artisan#programmatically-executing-commands

Ezután, ha egy sima oldal újratöltés nem lenne elég a migráció végrehajtásához, akkor futtassuk ezt az utasítást a terminal-ban:

composer dump-autoload

Amit azért adunk ki, mert a Composer nem fogja látni az új migrációs fájlunkat/osztályunkat automatikusan, hanem ezzel újrageneráljuk neki azt a listát, amit ő az osztályainkról számon tart és közben észreveszi, hogy "Jé, van itt valami új dolog, gyorsan fel is veszem a megfigyelési listámra!", betöltés után pedig már indítja is az általunk elhelyezett Artisan call parancshívást. Remélhetőleg ez gyorsan végrehajtódik, amit ugye most nehezen lehet ellenőrizni, de nálam lefutott egy olyan képfeltöltés, ami korábban az itt leírt problémát generálta: egy mező meglétét hiányolta egy adattáblából, és pont emiatt hoztam létre a migrációs fájlt, hogy ezt beletegyem... mivel a képfeltöltés most már hiba nélkül megtörtént, ezért arra a következtetésre jutottam, hogy a migráció sikeresen végrehajtódott. Végül, de nem utolsó sorban, ne felejtsük el kivenni az Artisan call hívást az AppServiceProvider-ből, mivel nagy valószínűséggel nem szeretnénk minden oldal betöltődéskor újrafuttatni éles környezetben a migrációt...


Saját tapasztalatok?

Szívesen fogadok saját tapasztalatokat is a "függőségi pokoljárásokról" (szigorúan a témához illeszkedően!), úgyhogy ha van valamilyen egyéni megoldásotok, amelyet egy régi projekt életre keltésekor, felfrissítésekor kellett megoldanotok, akkor jöhetnek a kommentek a kapcsolódó Facebook vagy Linkedin bejegyzésem alá.

A következő bejegyzésben már ismét tudunk a fő fejlesztési irányvonalunkra koncentrálni, hiszen egy teljesen felfrissített projekt lesz a kezeink alatt.