Auf Wunsch hier nochmal eine kleine Anleitung wie man Xdebug in einem Docker-Container zusammen mit PHPStorm verwendet. Ich hatte ja schon eine Anleitung für VSCode geschrieben und dort am Ende angemerkt, dass es mit PHPStorm sehr viel einfacher geht. Vorhin nochmal getestet und es geht sehr viel einfacher.
Aber von Anfang an. Wir müssen erstmal das Docker-Image um Xdebug erweitern:
RUN pecl install xdebug-2.8.0 && docker-php-ext-enable xdebug
RUN echo 'zend_extension="/usr/local/lib/php/extensions/no-debug-non-zts-20151012/xdebug.so"' >> /usr/local/etc/php/php.ini
RUN echo 'xdebug.remote_port=9000' >> /usr/local/etc/php/php.ini
RUN echo 'xdebug.remote_enable=1' >> /usr/local/etc/php/php.ini
RUN echo 'xdebug.remote_host=host.docker.internal' >> /usr/local/etc/php/php.ini
Dann installieren wir uns "Xdebug Helper" für den Chrome oder eben die Kombination aus Addon und Browser, die man gerne hätte.
Dann den Docker-Container starten. Die Seite im Chrome aufrufen und dort das Addon aktivieren. Einen Breakpoint in PHPStorm setzen und PHPStorm auf Debug Eingänge horchen lassen.
F5 im Webbrowser drücken und dann das Mapping bestätigen.
Das wars. Super einfach und schnell (abgesehen vom Docker-Container bauen) eingerichtet.
Wenn man Code-Coverage bei Unittests mit PHP haben will benötigt man extra Erweiterungen, wie z.B. XDebug. XDebug ist aber sehr langsam und deswegen gibt es Alternativen wie PCOV. Wenn man das nun installieren will kann es zu Problemen bei "docker-php-ext-install" auf den offiziellen PHP-Docker-Images kommen. Um es dort zu installieren, muss man
pecl install pcov && docker-php-ext-enable pcov
ausführen. Dann funktioniert die Installation und es steht für Unit-Tests zur Verfügung.
Es war ein harter Kampf.. aber ich war siegreich. Nachdem ich durch den Wechsel meines Arbeitgebers auch den Zugriff auf Intellij + PHP-Plugin einbüßen musste, bin ich jetzt vollständig auf VSCode gewechselt.
Wie ich XDebug + VSCode + Docker zum Laufen bekommen habe.
Dockerfile (nur der wichtige Teil):
RUN pecl install xdebug-2.8.0 && docker-php-ext-enable xdebug
RUN echo 'zend_extension="/usr/local/lib/php/extensions/no-debug-non-zts-20151012/xdebug.so"' >> /usr/local/etc/php/php.ini
RUN echo 'xdebug.remote_port=9000' >> /usr/local/etc/php/php.ini
RUN echo 'xdebug.remote_enable=1' >> /usr/local/etc/php/php.ini
RUN echo 'xdebug.remote_host=host.docker.internal' >> /usr/local/etc/php/php.ini
Lokale php.ini (ich habe 2.8.0 herunter geladen und als php_xdebug.dll gespeichert):
zend_extension=c:\php\ext\php_xdebug.dll
#.....
[XDebug]
xdebug.remote_enable = 1
xdebug.remote_autostart = 1
Dann habe ich dieses Plugin in VSCode installiert. Mit Hilfe dieses Boilerplate-Projekts habe ich mir eine passende .vscode/launch.json gebaut.
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for XDebug",
"type": "php",
"request": "launch",
"port": 9000,
"pathMappings": {
"/var/www/html": "${workspaceFolder}/"
},
"xdebugSettings": {
"max_data": 65535,
"show_hidden": 1,
"max_children": 100,
"max_depth": 5
}
}
]
}
Dann VSCode neugeladen. Docker gestartet und in die Debug-Ansicht von VSCode gewechselt.
Dort das ""Listen for XDebug" Profile starten und es sollte funktionieren.
PHPStorm/Intellij ist da viel einfacher, weil man nicht selbst noch JSON-Dateien anlegen und editieren muss. Aber es läuft!
sudo apt-get install nano
Bei der Arbeit habe ich Ubuntu 18.04 mit ZFS installiert. Ich mag VI so was von nicht.. nano ist so etwas wie "edit" unter DOS in besser.
Es klingt sehr viel komplizierter als es man Ende ist. Das Wichtigste ist es erst einmal XDebug auf der VM zu installieren. Ich habe da PHP7.1 und es erklärt sich an sich von selbst:
apt-get install php7.1-xdebug
Danach muss die /etc/php/7.1/mods-available/xdebug.ini bearbeitet werden. Wenn man nun davon ausgeht, dass die VM mit VirtualBox und einem Host-only Netzwerk in den Standard Einstellungen läuft ist der Host 192.168.56.1 . Die Datei sieht dann also so aus:
zend_extension=xdebug.so
xdebug.remote_enable=1
xdebug.remote_host=192.168.56.1
xdebug.remote_port=9000
Bei mit zeigt shopware.localhost auf die VM und auf eine Shopware-Installation. Einen Breakpoint habe ich ganz an den Anfang von der shopware.php gesetzt.
Im Firefox verwendet ich das Addons Xdebug Session Cookie. PHPStorm hört auf Port 9000 nach Debug-Verbindungen. Wenn man nun shopware.localhost aufruft und das Addon aktiviert, wird auch sofort in PHPStorm dieses erkannt und er stoppt beim ersten Breakpoint, den man gesetzt hat.
Ist also in wenigen Minuten machbar, das für seine lokale Umgebung einzurichten.
Etwas komplexer ist es mit SSH und Portweiterleitungen, was aber ermöglicht ganz sicher ein Live- oder Staging-System zu debuggen und man muss nicht auf dem Server mit VI und echo/var_dump arbeiten, um heraus zu finden warum es zwar lokal super lief, aber nicht auf dem Server, obwohl die an sich total gleich sind. Kennt jeder.. oder?
Irgendwann kommt der Zeitpunkt, da ist eine Anwendung langsam. Es liegt nicht am Datenbankserver oder der Netzwerkanbindung oder der Auslastung des Servers. Es liegt einfach ganz allein daran, dass die Anwendung langsam ist.
Oft findet man einige Dinge von selbst heraus. Aber oft ist man einfach überfragt in welchen Teilen der Anwendung die Zeit verloren geht. Was braucht lange? Werden einige Dinge unnötig oft aufgerufen? Zu viele Dateisystem-Zugriffe?
Hier hilft dann nur noch ein Profiling der Anwendung. Profiling ist einfach die Anwendung eine Zeit lang zu überwachen und zu protokollieren, wie viel Zeit in der Zeit auf welche Methoden oder Funktionen verwendet wird.
Das alleine sagt natürlich erstmal nicht wo Probleme vorhanden sind. Deswegen halte ich die Idee ein separate Team solche Performance-TEst durch zu führen und zu analysieren für nicht ganz so zielführend. Denn manchmal brauchen einige Methoden viel Zeit. Da man Zeit sowie so meistens nur in Verhältnis der Methoden zu einander betrachtet muss man wissen was schnell sein soll und was langsam sein sollte oder darf.
Ich hatte mal bei Bouncy Dolphin das Problem, dass alles an sich ganz schnell lief, aber beim Profiling auf eine Methode fast 40% der Zeit ging, die nur den aktuellen Punktestand auf das Canvas zeichnete. Nach viel hin und her Probieren kopierte ich den Inhalt eines Canvas mit dem Punktestand auf das Haupt-Canvas. Das Canvas mit dem Punktestand wurde nur neu gezeichnet wenn sich der Punktestand auch änderte. Danach verbrauchte die Methode nur nach 15%. Also war es schneller das gesamte Canvas zu kopieren als eine oder zwei Ziffern zeichnen zu lassen.
document.getElementById["aaaaa"].value=score verursacht z.B. auch extrem hohe Kosten. Also immer alle wichtigen Elemente in Variablen halten und nicht jedesmal neu im Document suchen!
Wärend man in Java extrem mächtige Tools wie VisualVM hat und der Profilier des Firefox oder Chrome einem bei JavaScript Problem sehr gut hilft, ist die Situation bei PHP etwas umständlicher. Zwar kann man so gut wie immer XDebug verwenden, aber so einfaches Remote-Profiling wie mit VisualVM ist da nicht zu machen.
Aber da man meistens sowie so lokal auf dem eigenen PC entwickelt und testet, reicht es die Daten in eine Datei schreiben zu lassen und diese dann mit Hilfe eines Programms zu analysieren.
xdebug.profiler_enable=1
xdebug.profiler_output_dir=C:/test/profile
PHPStorm bringt ein entsprechendes Tool mit.
Aber ich habe bis jetzt WinCacheGrind verwendet. Damit ließen sich nach etwas Einarbeitung dann schnell heraus finden, wo die Zeit verloren ging und welche Methoden wie oft aufgerufen wurden.
Der Class-Loader durchsuchte das System-Verzeichnis zu oft, weil an der Stelle nicht richtig geprüft wurde, ob die Klasse schon bekannt war. So konnte ich die Ladezeit einer Seite in meinem Framework am Ende nach vielen solcher Probleme von 160ms auf ungefähr 80ms senken. Viel Caching kam auch noch dazu und das Vermeiden von Zugriffen auf das Dateisystem.
Aber es gibt noch andere Profiler als XDebug für PHP. Hier findet man eine gute Übersicht:
PHP Profiler im Vergleich
Ich hab schön öfters gehört, dass solche Test und das Profiling ans Ende der Entwicklung gehören und man so etwas nur macht wenn man keine andere Wahl hat. Aber am Ende findet man viele Fehler dabei und ich halte es für falsch nicht schon am Anfang zu testen ob eine Anwendung auch später mit vielen produktiven Daten noch performant laufen wird. Denn am Ende sind grundlegende Fehler in der Architektur schwerer und auf wendiger zu beheben als am Anfang oder in der Mitte der Entwicklung.
Nachträglich an einzelnen Stellen Caching einzubauen ist auch nicht so gut wie von Anfang an ein allgemeinen Caching-Mechanismus zu entwerfen, der an allen relevanten Stellen automatisch greift.
Deswegen sollte man auch schon ganz am Anfang immer mal einen Profiler mitlaufen lassen und gucken, ob alles so läuft wie man es sich dachte.
Profilling ist wichtig. Das klassische Rausschreiben von microtime()-Werten ist nicht wirklich verlässlich, gerade wenn das Bootstraping und die Class-Loader teilweise vorher laufen. Da bei meinem Framework die Verbesserungen in der Performance zwar immer ein wenig was brachten, aber der Rest immer noch länger dauerte als der Code vermuten lies, habe ich mal xdebug aktiviert.
xdebug.profiler_enable=1
xdebug.profiler_output_dir=C:/test/profile
Mit WinCacheGrind dann die dort erzeugte Datei nach einem Seiten-Aufruf analysiert. Es gab wirklich einen Fehler im Class-Loader der dafür sorgte, dass das system/classes/ Verzeichnis immer wieder gelesen wurde, wenn nur die Klasse noch nicht eingebunden war, aber schon der Pfad dorthin bekannt war. Dateizugriffe kosten extrem und selbst mit den PHP internen Caches sollte man so wenige wie möglich verwenden. Übersetzungsdateien werden nun in der Session gecached. Das Einlesen war auch sehr aufwendig.
Ein dummy User-Objekt versuchte, die eigenen UserGroups zu laden. Da die Id aber 0 war kam natürlich nie ein Ergebnis aus der DB, aber der Overhead wurde erzeugt. Also eine Prüfung auf id==0 und schon lief alles besser.
Meine Grundregeln sind jetzt:
* wenige Zugriffe auf das Dateizugriff
* Caching von Daten
* unnötige DB-Connections verhindern
* Objekte instanziieren kostet viel, wenn möglich SingleTons für DAOs und ToolKits verwenden
* nur laden was man braucht
* ob JSON oder XML ist am Ende nicht so wichtig, solange die Dateien keine unnötigen Daten enthalten (schnell Parsen und Cachen)
* Profilling ist wichtig und sollte man immer mal wieder machen (auch ohne konkreten Anlass)
* Das Bootstraping des Frameworks/der Anwendung muss schnell sein, der Rest liegt in der Hand des Entwicklers und er sollte so entwickeln können, also würde das Framework keine Zeit benötigen (er soll sich auf seinen Code konzentieren können)
Wichtig bei der oben gezeigten Config in der php.ini ist, dass dasVerzeichnis shon exisieren muss,da xdebug es nicht von sich auch anlegen würde.