Még mindig sok fejlesztőt látok PHP-s körökben debugger nélkül fejleszteni. Minden tiszteletem azoké, akik képesek hatékonyan dolgozni print_r() és var_dump() függvényekkel. Tudom, hogy sokaknak nehézséget okoz beállítani az Xdebug-ot pláne Docker segítségével konténerizált fejlesztői környezetben. Ebben a bejegyzésben szeretnék segíteni és megmutatni azt, mennyire egyszerű is egy ilyen beállítás.

Mire lesz szükségünk

Első körben szükség lesz egy olyan szerkesztő programra, amivel lehet PHP-t debuggolni. Erre számtalan megoldás létezik én most a VSCode programmal fogom bemutatni, amihez szükség lesz a Felix Becker által készített PHP Debug (felixfbecker.php-debug) kiegészítőre is.

A hiba, amibe mindenki belefut

Mielőtt nekilátunk fontos megértsünk egy dolgot, amit általában mindenki elhibáz az elején. Amikor debuggolsz a szerkesztőprogramod lesz a szerver, a webszerver pedig a kliens.

Mivan, mivan, mivan?

Igen, jól hallottad. A szerkesztő program fogja megnyitni a fejlesztő gépén az alapértelmezetten 9000-es portot és ott hallgatózik a bejövő kérésekre, majd a PHP futtató, azon belül az Xdebug próbál meg majd rácsatlakozni. Ez akkor, amikor XAMPP/MAMP egyikét használod nem probléma, mert az IDE és a webszerver is ugyanazon a gépen fut. Amint a kód egy távoli gépen vagy egy Docker konténerben fut, problémát fog jelenteni.

A szerver kapcsolódik a fejlesztő gépén nyitott 9000-s portra.

Tehát az egy dolog, hogy a fejlesztő eléri a távoli gépet, arra is szükség lesz, hogy a webszerver is el tudja érni a fejlesztő gépét. Ez viszont már korántsem olyan magától értetődő. Ezt majd tesztelni is fogjuk.

Minta alkalmazás

Készítettem egy pici minta alkalmazást, amivel csak azt fogod megtanulni, hogy hogyan lehet működésre bírni az Xdebug-ot. Ha ez már zökkenőmentesen megy, akkor érdemes egy már létező alkalmazás tesztelésébe kezdened.

Dockerfile

A hivatalos PHP image-et, annak is az apache-os verzióját használjuk. Ez az image 4 stage-ből áll. Az első a base, ami az alkalmazás futtatásához szükséges csomagokat tartalmazza. Ezután jön az Xdebug-base, ami az Xdebug kiegészítőt. Végezetül jön az Xdebug és a név nélküli alap stage, ami az alkalmazás kódját tartalmazza. Röviden a lényege ennek a felépítésnek, hogy a ritkábban változó nagyobb dolgokat igyekszünk előre rakni a Dockerfile-ban, hogy azokat gyorstárból tudja venni a builder. Ekkor az alkalmazásunk kódjának módosítása esetén csak az utolsó stage-et és azon belül is azt az egy fájlt kell csak beletennie a buildernek az image-be.

Jelen bejegyzés szempontjából csak az Xdebug-base stage az érdekes. Ennek első sorában telepítjük az ncat programot, mivel szükségünk lesz arra, hogy teszteljük, hogy a PHP és benne az Xdebug eléri-e az IDE-nket. Utána feltelepítjük az Xdebug aktuális verzióját, engedélyezzük azt, majd a szükséges beállításokat tartalmazó xdebug.ini fájlt bemásoljuk a megfelelő helyre. A conf.d könyvtár az, ahonnan a php értelmező felolvassa az összes ini fájlt amit talál.

FROM php:apache as base
#Install needed package
#RUN apt-get update && apt-get install -y ...
#RUN docker-php-ext-install ...
#RUN pecl install ...
#RUN composer install
#...

FROM base as xdebug-base
RUN apt-get update && apt-get install -y ncat
RUN pecl install xdebug \
 && docker-php-ext-enable xdebug
COPY xdebug.ini	/usr/local/etc/php/conf.d

FROM xdebug-base as xdebug
COPY index.php /var/www/html/index.php

FROM base
COPY index.php /var/www/html/index.php

Xdebug.ini

Ez az a pont, ahol könnyű hibázni. Ugyanis számos felhasználó esetet támogat az Xdebug. Én most egy fapados, Docker konténer számára kidolgozott megoldást mutatok, ahol az Xdebug mindig elindul és mindig megpróbál felcsatlakozni a szerkesztőre. Ne aggódj, ha az Xdebug kiegészítő be van töltve a script futása lassabb lesz akár engedélyezve van, akár nem. Az állandó engedélyezés maga nem jelent már jelentős lassulást. Ha nem akarod ezt a lassulást, akkor egyszerűen ne kapcsold be ezt a kiegészítőt és használd az alapértelmezett stage-et. Fontos, hogy a remote_connect_back beállítás false értéken legyen, mert az Xdebug csak ebben az esetben veszi figyelembe a remote_host beállítást. A remote_connect_back, bár alapértelmezetten ki van kapcsolva, az egyértelműség kedvéért bekerült a beállítások közé.

xdebug.remote_enable = on
xdebug.remote_autostart = on
xdebug.remote_connect_back = false
xdebug.remote_host = "host.docker.internal"

Végezetül a remote_host beállítás értékéről kell ejteni pár szót. A fenti megoldás ugyanis csak Windows és OSX rendszereken működik Linuxon nem. A Docker Desktop for Windows/OSX ugyanis sok-sok kényelmi funkción túl megoldja nekünk azt, hogy a host.docker.internal domain név a tényleges host ip címére legyen feloldva.

Ennek megoldásához Linuxon meg kell néznned, hogy a docker0 interface-nek mi az ip címe és azt kell beállítanod. Ezt az ifconfig parancs kimenetéből tudod kideríteni, de nagy valószínűséggel 172.17.0.1.

Ezután nem kell mást tenned Linuxon, mint a konténernél beállítani a következő környezeti változót (environment): XDEBUG_CONFIG="remote_host=172.17.0.1" lásd még következő fejezet

Composer fájlok

A docker-compose.yml fájl viszonylag egyszerű. Olyan sok mindent nincs mit magyarázni rajta.

version: "2.4"
services:
  web:
    build: .

Az override fájl már érdekesebb. Ez a fájl nincs is a repoban, csak egy .dev kiterjesztésű, mivel ezt a fájlt módosíthatjuk az igényeinknek megfelelően. Ez a fájl lesz az ami minden környezetben más lesz. Más lehet az egyes fejlesztőknél és más a CI/CD-nél is.

version: "2.4"
services:
  web:
    ports:
      - 80:80
# Settings for Xdebug
    build:
      target: xdebug
# Settings for Linux
#    environment:
#      XDEBUG_CONFIG: "remote_host=172.17.0.1"

Ha Linuxon vagy, akkor az environment két sorának elejéről kell kiszedned a megjegyzésre szolgáló # jelet. Ha nem szeretnéd, hogy az Xdebug lassítsa a rendszerünket akkor pedig a build két sorát kell kikommentezni. Akkor pedig, ha nem a 80-as portra szeretnénd kitenni az alkalmazásodat, akkor értelemszerűen a ports résznél tudod módosítani azt. Ne felejtsd el módosítás előtt leállítani és újra elindítani a rendszered.

VSCode launch.json

A mintaalkalmazás repojában van még egy fájl a rejtett .vscode könyvtárban. Ahhoz, hogy ez a fájl ténylegesen működjünk szükséged lesz a korábban említett Xdebug kiegészítőre.

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for Xdebug",
            "type": "php",
            "request": "launch",
            "port": 9000,
            "pathMappings": {
                "/var/www/html": "${workspaceRoot}"
            },
            "stopOnEntry": true,
            "log": true
        }
    ]
}

Ha mindent jól csináltál, akkor a Debbuger tab-on lesz egy “Listen for Xdebug” task, amit elindítva megjelenik a debugger bar. Ezek értelmezéséhez lásd az ábrát alant:

Nézzük akkor az opciókat. A name részt tetszőlegesen átírhatod, lehet mondjuk röviden Xdebug. A type és request értékeket ne módosítsd. A port értelemszerűen az a port ahol hallgatózni fog az IDE. A pathMappings beállítás fontos, hisz más útvonalon helyezkednek el a fájlok a konténeren belül, mint kívül. Erre figyelj majd, ha már létező projektbe próbálod meg bevezetni az Xdebug-ot. Itt természetesen több útvonalat is megadhatsz, ha esetleg a konténerben nem ugyanolyan hierarchiába kerülnek be a fájlok.

A stopOnEntry beállítást true-ra állítottam be, mivel most az alap működést teszteljük és biztos akartam benne lenni, hogy megáll a debugger. Ha a pathMapping értéket elrontotod, akkor hiába helyezel el töréspontokat (breakpoints) nem fog megállni a program futása. Ezért azt javaslom, hogy első alkalommal mindig legyen bekapcsolva ez a beállítás, amit értelemszerűen a későbbiekben kikapcsolhatsz. Legyen ez az első amit bekapcsolsz, amikor arra gyanakszol, hogy nem működik az Xdebug.

Itt kell még megemlíteni, hogy az Xdebug alapbeállításban minden nem várt eseménynél (Notices, Warnings, Errors, Exceptions) megáll. Ezt a debug Tab alján található Breakpoints résznél tudod kikapcsolni. A képen látható Everything opció elől kell kivenni a pipát.

Műxik?

Amennyiben minden jól megy, akkor a böngészőbe behozva az alkalmazást a debugger az index.php első sorában meg fog állni. Ugyanígy megáll majd, ha az index.php-t parancssorból futtatod:

docker-compose exec web php index.php

Ha nem megy, két helyen lehet probléma. Az egyik az, hogy az Xdebug nem éri el az IDE-t. Nézd meg, hogy fut-e a “Listen for Xdebug” task. Vizsgáld meg a “DEBUG CONSOLE” ablakát, hogy van-e hibaüzenet. Ezután a fejlesztői gépeden nézd meg, hogy a 9000-s port elérhető-e. Ehhez persze kell az ncat, vagy azzal egyenértékű program a fejlesztői gépen is:

nc -vz localhost 9000

Ennek hatására a “Listen for Xdebug” task össze fog omlani, le fog állni. Csak nyugodtan, indítsd újra. Most nézzük meg, hogy a konténeren belülről elérhető-e az IDE:

docker-compose exec web nc -vz docker.host.internal 9000

Figyelj oda, hogy a docker.host.internal csak OSX-en és Windows-on működik Linuxon ki kell derítened, hogy mi az. Ha a fentiek stimmelnek és nem kapcsolódik az Xdebug, akkor nézd meg, hogy jól van-e beállítva a php:

docker-compose exec web php -i | grep remote_host

Ha a fentiek közül minden jó és mégsincs kapcsolat, akkor keress meg, hogy bővíthessem ezt a leírást!

Ha megy a kapcsolódás és a debugger, de nem látszik melyik fájlban van a végrehajtás, akkor a pathMappings beállítást kell módosítanod és újraindítani a “Listen for Xdebug” taskot.

Remélhetőleg ezek után már könnyebben debuggolsz és hatékonyabban keresed a hibát a jövőben… vagyis inkább a programodban.

Ha érdekel, hogy hogyan építs fel és vezess be Docker konténerekre alapuló fejlesztőkörnyezetet, akkor iratkozz be a Konténerizált Fejlesztői Környezet kialakítása című képzésemre.