Archive for the ‘PHP’ Category
Gratulation an „PHP Hates Me“ für 500 eindrucksvolle Artikel!
Tja, was soll ich viel sagen, einer meiner fast täglichen Stammblogs über PHP, und mehr oder weniger Inspirator für meinen eigenen Blog feiert heute: „PHP Hates Me“ hat an den letzten 500 Arbeitstagen 500 Artikel veröffentlicht, Gratulation meinerseits!
Auf einige interessante Artikel habe ich ja bereits verwiesen hier im Blog. Immer interessant finde ich auch die wöchentlichen Lesestoff-Links, vielleicht sollte ich das auch mal machen.
Wie dem auch sei, ich wünsche jedenfalls viel Erfolg für die nächsten 500 Artikel!
Software grafisch darstellen mit Code_Swarm
Hier erstmal das Video, damit ihr seht worum es geht:
Dies ist das Video, das ich gerade erstellt habe, und zwar vom Zend Framework 1.10 branch. Man sieht, wo welche Programmierer mitgeholfen haben, wer häufig welche Art von Dateien bearbeitet usw. Mit mehr Zeit könnte man da noch andere coole Dinge mit anstellen, wie beispielsweise weitere Dateiendungen farbig markieren etc. etc.
Es gibt natürlich auch aussagekräftigere Tabellen und Statistik-Tools, aber ich finde das eine sehr schöne Darstellung, sollte man mal von seinem Projekt machen wenn mehrere Leute involviert sind (als Motivation).
Code_Swarm kann auf svn, git, hg und wahrscheinlich noch weiteren Repositories losgelassen werden. Wie man es installiert und laufen lässt steht hier:
http://github.com/rictic/code_swarm
ich möchte es nicht kopieren. Ich habe es bereits ohne Probleme unter Mac OS X und Ubuntu ans Laufen bekommen. Unter Ubuntu 9.10 sah das so bei mir aus:
cd mkdir code_swarm cd code_swarm sudo apt-get install git-core subversion mencoder git clone git://github.com/rictic/code_swarm.git svn co http://framework.zend.com/svn/framework/standard/branches/release-1.10/ export PATH=$PATH:~/code_swarm/code_swarm/bin cd release-1.10 code_swarm
Der erste Aufruf von code_swarm generiert eine Folge von Ergebnisbildern, die man auch als Film angezeigt bekommt.. Um viele Tausend png-Bilder auf der Festplatte zu generieren, die wir dann noch in einen Film umwandeln können, muss in der Projektdatei (~/code_swarm/release-1.10/.svn/.code_swarm/project.config) folgendes geändert werden:
ColorAssign3="Source Code" <-- .php hinzufügen TakeSnapshots=true SnapshotLocation=frames/#####.png
Dann nochmal code_swarm im Repository-Verzeichnis aufrufen, anschliessend wird der Aufruf von mencoder daraus ein Video generieren:
cd ../code_swarm/frames/ mencoder mf://*.png -mf fps=24:type=png -ovc lavc -oac copy -o video1.avi
Bei Problemen oder weiteren Einstellungsfragen sollte man dieses HowTo gelesen haben. Hier ist noch ein Posting wo man sehr viele Einstellungsmöglichkeiten sehen kann (Die „Masse“ der Entwickler, Framegröße, Geschwindigkeiten usw)
Weitere interessante Projekte, die sich mit der visuellen Darstellung von Repositores oder Logs beschäftigen, kann man hier sehen:
6 Methoden, ein Verzeichnis rekursiv zu scannen
Wer von euch kennt solchen Code:
if ($handle = opendir('/path/to/files')) { while (false !== ($file = readdir($handle))) { echo "$file\n"; } closedir($handle); }
So wird es häufig gemacht, aber hier wird natürlich nur ein Verzeichnis durchsucht und keine Unterordner betrachtet. Und häufig schleichen sich dort auch Fehler ein, beispielsweise wenn man im obigen Beispiel nur
while ($file = readdir($handle)) {
schreibt. Es gibt aber noch eine Menge anderer Möglichkeiten, die ich hier einmal vorstellen und vergleichen möchte. Danach stelle ich noch einige Testergebnisse vor, um die verschiedenen Möglichkeiten zu vergleichen sowie Vor- und Nachteile aufzuzeigen.
Hier erstmal das Script mit den einzelnen Funktionen:
$method = 'dirRead'; echo "mem before:".memory_get_peak_usage(true)."\n"; echo "Starting $method()\n"; clearstatcache(); $startTime = microtime(true); $allFiles = DirectoryParser::$method('C:/Temp'); echo "Zeit: ".(microtime(true) - $startTime)." sec\n"; echo "mem after:".memory_get_peak_usage(true)."\n"; echo count($allFiles)."\n\n"; class DirectoryParser { public static function createTestFolders($dir) { for($i=0; $i<10000; $i++) { @mkdir($dir.'/'.$i); for ($u=0; $u<10; $u++) { touch($dir.'/'.$i.'/'.rand(10000, 999999).'.txt'); } } } public static function dirRead($dir, &$fileinfo = array()) { if ($handle = dir($dir)) { while (false !== ($file = $handle->read())) { if (!is_dir($dir.'/'.$file)) { $fileinfo[] = array($file, filesize($dir.'/'.$file)); } elseif (is_dir($dir.'/'.$file) && $file != '.' && $file != '..') { self::dirRead($dir.'/'.$file, $fileinfo); } } $handle->close(); } return $fileinfo; } public static function find($dir) { if (substr(php_uname(), 0, 7) == "Windows") { foreach (explode("\n",` cd "$dir" && dir /S /B /A-D `) as $fullFilename) { if ($fullFilename != '') { $fileinfo[] = array($fullFilename, filesize($fullFilename)); } } } else { foreach (explode("\n",` cd $dir && find -maxdepth 3 -type f ! -name ".*" -printf "%f\r%s\n" `) as $fileInfos) { if ($fileInfos != '') { $fileinfo[] = explode("\r", $fileInfos); } } } return $fileinfo; } public static function glob($dir, &$fileinfo = array()) { foreach (glob($dir.'/*') as $file) { if (is_dir($file)) { self::glob($file, $fileinfo); } else { $fileinfo[] = array(basename($file), filesize($file)); } } return $fileinfo; } public static function scandir($outerDir){ $dirs = array_diff(scandir($outerDir), array(".", "..")); $fileArray = array(); foreach ($dirs as $d) { if (is_dir($outerDir."/".$d)) { $fileArray = array_merge($fileArray, self::scandir($outerDir."/".$d)); } else { $fileArray[] = array($d, filesize($outerDir."/".$d)); } } return $fileArray; } public static function opendir($dir, &$fileinfo = array()) { if ($handle = opendir($dir)) { while (false !== ($file = readdir($handle))) { if (!is_dir($dir.'/'.$file)) { $fileinfo[] = array($file, filesize($dir.'/'.$file)); } elseif (is_dir($dir.'/'.$file) && $file != '.' && $file != '..') { self::opendir($dir.'/'.$file, $fileinfo); } } closedir($handle); } return $fileinfo; } public static function directoryIterator($dir) { $iterator = new RecursiveDirectoryIterator($dir); foreach(new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::CHILD_FIRST) as $file) { if (false == $file->isDir()) { $fileinfo[] = array($file->getFilename(), $file->getSize()); } } return $fileinfo; } }
Man wählt also in Zeile 1 eine Methode, stellt in Zeile 7 den Pfad korrekt ein, und lässt das Script laufen. Falls man ein Testverzeichnis erstellen möchte mit 100.000 Dateien, kann man die Methode createTestFolders nutzen.
Bei allen Methoden, die Verzeichnisse durchsuchen und Dateien auslesen (z.B. um die Größe festzustellen) muss man darauf achten, nicht zuviele „offene Filehandles“ zu haben, denn viele Betriebssysteme haben da festgelegte Grenzen, wieviele offene Dateien ein Prozess haben darf.
Unter Linux findet man das beispielsweise heraus mit
$ cat /proc/sys/fs/file-max 184665 $ sysctl fs.file-max fs.file-max = 184665
Leider weiß ich nicht, wie man die maximale Anzahl an gleichzeitigen FileDescriptors („open files“) eines laufenden Prozesses herausfindet, deshalb habe ich in der unten stehenden Tabelle nur die Gesamtanzahl der Filesystem-Events unter Windows 7 rausgeschrieben (herausgefunden mit dem Process Monitor). Wieviele davon jeweils gleichzeitig geöffnet waren konnte ich nicht herausfinden. Wer weiß Bescheid?
Des weiteren habe ich die Laufzeit protokolliert und auch die Algorithmen einmal einzeln laufen lassen, um den maximalen Speicherverbrauch herauszufinden.
Methode | Laufzeit (s) | MemoryPeak (MB) | FilesystemEvents Win7 | Probleme |
---|---|---|---|---|
dir()/read() | 51 | 33 | 1207556 | |
find/dir | 46 | 39 | 805840 | |
glob() | 56 | 34 | 900006 | findet keine Dateien, die mit . beginnen |
scandir() | 312 | 39 | 1057467 | |
opendir()/readdir() | 52 | 33 | 1206817 | |
DirectoryIterator | 87 | 33 | 2146331 |
Die Messungen habe ich mit meiner normalen SATA2 Festplatte gemacht, das untersuchte Verzeichnis hat 90.499 Dateien, die meisten davon 0 Byte groß und insgesamt über 10078 Unterordner verteilt.
Interessanterweise sind die Ergebnisse unter Linux deutlich anders gewesen.
- Einmal habe ich einen Ubuntu 9.10 Server genommen, mit SSD, aber leider relativ schwacher CPU. PHP 5.2.10
Methode | Laufzeit (s) | MemoryPeak (MB) |
---|---|---|
dir()/read() | 499 | 93 |
find/dir | 135 | 109 |
glob() | 510 | 95 |
scandir() | 1043 | 113 |
opendir()/readdir() | 507 | 94 |
DirectoryIterator | 622 | 88 |
- Den selben Ubuntu Server, aber mit PHP 5.3.1
Methode | Laufzeit (s) | MemoryPeak (MB) |
---|---|---|
dir()/read() | 160 | 56 |
find/dir | 146 | 76 |
glob() | 131 | 58 |
scandir() | 2915 | 79 |
opendir()/readdir() | 142 | 57 |
DirectoryIterator | 156 | 57 |
- Einmal einen Debian-Server mit QuadCore, Raid1 SATA2 und PHP 5.2.11
Methode | Laufzeit (s) | MemoryPeak (MB) |
---|---|---|
dir()/read() | 380 | 53 |
find/dir | 2 | 64 |
glob() | 349 | 55 |
scandir() | 744 | 64 |
opendir()/readdir() | 366 | 54 |
DirectoryIterator | 3 | 46 |
Verstehen tue ich einige Ergebnisse noch nicht, zum Beispiel warum die find-Methode auf dem Debian-Server nur 2 Sekunden benötigt, wohingehen sie unter Ubuntu jeweils > 2 Minuten dauert. Das selbe beim DirectoryIterator. Komisch. Außerdem scheint PHP 5.3 ordentlich an Geschwindigkeit zugelegt zu haben gegenüber 5.2.
Falls jemand von euch weiß, wie man die Anzahl der FilesystemEvents (inotify?) unter Linux herausbekommen kann, oder die Anzahl der maximalen gleichzeitigen Filehandles (sowas wie lsof -p PID, nur das Maximum während eines Programmdurchlaufs), bitte in den Kommentaren melden.
Ich überlege auch, ob es sich lohnt, diese Tests mal auf einem NFS-Laufwerk im Netzwerk zu machen, das könnte man evtl. auch mal brauchen.
(Die Tabellen wollte ich übrigens mal ausprobieren, sind mit dem WordPress-Addon WP-Table Reloaded erstellt worden)
WordPress Bilder optimieren mit WP-Smash.it und Zend_Http_Client
Die WordPress Mediathek wächst und wächst, ab und zu sind auch recht große Screenshots dabei oder gar Bilder der Digitalkamera, die häufig sehr groß sind und Metadaten enthalten.
Mit dem Service Smush.it von Yahoo kann man sehr einfach seine Bilder optimieren lassen. Man lädt die zu optimierenden Bilder hoch und erhält eine Auswertung, wieviel Prozent Smush.it sparen konnte (wohlgemerkt ohne die Bildqualität zu verschlechtern, es werden nur Metadaten entfernt sowie die Farbpalette angepasst usw.) und man kann die Bilder dann als zip-Datei herunterladen. Laut FAQ verwendet Smush.it intern aktuell ImageMagick, pngcrush, jpegtran und gifsicle und wählt das beste Ergebnis.
Der Service bietet auch eine API an. Um uns Bloggern das Leben einfacher zu machen gibt es das WordPress-Plugin WP-Smush.it, welches automatisch alle neuen Bilder, die man in die Mediathek hochlädt durch Smush.it optimieren kann. Auch bereits vorhandene Bilder kann man mit einem Klick optimieren.
Im Durchschnitt erhält man dabei Größenoptimierungen von 5-40%, also durchaus akzeptabel, und es passiert im Hindergrund, man muss nicht extra mit Bildprogrammen rumhampeln oder mit Konsolenprogrammen wie optiPNG und Konsorten hantieren.
Ein Problem hatte ich jedoch: Meine Mediathek enthielt bereits 121 Bilder. Da ich nicht 121 Mal den „Smush.it now“ Link in der Mediathek klicken wollte, habe ich mir ein kleines Script geschrieben was das für mich erledigt.
Vorgehensweise ist wie folgt: Ich brauche zuerst alle IDs der Bilder, die ich komprimieren kann, denn ein Aufruf zum Optimieren sieht so aus:
https://www.phpgangsta.de/wp-admin/admin.php?action=wp_smushit_manual&attachment_ID=34
Ich möchte also 121 mal diese URL aufrufen, jeweils mit den passenden IDs. Die IDs hole ich mir eben schnell via phpMyAdmin aus der Datenbank mit dem Query:
SELECT ID FROM `wp_posts` WHERE `post_type` = 'attachment' AND `post_mime_type` LIKE 'image/%'
Das Ergebnis exportiere ich als csv-Datei, sodass ich eine Datei erhalte in der jede Zeile exakt eine ID enthält.
Um die entsprechende WordPress-Plugin-URL aufzurufen werde ich Zend_Http_Client verwenden. Sieht dann so aus:
<?php $ids = file('ids.txt'); include 'Zend/Loader/Autoloader.php'; $autoloader = Zend_Loader_Autoloader::getInstance(); $client = new Zend_Http_Client(); // Zu Debugzwecken möchte ich das Ergebnis in lesbarer Form, sonst kommt evtl. gzip zurück $client->setHeaders(array( 'Accept-encoding' => '' )); // Timeout erhöhen, 10 Sekunden reichen nicht aus. $client->setConfig(array('timeout' => 60)); $client->setCookieJar(); // Erste Anfrage: einloggen und eine Session starten (Cookie wird in den CookieJar gepackt und bei den folgenden Requests immer mitgesendet) $client->setUri('https://www.phpgangsta.de/wp-login.php'); $client->setParameterPost('log', 'MeinAdministratorName'); $client->setParameterPost('pwd', 'MeinAdministratorPasswort'); $client->setParameterPost('rememberme', 'forever'); $client->setParameterPost('wp-submit', 'Anmelden'); $client->setParameterPost('redirect_to', 'https://www.phpgangsta.de/wp-admin/'); $client->setParameterPost('testcookie', '1'); $response = $client->request(Zend_Http_Client::POST); foreach ($ids as $id) { $id = trim($id); $client->setUri('https://www.phpgangsta.de/wp-admin/admin.php?action=wp_smushit_manual&attachment_ID='.$id); $response = $client->request(Zend_Http_Client::GET); echo 'finished'.$id."\n"; } echo "finished";
Zuerst müssen wir uns natürlich einloggen und speichern das Cookie im CookieJar. Erst danach können wir die URLs in einer Schleife aufrufen.
War eine schöne kurze Übung und garantiert interessanter und schneller als 121 Mal einen Link anzuklicken, wobei man zwischen jedem Klick 3-20 Sekunden warten muss, je nach Bildgröße.
Aktion: Die kleinste Blogsoftware der Welt!
Inspiriert durch Stefan Wienströers Artikel auf SteviesWebsite möchte ich hier dazu aufrufen, den Rekord für das „kleinste Blog System der Welt“ zu brechen! Es geht darum eine Blogsoftware zu schreiben mit möglichst wenig Code. Stefan hat es mit 894 Byte geschafft, ein ganz rudimentäres CMS zu schreiben (das aber nicht ganz unten stehenden Regeln erfüllt).
Die folgenden Regeln werden definiert, damit auch alle Projekte den selben Funktionsumfang bieten:
- Die öffentlich sichtbare Seite muss alle Blogartikel in chronologischer Reihenfolge auflisten
- Jeder Artikel hat eine Überschrift, einen Artikeltext und eine Zeitangabe der Erstellung
- Es gibt einen passwortgeschützten Admin-Bereich, wo man neue Artikel erstellen kann
- Es muss kein gültiger HTML-Code sein, aber die Webseite muss im Firefox ≥ 3.5 darstellbar und nutzbar sein
- Es kann jede PHP Version ≥ 5.2 genutzt werden, es dürfen keine besonderen Extensions vorausgesetzt werden
- Es dürfen Short-Open-Tags genutzt werden
- Die Speicherung der Artikel soll in einer Datei stattfinden
- Es dürfen alle Tricks genutzt werden um kurzen Code zu schreiben: Weglassen von Umbrüchen und Leerzeichen, kurze Variablennamen usw. Es geht nicht um Lesbarkeit, sondern um Funktion und kleinen Code!
- Der SourceCode muss veröffentlicht werden (natürlich 😉 ), evtl. mit Erklärungen falls der Code unlesbar ist 😉
- Es ist möglich, seinen Code zu verbessern und mehrere Lösungen einzusenden
Die Aktion endet am 07.03.2010, ihr habt also 4 Wochen Zeit. Um teilzunehmen erstellt in eurem Blog einen Artikel mit einem Link zu dieser Aktion. Damit ich euren Rekord mit aufnehmen kann in die Liste benötige ich einen Trackback, einen Kommentar oder eine Email. Der SourceCode muss natürlich öffentlich zugänglich und herunterladbar sein, damit jeder das Ergebnis überprüfen kann.
Ich bin sehr gespannt wo die Grenze liegt, wenn man es wirklich drauf anlegt!
————————————-
Aktuelle Rekorde:
Weiterlesen »