Entwickler oder Entwicklerinnen die sich besonders schlau vorkommen …

… bauen gerne mal Konstrukte der folgenden Art:

$path = ltrim( end( @explode( get_template(), str_replace( '\\', '/', dirname( __FILE__ ) ) ) ), '/' );

Sortiert sieht es dann so aus:

$path = ltrim(
    end(
        @explode(
            get_template(),
            str_replace('\\', '/', dirname(__FILE__))
        )
    ), 
    '/'
);

Zum Kontext: diese Zeile stammt aus einem kommerziellen WordPress-Template und es soll der letzte Teil des Pfadnamens des aktuell ausgeführten Skripts bestimmt werden. Aus /var/www/wordpress/wp-content/themes/valenti/option-tree soll also option-tree werden.

Von innen nach außen:

  • dirname(__FILE__) liefert den Pfadanteil des aktuell ausgeführten PHP-Skriptes; aus /var/www/wordpress/wp-content/themes/valenti/option-tree/ot-loader.php wird also /var/www/wordpress/wp-content/themes/valenti/option-tree
  • str_replace('\\', '/', …) ersetzt den Backslash (vermutlich als Workaround für Windows-Systeme) mit einem Slash, der Pfad bleibt auf einem unixoiden System also erstmal unverändert (es sei denn, er enthält tatsächlich den Backslash)
  • get_template() liefert den Namen des aktiven WordPress-Templates, in diesem Fall valenti
  • explode() trennt den Pfadnamen in einzelne Teile, und zwar an jeder Stelle an der der String valenti vorkommt; man erhält also dieses Array: [0 => '/var/www/wordpress/wp-content/themes/', 1 => '/option-tree']
  • end() liefert den Wert des letzten Elements des Arrays: /option-tree
  • ltrim(…, '/') entfernt zum Schluss den Slash am Anfang des Strings, so dass wir das Ergebnis erhalten: option-tree

Soweit, so gut.

Diese Variante hat unter Umständen einige Probleme, Backslashes im Pfadnamen könnten eines davon sein. Unterdrückte Fehlermeldungen mit dem @-Operator könnte zu weiteren Hässlichkeiten beim Debugging führen.

Aber das Hauptproblem ist, dass der Entwickler oder die Entwicklerin hier eine komplexe Lösung für ein einfaches Problem geschaffen hat. PHP löst die gegebene Fragestellung nämlich mit Bordmitteln viel eleganter und zudem fehlerunanfällig und OS-unabhängig:

$path = basename(__DIR__);

Keine Ursache.

Pipes, oder: eine von vielen Dingen, die mich an Unix begeistern

Oft benötige ich für meine lokale Entwicklungsumgebung die aktuelle Struktur und die Inhalte der Datenbank des Produktivsystems. Ein möglicher Ablauf, um die Daten aus dem Produktiv-Datenbankserver herauszuholen und lokal zu importieren könnte so aussehen:

  1. Einloggen auf dem Server
  2. per mysqldump eine Datei mit allen SQL-Statements zum Erzeugen der Datenbank samt ihrer Daten schreiben
  3. Den Dump mit gzip oder bzip2 komprimieren
  4. Diese Datei mit scp oder rsync auf den lokalen Rechner herunterladen und in die Entwicklungs-Virtual-Machine kopieren
  5. Einloggen auf der Entwicklungs-VM
  6. Entpacken sowie Importieren des Dumps

Oder man macht es einfach als Einzeiler auf der Shell:

ssh server "mysqldump db_name | gzip -c" | ssh vagrant "gzip -dc | mysql db_name"

Wahlweise kann man auch noch pv als Fortschrittsanzeige dazwischenschalten:

ssh server "mysqldump db_name | gzip -c" | pv | ssh vagrant "gzip -dc | mysql db_name"

Mit Hilfe des Pipes-Konzepts lässt sich der Ausgabestream eines Kommandos als Eingabestream eines anderen Kommandos verwenden, ohne dass temporäre Dateien angelegt und herumkopiert werden müssen. Es dürfte sich neben der „Everything is a file“-Philosophie¹ um eines der praktischsten Dinge von Unix sein.

¹ Eine Pipe hat übrigens auch einen file descriptor.

Workaround für PHP Bug #43225

In PHP gibt es einen Bug, der dazu führt, dass die Funktion fputcsv unter Umständen fehlerhaftes CSV generiert, welches dann von Programmen nicht mehr eingelesen werden kann.

Der Fehler tritt auf, wenn in einem Feldwert ein Backslash \ vorkommt, der direkt von einem doppelten Anführungszeichen " („double quote“) gefolgt wird. Ein Beispiel:

$data = [
    'CMXINV',
    '-1001338',
    'Some string data here: "quoted text // highlight string \\\\" and "foo" bar',
    'baz'
];

Hier folgt auf die zwei Backslashes[1] direkt ein doppeltes Anführungszeichen.

Erzeugt man aus dem $data-Array mit fputcsv eine CSV-Zeile, erwartet man folgende Ausgabe:

CMXINV;-1001338;"Some string data here: ""quoted text // highlight string \\"" and ""foo"" bar";baz

Tatsächlich erhält man aber:

CMXINV;-1001338;"Some string data here: ""quoted text // highlight string \\" and ""foo"" bar";baz

Bei genauem Hinschauen erkennt man, dass hinter den beiden Backslashes nur ein doppeltes Anführungszeichen steht, obwohl es zwei sein sollten[2].

Dieses Verhalten ist als PHP Bug 43225 bereits 2007 dokumentiert worden und wurde bis heute nicht korrigiert.

Ich setze fputcsv in meiner Open Source Library für Collmex ein. Jegliche Kommunikation mit der Collmex-API geschieht über das Senden und Empfangen von Nachrichten im CSV-Format. Bei einem Projekt trat nun der Fall auf, dass in einem Feld tatsächlich Backslashes vor doppelten Anführungszeichen standen und Collmex den erzeugten fehlerhaften Datensatz nicht lesen konnte. Ich habe jetzt einen Workaround in der Collmex-Library eingebaut, mit der dieser spezielle Fehler nicht mehr auftritt.

Der Workaround macht nichts anderes als vor dem Aufruf von fputcsv temporär einen String zwischen Backslash und doppeltem Anführungszeichen einzufügen und ihn nach dem Erzeugen der CSV-Daten wieder zu entfernen. Die Details lassen sich in der betreffenden Quelldatei bei Github ansehen.


[1] Es sind hier vier Backslashes zu sehen, weil das Backslash-Zeichen zum „escapen“ benutzt wird und selbst auch escaped werden muss – durch einen weiteren Backslash.
[2] Ähnlich wie bei [1] muss ein doppeltes Anführungszeichen durch ein weiteres doppeltes Anführungszeichen „escaped“ werden, wenn die Feldwerte durch ein doppeltes Anführungszeichen eingeschlossen werden.

UCI-Streifen nur mit CSS bauen

Dank des großartigen CSS-Gradient-Editors von ColorZilla war es ein Leichtes, die UCI-Streifen für einen Element-Hintergrund auf einer Website nur mit CSS zu bauen:

.uci-stripes {

background: -moz-linear-gradient(top, #1e76bd 0%, #1e76bd 20%, #c7203c 20%, #c7203c 40%, #000000 40%, #000000 60%, #f8df00 60%, #f8df00 80%, #35b24a 80%, #35b24a 100%); /* FF3.6-15 */

background: -webkit-linear-gradient(top, #1e76bd 0%,#1e76bd 20%,#c7203c 20%,#c7203c 40%,#000000 40%,#000000 60%,#f8df00 60%,#f8df00 80%,#35b24a 80%,#35b24a 100%); /* Chrome10-25,Safari5.1-6 */

background: linear-gradient(to bottom, #1e76bd 0%,#1e76bd 20%,#c7203c 20%,#c7203c 40%,#000000 40%,#000000 60%,#f8df00 60%,#f8df00 80%,#35b24a 80%,#35b24a 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */

}

(Direktlink zum Editor)

UCI-Streifen

make – der bessere Task-Runner für das Web Development

Fast im Wochentakt kann man das Erscheinen neuer Task-Runner-Systeme beobachten – sie alle versprechen weniger Bloat, leichteres Erstellen der Tasks und immer dreimal besser zu sein als die Konkurrenz. Heutzutage basieren diese System meist auf Node.js, da es hier mittlerweile einen riesigen Fundus an Tools gibt, die für die einzelnen Aufgaben eines Task-Runners für das Web Development benötigt werden.

Ich habe einige dieser Systeme (z. B. Grunt, Gulp) auch produktiv benutzt. Mittlerweile wird aber von mir wieder das selbe Werkzeug für diese Aufgaben eingesetzt wie bereits vor über zehn Jahren: make.

make ist ein Build-Management-Programm und kann beliebige Kommandos in Abhängigkeit von bestimmten Bedingungen ausführen. Der Ansatz unterscheidet sich grundlegend von dem der modernen Task-Runner.

Am einem Beispiel lässt sich das Konzept erklären: Ich möchte alle für eine Website benötigten Javascript-Dateien in eine einzige Datei zusammenfassen und davon dann eine minifizierte Version erstellen, die danach deployed werden kann.

Einem Task-Runner würde man in etwa sagen: „Nimm diese Liste von Dateien, hänge sie aneinander und speichere das Ergebnis als Datei. Danach nimm diese Datei, minifiziere sie und speichere sie ebenfalls ab.“

Mit make geht es eleganter: Statt einer Liste von abzuarbeitenden Kommandos gibt man einerseits die Voraussetzungen (Prerequisites) an, die erfüllt sein müssen, um die Zieldatei (Target) zu erzeugen. Zum anderen spezifiziert man die Kommandos, welche nach der Erfüllung der Voraussetzungen die Zieldatei erzeugen. Make wird dann zuerst rekursiv alle Voraussetzungen erfüllen, um zum Schluss die Zieldatei zu erzeugen. Klingt kompliziert, ist es aber nicht. Ein Beispiel:

assets/site.js: resources/jquery.js \
resources/app.ux.js \
resources/app.comments.js
	cat $^ > $@

Diese Datei nennt sich im make-Jargon Makefile und wird auch unter diesem Dateinamen gespeichert. Im Makefile ist ein sogenanntes Target samt seiner Prerequisites sowie die Kommandos zur Erstellung der Zieldatei definiert.

Das Target lautet hier assets/site.js und steht vor dem Doppelpunkt. Darauf folgend sind die Prerequisites in der selben Zeile aufgezählt (bzw. in aufeinanderfolgenden Zeilen die mit dem escapten Newline  \ enden, wie im Beispiel zu sehen). Das oder die Kommandos stehen mit jeweils einem Tabulator-Zeichen (!) eingerückt unter dem Target.

Die Voraussetzungen für das Erzeugen der Datei assets/site.js sind in Form von Dateinamen aufgezählt und lauten resources/jquery.js resources/app.ux.js resources/app.comments.js. Das Kommando erzeugt das Target durch einfaches Aneinanderhängen der Prerequisite-Dateien mit dem Unix-Kommando cat. Hierbei kommen zwei Variablen zum Einsatz, die von make automatisch gefüllt werden: $^ ist die Liste der Prerequisites und $@ ist das Target. Ausgeschrieben lautet das Kommando:

cat resources/jquery.js \
resources/app.ux.js \
resources/app.comments.js > assets/site.js

Der Aufruf von make assets/site.js baut jetzt diese Javascript-Datei. Clever dabei: make wird die Datei nur erzeugen, wenn sie noch nicht existiert oder mindestens eine der Prerequisites zwischenzeitlich geändert wurde. Ruft man das Kommando zweimal nacheinander auf, wird make beim zweiten Mal sagen, dass es nichts zu tun gibt, da die Zieldatei schon aktuell ist.

Ein zweites Target könnte dann die minifizierte Variante der Javascript-Datei erzeugen:

assets/site.min.js: assets/site.js
    uglifyjs $< --mangle --comments > $@

Update: Steffen wies mich per E-Mail darauf hin, dass obiges Make-Target unter Umständen zu Problemen führen kann, nämlich dann, wenn in dem Beispiel `uglifyjs` fehlschlägt. Die Ausgabedatei wird sofort durch die Shell erzeugt – und zwar unabhängig davon, ob das Kommando erfolgreich ist oder mit einem Fehler beendet wird. Im Fehlerfall liegt dann eine leere Zieldatei vor ohne dass irgendeine Fehlerbehandlung durchgeführt wurde oder man den Fehler überhaupt bemerkt. Besser ist es also, das Target folgendermaßen zu definieren:

assets/site.min.js: assets/site.js
    uglifyjs $< --mangle --comments > $@ || { rm -f $@ ; exit 2 ; }

So bekommt man sofort mit, wenn irgendetwas schief läuft und verarbeitet nicht aus Versehen eine leere Datei weiter.

Man sieht, dass jetzt die eben erzeugte Datei assets/site.js als Prerequisite für die minifizierte Datei dient. Die Variable $< steht dabei für die erste Prerequisite aus der Liste; in diesem Fall wird sie durch assets/site.js ersetzt.

Wenn jetzt make assets/site.min.js aufgerufen wird, stellt make sicher, dass zuerst assets/site.js erzeugt wird (siehe oben, dort ist das Target assets/site.js definiert) und führt dann das Kommando zur Minifizierung aus. Auch hier wird make nur tätig, wenn das Ziel noch nicht existiert oder sich eine der Quelldateien zwischenzeitlich geändert hat.

Mit diesem Prinzip kann man jetzt alle benötigten Assets für eine Website erzeugen, von Image-Sprites über robots.txt-Dateien bis hin zu Konfigurations-Dateien und vieles andere mehr, von sehr simpel bis beliebig komplex.

Die größten Vorteile von make sind meines Erachtens:

  • die Geschwindigkeit: bei meinen Websites ist make im Durchschnitt dreimal schneller als Gulp (inkl. der langsamen Tasks wie JavaScript Minifying usw.). Der Unterschied zwischen zehn Sekunden und drei Sekunden Build-Dauer ist nicht zu vernachlässigen. Vor allem wenn man das zig Mal pro Tag macht.
  • die Verfügbarkeit: make ist auf so ziemlich jedem unixoiden Betriebssystem dabei bzw. lässt sich aus Paketquellen sofort installieren
  • die Einfachheit: make ist eine einzige ausführbare Datei vom kaum 200 KiB Größe (Linux, x86_64). Moderne JavaScript-Task-Runner haben viele Abhängigkeiten, die man mit npm installieren muss – was aber erst geht, wenn man npm selbst installiert hat, für welches man wiederum erst mal Node.js installieren muss…[1]
  • make folgt der Unix-Philosophie: anstatt das Rad jedes Mal neu zu erfinden, nutzt man mit make die Möglichkeiten, die Unix bietet, z. B. Pipes und Redirections, mit denen simple Werkzeuge verknüpft werden um komplexe Aufgaben zu lösen.
  • die klarere Strukturierung der Anweisungen: ein Makefile ist üblicherweise leichter lesbar als die Anweisungen für einen JavaScript-Task-Runner, da make selbst auf komplexe Funktionialitäten, wie zum Beispiel Closures verzichtet.
  • make macht nicht mehr als es wirklich tun muss: anstatt z. B. die kompletten Assets bei jedem Aufruf neu zu bauen, wird make nur die Targets ausführen, deren Prerequisites sich auch wirklich geändert haben. Ändert man etwas am CSS, wird make nicht auch die JavaScript-Assets bauen, da es weiß, dass dieses bereits aktuell sind. Sind alle Targets aktuell, wird make das auch direkt so mitteilen: `make: Nothing to be done for ‚assets’`.

make gibt es in verschiedenen Versionen, ich setze GNU make ein: Homepage.


[1] Zugegeben, das ist nicht ganz fair, da Programme wie uglifyjs üblicherweise ebenfalls per npm installiert werden. npm und Node.js sind also unter Umständen doch Voraussetzung.

PHP-Entwicklung: Full-blown IDE vs. Text-Editor

Das Entwickeln von PHP-Applikationen ist die Tätigkeit, mit welcher ich die meiste Zeit des Tages beschäftigt bin. Das ist Grund genug, ständig nach Dingen zu suchen, die mir diese Arbeit erleichtern.

Das zentrale Tool für die Entwicklung ist dabei das Programm, in welchem ich Code eingebe, ändere, suche, ersetze, verwalte und vieles mehr. Gerade hier ist über die Jahre eine Menge passiert, vieles aber doch gleich geblieben. Anfangs – noch als Windows-Nutzer – habe ich Editoren wie z. B. SciTe, Notepad++, UltraEdit o. Ä. zum Coden benutzt. Da er bei der Arbeit auf Servern zu den wichtigsten Tools gehörte und allgegenwärtig war, lernte ich später auch Vim und nutzte ihn dann in der grafischen Version GVim als meinen primären PHP-Editor.

Mit dem Umstieg auf OS X rutschte Vim als Alltagseditor etwas aus dem Fokus, was auch daran lag, dass es damals noch keine anständige Umsetzung für den Mac gab (diese wurde dann in Form von MacVim um 2007 oder 2008 veröffentlicht). Zwischenzeitlich wechselte ich – wie vermutlich fast jeder zu dieser Zeit – auf TextMate, welcher mit seinen Plugins (im TextMate-Sprech Bundles genannt) unfassbar leistungsfähig war.

Hin und wieder wagte ich den Blick über den Tellerrand und schaute mir integrierte Entwicklungsumgebungen mit PHP-Support an. Es gab zwar durchaus einige (mir fallen spontan NetBeans, Komodo, Eclipse PDT und Zend Studio ein) aber nichts konnte mich überzeugen. Ich ließ mir aber ein fertig konfiguriertes NetBeans liegen, welches ich zum Step-by-Step Debuggen nutzte – das war das einzige wichtige Feature, das mir TextMate nicht bieten konnte.

Die IDE-Welt wurde dann mit dem Erscheinen von JetBrains PhpStorm hart durchgerüttelt. PhpStorm wuchs gerade in den letzten beiden Jahren zum defacto-Standard der PHP-IDEs heran. Nach und nach verlagerte ich meine PHP-Entwicklung auch komplett nach PhpStorm. JetBrains legt in einer unglaublichen Geschwindigkeit neue Features nach, auch die Unterstützung neuer PHP-Versionen und Tools wie Composer ließen nie lange auf sich warten.

Auch ein Grund, mit dem mich JetBrains überzeugen konnte, jedes Jahr etwas Geld auszugeben um die Lizenz zu verlängern, war der Umstand, dass PhpStorm für eine riesige Java-Desktop-Applikation ausreichend schnell war.

Leider hat sich das zuletzt geändert. Auch auf neuen Rechnern fühlt sich PhpStorm nicht mehr flüssig an, es ruckelt mittlerweile an allen Ecken und Enden. Dieser Umstand führte in letzter Zeit dazu, dass ich wieder öfter zum Text-Editor greife, statt PhpStorm zu booten.

Zum Glück ist der Markt der Text-Editoren lebendig wie nie zuvor. TextMate kommt seit einiger Zeit in Version 2 (Beta) daher und ist immer noch wunderbar zum Entwickeln von PHP-Applikationen mit allem was dazu gehört geeignet. Github hat mit Atom einen nagelneuen Editor entwickelt, auch wenn dieser konstruktionsbedingt (basiert auf dem Chromium-Browser) ein paar Nachteile hat. Seit einigen Jahren ist allerdings Sublime Text der Platzhirsch der Code-Editoren. Sublime Text bietet wie auch TextMate Unterstützung für Plugins und kann so auf den eigenen Bedarf angepasst werden und ist auch bei großen Projekten unfassbar schnell. Grund genug, dass ich seit einiger Zeit vermehrt Sublime Text als Editor statt PhpStorm zum Entwickeln nutze.

Gerade zuletzt musste ich sehr viele kleinere Änderungen in vielen Projekten in kurzer Zeit durchführen – die Geschwindigkeit von Sublime Text (in allen Belangen: Projekte öffnen, suchen über das gesamte Projekt, ersetzen usw.) hat das alles überhaupt erst erträglich gemacht.

Alles in allem ist es interessant zu sehen, wie sich der Kreis schließt und schlussendlich doch wieder der einfache Text-Editor das Tool meiner Wahl wird wenn es ums Coden geht.

Weiterleitung für WordPress-URLs bei Permalink-Änderung

Ich habe eben die URLs der Blog-Posts hier im WordPress geändert. Das geht an sich ganz einfach – in den Permalink-Settings von WordPress kann man sich den URL-Pfad für die Blog-Posts fast beliebig zusammenstellen.

Das Problem an der Sache ist, dass die Links zu bereits veröffentlichten Blog-Posts ungültig werden. Man möchte also für die alten Links einen Redirect einrichten.

Die alte Permalink-Struktur sah so aus:

https://www.marcusjaschen.de/fahrt-in-den-sommerlichen-sonnenuntergang/

Nach der Änderung sind die URLs etwas anders aufgebaut:

https://www.marcusjaschen.de/blog/2015/fahrt-in-den-sommerlichen-sonnenuntergang/

Um jetzt nicht alle Weiterleitungen per Hand anlegen zu müssen, habe ich mir die Arbeit vereinfacht und die fertigen Redirect-Direktiven für die Apache-Konfiguration direkt per SQL ausgeben lassen:

SELECT 
    CONCAT(
        'RedirectPermanent /',
        `post_name`,
        ' ',
        'https://www.marcusjaschen.de/blog/', 
        DATE_FORMAT(`post_date`, '%Y'), 
        '/', 
        `post_name`
    ) 
FROM 
    `wp_posts` 
WHERE
    `post_status` = 'publish'
    AND `post_type` = 'post'
ORDER BY `post_date` ASC

Da fällt dann z. B. so etwas heraus:

RedirectPermanent /kopf-hoch-lenin https://www.marcusjaschen.de/blog/2015/kopf-hoch-lenin
RedirectPermanent /airport-berlin-brandenburg-international https://www.marcusjaschen.de/blog/2015/airport-berlin-brandenburg-international
RedirectPermanent /fahrt-in-den-sommerlichen-sonnenuntergang https://www.marcusjaschen.de/blog/2015/fahrt-in-den-sommerlichen-sonnenuntergang

Es reicht die Ausgabe in die Apache-Konfiguration oder, falls vorhanden, in die .htaccess-Datei zu kopieren um alle bisherigen Links auf die geänderten URLs weiterzuleiten.

PHP: foreach vs. array_reduce

Ich habe letztens meine PHP-Library phpgeo mit Scrutinizer CI auf Code-Qualität untersuchen lassen. Es wurde an zwei Stellen Code-Duplication angemahnt, die ich dann auch gleich entfernt habe.

Dabei stieß ich auf eine foreach-Schleife, welche für die Berechnung der Länge einer Polyline (GPS-Track) zuständig ist.

Das Ergebnis der Schleife lässt sich auch wunderbar mit PHPs Funktion array_reduce berechnen. Die Frage war jetzt, ob ich es so lasse wie gehabt oder tatsächlich auf array_reduce umstelle.

Die betreffende Methode:

public function getLength(DistanceInterface $calculator)
{
    $distance = 0.0;

    if (count($this->points) <= 1) {
        return $distance;
    }

    foreach ($this->getSegments() as $segment) {
        $distance += $segment->getLength($calculator);
    }

    return $distance;
}

lässt sich auch so schreiben:

public function getLength(DistanceInterface $calculator)
{
    $distance = 0.0;

    if (count($this->points) <= 1) {
        return $distance;
    }

    return array_reduce(
        $this->getSegments(),
        function($carry, $item) use ($calculator) {
            return $carry + $item->getLength($calculator);
        }
    );
}

array_reduce müsste schon mindestens einen relevanten Vorteil gegenüber foreach bieten und außer der Geschwindigkeit und vielleicht ein bisschen mehr Eleganz fiel mir keiner ein. Die Geschwindigkeit lässt sich zum Glück schnell testen und ein kurzes Skript für diesen Zweck war schnell geschrieben:

<?php
/**
 * we need something to call
 */
function calculate_something($value) {
    return $value / ($value + 1);
}

$cycles = 100000;

// create some random data
$array = [];
for ($i = 0; $i < $cycles; $i ++) {
    $array[$i] = mt_rand(0, 0x7fffffff);
}

$timer['start'] = microtime(true);

$sumForeach = 0;
foreach ($array as $value) {
    $sumForeach += calculate_something($value);
}

$timer['foreach'] = microtime(true);

$sumArrayReduce = array_reduce($array, function($carry, $item) {
    return $carry + calculate_something($item);
});

$timer['array_reduce'] = microtime(true);

echo $sumForeach . " == " . $sumArrayReduce . PHP_EOL;
echo "foreach     : " . ($timer['foreach'] - $timer['start']) . PHP_EOL;
echo "array_reduce: " . ($timer['array_reduce'] - $timer['foreach']) . PHP_EOL;

Es zeigte sich, dass foreach deutlich schneller ist als array_reduce:

[mj@neptune:~/tmp]
% php test.php
99999.999335167 == 99999.999335167
foreach     : 0.068737030029297
array_reduce: 0.2411630153656

Wenn man darüber nachdenkt ist auch schnell klar warum das so sein muss: Für jeden Wert des Arrays wird in array_reduce ein – in PHP relativ teurer – Funktionsaufruf fällig (nämlich die Callback-Closure), was in foreach nicht notwendig ist.

Ende der Geschichte: Die Längenberechnung in phpgeo geschieht weiterhin in einer foreach-Schleife.

Es ist allerdings leicht vorstellbar, dass der Overhead eines Funktionsaufrufes bei anderen Anwendungen weniger ins Gewicht fällt und dort array_reduce die elegantere Alternative darstellen kann.

FFmpeg beim Erstellen von Standbildern auf die Beine helfen

Um in einem Videoplayer Vorschaubilder beim Bewegen der Maus über die Zeitleiste anzuzeigen, erstellt man vom jeweiligen Video ausreichend viele Standbilder und gibt diese dem Videoplayer zeitcodiert mit.

Das Erstellen eines Standbildes kann mit FFmpeg (bzw. avconv unter debianoiden Linuxen) relativ einfach erledigt werden:

ffmpeg -y -i videofile.mp4 -ss 10 \
-f image2 -s 1280x720 -vframes 1 \
-threads auto -an screenshot.jpg

FFmpeg bekommt in diesem Beispiel mit mit dem Argument -ss 10 gesagt, im Video zum Zeitpunkt 10 Sekunden „vorzuspulen“ (im Sinne von „den ganzen Film decoden und an der entsprechenden Stelle anhalten“) und ein Standbild dieser Stelle abzuspeichern. Ist der Zeitpunkt nah am Anfang des Videos geht das recht schnell. Je weiter sich der Zeitpunkt vom Start entfernt, desto länger dauert der ganze Vorgang; wir reden hier von etlichen Sekunden schon bei relativ kurzen Videos von wenigen Minuten Länge. Wenn man jetzt z. B. 80 dieser Bilder aus einem Video benötigt, dauert das schnell mal an die 10 Minuten. Und wenn man dann nicht nur eins, sondern rund 30.000 Videos hat, wird’s schnell ungemütlich.

Zum Glück gibt es einen Trick, wie ich gestern gelernt habe: Fast-Seeking.

Hierbei springt FFmpeg an den dem Zeitstempel am nächsten liegenden Keyframe, von dort zum gewünschten Zeitstempel und erzeugt dann den Screenhot. Sehr praktisch und für den gegebenen Anwendungsfall absolut ausreichend.

ffmpeg -y -ss 10 -i videofile.mp4 \
-f image2 -s 1280x720 -vframes 1 \
-threads auto -an screenshot.jpg

Was hat sich im Befehl geändert? Lediglich die Position des Arguments -ss 10: Steht dieses vor dem Input-Argument (-i) aktiviert FFmpeg das Fast-Seeking. Wer möchte kann auch eine Kombination von Slow- und Fast-Seeking einsetzen (das nennt sich dann Combined Seeking), die Details kann man auf der Wiki-Seite nachlesen.

Damit war es möglich die Dauer für das Extrahieren der 80 Screenshots eines Videos von 10 Minuten auf 5 Sekunden zu reduzieren. Es liegen immerhin rund zwei Größenordnungen zwischen den Werten – damit sind dann auch 30.000 Videos durchaus in vertretbarer Zeit abzuarbeiten.