Még 2009 októberében írtam cikket Hibakeresés PHP-ban címmel. Ott főként elvi megoldásokról és a forráskód megváltoztatásával történő hibafelderítésről volt szó. Kézi beavatkozást igényelt és azt, hogy el ne felejtsük a végén törölni a módosításokat.
Van azonban egy jóval hatékonyabb módszer is, amit ugyan megemlítettem már akkor is, de ebben a fejezetben konkrétan az XDebug telepítését, konfigurálását és használatát mutatom be a jelenleg általam preferált NetBeans segítségével.
Tartalomjegyzék
Előszó
Miért is jó ez? Egy egyszerűbb forrásnál hatékony lehet az is, ha csak teletűzdeljük kiírásokkal. Így nézzük meg a változók értékét, hogy pontosan olyanok-e, mint amilyennek lenniük kell. Belefut-e egy ciklusba a program.
Ehhez viszont egy részt mindig tudni kell, hogy egy függvény pontosan hol van definiálva, ha abban is szeretnénk hibát keresni. Kutathatunk a fájlokban, ami lassú és fárasztó munka.
Az ilyen hibakereséssel egy idő után annyi kiírás gyűlhet össze, hogy aztán már az viszi el az időt, amíg megtaláljuk, hova tettük be azt a fránya
részt. Vagy mindig csak egy sorban írunk ki valamit, majd elfelejtjük az előző kiírás eredményét és meg kell ismételni.
Igaz, hogy XDebuggal sem látjuk folyamatosan az összes változót, csak azt, ami adott hatókörben elérhető. Nem kell viszont kiírásokkal bajlódni, tetszőleges helyre letehetünk töréspontokat, ahol az XDebug megszakítja a program futását. De futás közben is dönthetünk úgy, hogy egy előre nem megjelölt ponton állítsuk meg a programot. Akár sorról sorra vizsgálódhatunk és eközben a program változatlan marad.
Ha valaki mégis a kiírások mellett döntene, a var_dump függvény kimenetét is módosíthatja a program. Így egy sokkal szebb formában jelennek meg a változók, akár tömbök értékei.
Nem csak megnézhető a változók értéke, de meg is változtatható az értékük a memóriában. Így akár közbe is lehet avatkozni futásidőben, ha más értékekkel látjuk jobbnak a tesztelést. Akár más konfigurációval. Írhatunk rövid programkódot is, ami az aktuálisan elérhető adatokkal dolgozik és annak értékét is figyelhetjük. Ehhez sem kell az eredeti forrást bántani. És még a hívási vermet is láthatjuk.
Hogyan működik? Egy debugger kliens, ami jelen esetben a NetBeans, meghatározott porton információkat vár a webszervertől. Pontosabban az azon levő PHP-tól. Ezeket az üzeneteket a telepített XDebug küldi el, majd felfüggeszti a PHP értelmezését, amíg a klienstől utasítást nem kap a folytatásra. A kapott üzenetekből a kliens megjelenítheti a változókat, vagy egyéb, hasznos információkat egy felhasználóbarát felületen. Mi pedig az alapján döntünk a folytatásról.
A kliensnek és a szervernek egyaránt birtokában kell lenni ugyanannak a forráskódnak. És amit jó tudni, hogy ha a szerveren a forrás fizikailag több különböző helyről van egy könyvtárba linkelve, akkor a NetBeans egy ilyen linkelt fájlnál nem tudja beazonosítani a töréspontokat és vakok maradunk.
Telepítés
Ezt is többféleképpen lehet telepíteni. Csak az egyiket fejtem ki bővebben. Az XDebug telepítésről szóló oldalán viszont szépen le vannak írva a lehetőségek.
Forráskód letöltése és fordítás
A download oldalán be lehet szerezni a legfrissebb forráskódot, majd a már sokat emlegetett configure, make, make install hármassal telepíthető is.
Ehhez nyújt segítséget a telepítés varázsló, ami a phpinfo() parancssori kimenetét várja. Abból pedig leírja, milyen lépéseket kell a saját környezetünkben elvégezni.
Windowshoz előre fordított dll fájlt is le lehet tölteni.
Telepítés pecl-lel
A PHP-hez tartoznak különböző segédprogramok. Ha a PEAR-t telepítettük a PHP-hez, akkor kapunk egy "pecl" nevű programot is, amivel egyszerűen feltelepíthetjük az adott PHP verzióhoz az XDebug-ot. Ez az, amit javaslok, és amit én is használok.
Ha a következő hibaüzenettel találkoznánk:
ERROR: `phpize' failed
akkor telepítsük fel az autoconf-ot!
Majd futtassuk újra a telepítőt! Ha a telepítés végén kiírja, hogy:
You should add "zend_extension=xdebug.so" to php.ini
Akkor a php.ini végére kézzel kell beírni az idézett részt. Pontosabban ha biztosak akarunk lenni a sikerben, akkor a teljes útvonalat az xdebug.so -ig. Ezt a fent idézett sorok felett ki is írja a telepítő. De a
utasítással rá is kereshetünk. Így minimum a következő kerüljön be a php.ini végére:
zend_extension=/opt/php/5.4.14/lib/php/extensions/no-debug-non-zts-20100525/xdebug.so
Ha a szerver újraindítása után a phpinfo() kimenete tartalmaz egy "xdebug" szekciót, akkor a beállítás sikeres volt.
Hibakeresés kiírásokkal
A var_dump függvény kimenetét csak akkor szépíti meg az xdebug, ha az xdebug.overload_var_dump az xdebug szekcióban 1-re van állítva. Ez az alapértelmezett beállítás. Viszont a "html_errors" -nak is engedélyezve kell lennie még a php.ini -ben.
Ha az overload nincs engedélyezve, az xdebug_var_dump()
függvény használható helyette.
Ezen kívül a kezeletlen kivételeket is formázva jeleníti meg, ha a html_Errors engedélyezett.
Valamit ez is segít, de ez csak egy fokkal jobb, mint az eredeti formázással történő kiírás.
Remote debug
Bár én NetBeans-t használok, vannak más szerkesztő programok, amik támogatják az xdebug-ot. Ezek listáját az XDebug Remote Debug oldalán lehet olvasni.
Kommunikáció szerver és kliens között
A szervernek tudnia kell, hova kell küldenie a debug információkat. A kliensnek pedig tudnia kell, hogy milyen porton fognak érkezni ezek az információk. A NetBeans-ben a Tools » Options » PHP » Debugging fülön lehet beállítani a portot.
Van még néhány beállítás a PHP ini-ben.
xdebug.remote_enable
Ezt mindenképp 1-esre kell állítani, hogy engedélyezzük a debug funkciót.
xdebug.remote_port
Opcionális. Alapértelmezett a 9000-es port. A kliensnek ezen portjára küldi az xdebug az információkat.
xdebug.remote_host
Opcionális. Alapértelmezetten localhost. Erre a hosztra küldi az xdebug a megadott porton az információkat. A virtuális gépünk viszont egy külön gépnek számít. Így ott meg kell adni a gazda gép IP címét. Lehet ez belső hálózati IP, vagy a világ felé látszó. Utóbbi viszont nem biztos, hogy állandó.
xdebug.remote_connect_back
Ha ezt 1-esre állítjuk, akkor az xdebug megpróbálja arra az IP-re küldeni a debug infókat, amelyikről a kérés érkezett. Ez a virtuális gépünknél host-only hálózat esetén a 192.168.56.1. Ezen az IP-n viszont nem lehet megszólítani a gazda gépet. Így kénytelenek vagyunk a fix IP megadásánál maradni.
xdebug.idekey
Szokták javasolni, hogy állítsuk be az idekey-t arra, ami a NetBeans-ben van a portbeállítás alatt Session ID néven. Ez viszont nem befolyásolja a debug sikerességét. A kliensszoftverben beállított session azonosító arra jó, hogy a kliens csak azokkal a küldött adatokkal foglalkozzon, amik az ő azonosítójával lettek elküldve. Ezt tehát üresen lehet hagyni.
A debug akkor indul el, ha az XDEBUG_SESSION nevű session létezik. Ezt úgy állítjuk be, hogy az URL-ben elküldjük az XDEBUG_SESSION_START=netbeans-xdebug paramétert. A NetBeans pontosan ezt küldi el. A netbeans-xdebug helyett más debuggereknél más lehet.
A beállítások helye
A beállítások helye a php.ini, ahogy azt eddig is írtam. Ezen kívül viszont htaccess-ben is lehet a php_value és php_flag opciókkal módosítani a php.ini értékeit, ha nem PHP-FPM -mel fut a PHP. Ha viszont FPM-et használunk, akkor annak konfigurációs fájljában lehet hasonló beállításokat végezni.
Ami fontos, hogy az összes olyan PHP verziónál telepíteni kell az xdebug-ot és elvégezni a beállításokat, amelyekben szükségünk van az xdebug-ra. Az 5.4.14-es verzión mutattam be a lépéseket, de ugyanez a módszer a többinél is. Az xdebug.so fájl útvonala, ami változó.
XDebug a gyakorlatban
Nézzünk egy egyszerű példát az xdebug használatára a következő forráskóddal:
Kerüljön például a letöltött és kitömörített tar fájl tartalma a "/var/www/vhosts/vm1/a22/www/xdebug" mappába! Nyissuk meg NetBeans-ben, mint új projekt létező forrásból (File » New Project » PHP » PHP Application with Existing Sources).
A legjobb, ha Samba szerver beállításánál írt megosztáson keresztül vagy SSHFS-sel egyenesen a szerveren nyitjuk meg a projektet. De az is elég, ha projekt forrása megegyezik a szerveren levővel. Ekkor viszont belemódosítani nehezebb.
A példában 3 fájl van. Az index.php -ben egy űrlap van egy select listával. Választás alapján a program vagy egy ciklust futtat a ciklus.php-ben, vagy megállítja a programot exit-tel, vagy kivételt dob. Üres érték választásakor pedig kiírja, hogy nem tudja, mit tegyen. De nem is ez a lényeg.
Az index.php-t megnyitva a szerkesztő ablakban a sorszámokat mutató baloldali oszlopban kattintsunk egérrel az if sorára (4. sor). Ezzel kijelöltük a töréspontot, ahol a program meg fog állni.
Most pedig a CTRL+F5 billentyűkombinációval, vagy a projekt nevére jobb egérgombbal kattintva a "Debug" -ot választva, vagy a felső menüsávban a "Debug" menüben a "Debug project" -re kattintva indítsuk el a debug funkciót!
Az aktuális, vizsgált sor mindig zölden van kiemelve. Debug közben a felső gombsor megváltozik.
- Debug befejezése (SHIFT+F5): Törölteti a debug sessiont a NetBeans a böngészővel.
- Folytatás (F5): Folytatja a program futtatását. Ha van következő töréspont, amire a program is ráfut, akkor ott áll meg legközelebb. Ha nincs, akkor a Debug befejezése ikonon kívül minden ikon inaktív lesz a program következő futtatásáig vagy a debugolás befejezéséig, amikor eltűnnek ezek az ikonok.
- Következő sor futtatása (F8): Folytatja a program futtatását a következő sorig, majd ismét megáll, mintha oda is töréspontot tettünk volna.
- Belépés függvénybe (F7): Ha épp egy függvényhívás során áll a program, akkor beleugrik a függvény törzsébe. A NetBeans megnyitja a függvényt tartalmazó forrásfájlt, ha tudja, és a függvény első értelmezhető sorára áll.
- Kilépés függvényből (CTRL+F7): Kilép a függvényből, ha abban nincs további töréspont, amire a program ráfut, Majd megáll a függvényhívást követő első értékelhető sornál.
- Ugrás a kurzor pozíciójára (F4): Amelyik sorra tesszük a szerkesztőben a kurzort, ha a program ráfut, megáll.
Alul pedig a "variables" ablakban látható az éppen elérhető változók listája és értékük. Most töröljük az előző töréspontot az újra rákattintással, vagy a "Breakpoints" ablakban jobb egérgomb, Delete-tel, és tegyünk új töréspontot a ciklus.php-ben a ciklusmagba az echo sorára. Majd frissítsük a böngészőben a weblapot! Az űrlapon válasszuk ki a "ciklus tesztelés" -t és nézzük meg a NetBeanst ismét munka közben.
Alul látható az $i változó értéke és típusa is. Utóbbi akkor jöhet jól, ha egy program típusfüggő. Láthatjuk még az $optname változót is a listában, pedig a forrásban nem szerepel. A program adott pontján viszont elérhető, mivel az xdebug.php -ben levő metódusban létezett. A ciklus.php pedig ott lett meghívva. Nyomjunk kétszer F5-öt! Az $i változó értéke rááll 3-ra. Az értékre kattintva írjuk át a 3-ast 9-esre, majd nyomjunk ismét kétszer F5-öt! Ekkor a weboldal kimenetén csak az 1-es, 2-es, 9-es és 10-es lépések kiírását láthatjuk csak, mivel a többit átugrottuk a forrás változtatása nélkül.
A NetBeansben ott, ahol a portot is állítani lehetett, jelöljük be a "Watches and Balloon evaluation" opciót. Ekkor egy figyelmeztetés felugrik, hogy ezzel nem működik tökéletesen az xdebug. Azért csak próbáljuk ki.
Ismételjük meg a ciklus futtatását az előző módon. De most a töréspontra ugráskor kattintsunk a "Watches" fülre alul a "Variables" mellett, majd jobb egérgomb, "New Watch". Írjuk be a következőt:
Menjünk vissza a "Variables" fülre! Ott is látható a bevitt kifejezés. Majd nyomjunk F5-öt
Látható, hogy a kifejezés értéke 4. Ha folytatjuk a program futtatását, kettesével csökkenni, majd ismét nőni fog. Nekem még nem volt szükségem erre a funkcióra, de jó lehet, ha egy összetett adathalmazból, tömbből vagy objektumból kell egy érték. Így nem kell kinyitogatni és keresgélni azt.
Ha pedig a "Call stack" fület nyitjuk meg, követhető a program útja. Milyen fájlokon, mely függvényeken keresztül jutott az aktuális pontig.
XDebug FPM-mel
FPM-mel használva az XDebugot, van, amire oda kell figyelni. Azon túl, hogy a virtuális gépen el kell indítani az fpm-et, van egy időkeret is. Debug közben a szervernek küldjük el a kérést, ami átadja azt az FPM-nek. A szerver viszont rövid időn belül eldobja a kapcsolatot. És bár a program lefut, a szerver már nem várja meg és a böngészőben "Internal Error" üzenetet kapunk.
A megoldás 2.2-es Apache HTTPD -nél, hogy a FastCGIExternalServer sorának végére írjuk az "idle-timeout" opciót egy magasabb értékkel másodpercben számítva. Például 1 órát írhatunk.
Ezt a 2.2-es Apache szerver minden olyan virtuális hosztján meg kell adni, amelyik FPM-mel működik és számítunk a hosszabb debugolásra.
A 2.4-es Apache szervernél FCGID-t használtunk mod_proxy-val. Így annál a proxy timeoutját kell megnövelni az alábbi módon:
ProxySet timeout=3600
</Proxy>
Ellenkező esetben itt a "Service Unavailable" üzenet fogad. A Proxy tag-ben a protokollt, IP-t és portot kell csak megadni. Az útvonalat nem.
Összefoglalás
Most már aztán tényleg nincs ok panaszra a hibakeresés körül. Nem csak a hagyományos, fapados, kiírásos módszert szépítettük meg, hanem akár távoli szerveren is képesek vagyunk a forráskód változtatása nélkül belenézni a program lelkébe. Végigkövethetjük minden lépését a programnak és még el is téríthetjük.
Az FPM-mel egy fokkal problémásabb a debugolás, de egy apró konfigurációval ezen is lehetett segíteni. Az 5.4.14-es PHP-hoz leírt módon a többi PHP-hoz is fel kell telepíteni az XDebug-ot, ha használni szeretnénk. A telepítésük nem bonyolult, ahogy a beállításuk sem. De figyelni kell az aktuális környezetünknek megfelelő adatokra. Úgy, mint a gazda gép IP-je.
Miután minden verzióhoz telepítettem az XDebug-ot, elmentem a virtuális gépet "wtk-vm1-v13-xdebug" néven. Így már az egyes komponensek kézi indítását leszámítva egy komplett teszt szervert állítottunk össze.