Segfault bei PHPUnit vermeiden

Wir führen unsere Unittests automatisiert beim Push auf das Git-Repository mit Jenkins aus. Bevor Änderungen auf dem Livesystem eingespielt werden dürfen ist ein “grüner Build” zwingend erforderlich. Bei größeren Projekten kam es bei der Ausführung von PHPUnit immer wieder zu Segmentation Faults mit exec returned: 139. Beim zweiten Anlauf funktionierte es dann meistens, aber es ist natürlich sehr lästig, weil dadurch bei einer Dauer von ca. 30 Minuten für einen vollständigen Build das Livestellen unnötig verzögert wird.

Im Netz kursieren teils abenteuerliche Lösungen, die beispielsweise den automatischen Neustart von PHPUnit bei einem Segfault favourisieren. Klar, das ist besser als nichts, aber behebt das Problem nun mal nicht. Interessant fand ich daher den dritten Kommentar zu oben genanntem Artikel:

In phpunit you can do:
phpunit -d zend.enable_gc=0

Bei meinem ersten Versuch mit deaktivierter Garbage Collection wurde natürlich das memory_limit überschritten. Eine Erhöhung auf 1,5GB löste zwar das Problem mit dem Limit, jedoch brach daraufhin PHPUnit bei der Erzeugung des Clover-Reports unvermittelt ab. Diesmal mit exec returned: 143. Google förderte leider kaum brauchbare Ergebnisse zutage, jedoch lag die Vermutung nahe, dass es eben nicht so optimal ist, die Garbage Collection für den kompletten PHPUnit-Lauf zu deaktivieren.

Daraufhin haben wir eine eigene Zwischenklasse zwischen PHPUnit_Framework_TestCase und unsere TestCases geschoben, in der die Garbage Collection explizit gesteuert wird. Vor jedem Test wird die GC explizit mit gc_collect_cycles() aufgerufen, anschließend für den Test deaktiviert und nach dem Test wieder aktiviert:

<?php
abstract class Flagbit_Test_PHPUnit_TestCase extends PHPUnit_Framework_TestCase
{
    protected function setUp()
    {
        gc_collect_cycles();
        gc_disable();

        parent::setUp();
    }

    protected function tearDown()
    {
        parent::tearDown();

        gc_enable();
    }
}

Dies funktioniert jetzt seit einigen Tagen recht gut. Ob das Problem damit wirklich aus der Welt ist, wird sich in den nächsten Wochen zeigen!

UML-Diagramme aus Text mit PlantUML

Dieser Artikel erschien zuerst auf flagbit.de.

Jeder Entwickler weiß, dass Diagramme oft helfen, über Inhalte zu sprechen. Aber Lust sich in eines der grafischen Tools einzuarbeiten, hat man dann meist doch nicht. Jedes verwendet einen anderen Standard und andere Konventionen, um im Wiki darüber zu reden muss man das Diagramm mühsam als Bild exportieren und eigentlich schreibt man ja am liebsten Code und klickt eher ungern.

Wir haben bei bisher vereinzelt Visual Paradigm UML eingesetzt. An sich ist das Tool ganz brauchbar, vor allem wenn man Java entwickelt und das Round-Trip-Engineering funktioniert. Wir arbeiten aber eher punktuell mit UML-Diagrammen, d.h. meistens möchte man nur eine kleine Änderung machen oder nur ein Fragment der gesamten Software abbilden. Daher lohnt es sich nicht für jeden Entwickler eine Lizenz zu kaufen und wir haben deshalb eine Floating License. Funktioniert auch prima, solange man im Büro (und damit im Firmennetzwerk) ist. Aber sobald man unterwegs oder beim Kunden vor Ort ist kann man nicht “mal eben” das Diagramm anpassen. Außerdem braucht die Software relativ lang zum Starten, was gerade bei kleinen Änderungen auch lästig sein kann (und offen lassen will man das Programm ja auch nicht, weil man dann die Floating License blockiert).

Vor ein paar Wochen bin ich auf der Suche nach einem UML-Plugin für DokuWiki über PlantUML gestolpert. Mit PlantUML lassen sich (mit Hilfe von Graphviz) aus Text UML-Diagramme erstellen. So lässt sich folgendes Authentifzierungs-Sequenzdiagramm einfach mit ein paar Zeilen Code erzeugen: Sequenzdiagramm Authentication

@startuml
Alice -> Bob: Authentication Request
Bob --> Alice: Authentication Response
@enduml
Sequenzdiagramm Authentication

Neben Sequenzdiagrammen werden auch Use-Case-Diagramme, Klassendiagramme, Komponentendiagramme, Zustandsdiagramme und Objektdiagramme unterstützt. Außerdem gibt es Integrationen für MediaWiki, Redmine, Confluence, Trac, TinyMCE, Eclipse, NetBeans, Intellij (auch PHPStrom), Word, OpenOffice LaTeX, Doxygen, Sphinx und vieles mehr. Dadurch sind sowohl die Diagramme als auch das Syntax-Wissen portabel und vielseitig einsetzbar. Ich habe als nächstes direkt das Plugin für unser Confluence installiert und auch Sphinx (was wir gerade für Dokumentationen testen) funktioniert prima.

Mime-Type-Problem mit PHP Autoload

Dieser Artikel erschien zuerst auf flagbit.de.

Als ich mir heute mit PHP Autoload (phpab) meine Autoload-Datei neu generieren ließ, war ich bass erstaunt. Statt der üblichen 700 Klassen waren plötzlich nur noch 15 Dateien enthalten.

Nach ein bisschen Debuggen kam ich dann recht schnell dahinter, dass die Ursache in der Klasse \TheSeer\DirectoryScanner\PHPFilterIterator liegt:

<?php
public function accept() {
    $finfo = new \finfo(FILEINFO_MIME);
    return strpos($finfo->file($this->current()->getPathname()), 'text/x-php') === 0;
}

Dort wird nämlich der Mime-Type der Datei überprüft und nur Dateien mit Mime-Type text/x-php werden überhaupt nach Klassen durchsucht. Meine Dateien haben lustigerweise aber zum Großteil text/x-c++ – zumindest wenn ich mir das unter Windows anschaue. In meiner virtuellen Maschine haben die selben Dateien, die dann über vboxsf eingebunden sind, den korrekten Mime-Type.

Klar – ich kann das erstmal umgehen, indem im PHPFilterIterator ein return true einbaue; aber schön ist anders. Hat jemand einen Tipp wie man den Mime-Type einer Datei ändern kann oder noch interessanter: wie das Problem überhaupt entstanden sein könnte? Da es in der Linux-VM korrekt angezeigt wird ist ja vermutlich irgendwas an meinem Windows kaputtkonfiguriert…

ISO-8601-Stolperfalle bei Zend_Date

Jeder der mit Datumswerten gearbeitet hat, weiß (oder sollte zumindest) um die kleinen und großen Untiefen wie Zeitzonen, Zeitumstellung und Schaltjahre die es dabei zu umschiffen gilt. Neben der seit PHP 5.2 verfügbaren DateTime-Klasse gibt es im Zend Framework die ältere Zend_Date-Komponente. Mit beiden lässt sich die Handhabung von Datumswerten in einer Applikation vereinfachen und vereinheitlichen:

<?php
$date = new Zend_Date('01.01.2011', null, 'de_DE');
echo $date; // 01.01.2011 00:00:00

Soweit so gut. Formatieren kann man das Datum mit Zend_Date auch, zum Beispiel zur Verwendung in einem MySQL-Query:

<?php
$date = new Zend_Date('01.01.2011', null, 'de_DE');
echo $date->toString('yyyy-MM-dd HH:mm:ss'); // 2011-01-01 00:00:00

Bei der Verwendung der Formatcodes ist darauf zu achten “yyyy” und das ähnlich aussehende “YYYY” keinesfalls zu verwechseln! Beide geben zwar das Jahr aus, aber mit einem kleinen, aber gewichtigen Unterschied.

Intern verwendet Zend_Date zur Formatierung die date-Funktion und konvertiert die Zend_Date-Formatcodes entsprechend. Während “yyyy” zu “Y” konvertiert wird und das erwartete Ergebnis liefert, wird “YYYY” zu “o” umgewandelt. Und “o” hat folgende Bedeutung:

ISO-8601 year number. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead. (added in PHP 5.1.0)
php.net

Da die erste Tage im Jahr 2011 noch zur letzten Kalenderwoche des Jahres 2010 gehören, wird gemäß ISO 8601 das Jahr 2010 ausgegeben. Und das dürfte in den seltesten Fällen das sein, was man möchte…

PHP 5.4: Die wichtigsten Änderungen

Dieser Artikel erschien zuerst auf flagbit.de.

Vor einigen Tagen war es soweit: PHP 5.4 wurde veröffentlicht. Die Anzahl der Änderungen sind, im Vergleich mit PHP 5.3, überschaubar, aber dennoch sollte man sie kennen. Die wichtigsten Änderungen kurz zusammengefasst:

PHP 5.4 - So what?! from flagbit

Einige Code-Beispiele zu den Änderungen sind im zugehörigen Git-Repository: https://bitbucket.org/flagbit/php54.

Die Informationen zu Traits und dem Diamond-Problem sind im seperaten PDF.

Vorsicht bei der Verwendung von isset() mit Arrays

Manchmal sucht man Fehler, die man wieder und wieder überliest, weil man die falschen Annahmen getroffen hat und der Code oberflächlich richtig aussieht. Erfahrungsgemäß gibt es dann ein paar “übliche Verdächtige”, die man sich genauer anschaut. Zuweisungen im if-Statement, Vergleiche ohne Typprüfung, empty() und isset() sind dabei immer heiße Kandidaten.

Wenn man die Handbuch-Seite zu isset() durchliest, klingt es, als wäre es problemlos möglich auch die Existenz von Array-Keys zu prüfen. Sogar ein schönes Beispiel gibt es dazu:

<?php
$a = array (
    'test' => 1,
    'hello' => NULL,
    'pie' => array('a' => 'apple'),
);
 
var_dump(isset($a['test']));            // TRUE
var_dump(isset($a['foo']));             // FALSE
var_dump(isset($a['hello']));           // FALSE
 
// The key 'hello' equals NULL so is considered unset
// If you want to check for NULL key values then try: 
var_dump(array_key_exists('hello', $a)); // TRUE
 
// Checking deeper array values
var_dump(isset($a['pie']['a']));        // TRUE
var_dump(isset($a['pie']['b']));        // FALSE
var_dump(isset($a['cake']['a']['b']));  // FALSE

Doch ganz so einfach ist es leider nicht. Interessant ist hier vor allem die letzte Zeile. Erst auf den zweiten Blick fällt auf, dass als erster Schlüssel “cake” statt “pie” verwendet wird. Und das ist der einzige Grund dafür, dass FALSE zurückgegeben wird. Ändert man nämlich den ersten Schlüssel auf “pie”, wird TRUE zurückgegeben, auch wenn der dritte Key nicht existiert:

<?php
$a = array(
    'test' => 1,
    'hello' => NULL,
    'pie' => array('a' => 'apple'),
);
 
var_dump(isset($a['pie']['a']['b']));  // TRUE

Das Problem und die Lösung wird auch in einem Kommentar von 2004 angesprochen. Die Prüfung mit isset() reicht nicht aus. Zusätzlich muss auf den vorletzen Key mit is_array() geprüft werden:

<?php
$a = array (
    'test' => 1,
    'hello' => NULL,
    'pie' => array('a' => 'apple'),
);
 
var_dump(isset($a['pie']['a']['b']) && is_array($a['pie']['a']));  // FALSE

Wichtig ist hier auch, dass man zuerst das isset() und danach das is_array() schreibt, sonst bekommt man einen Undefined-index-Fehler, wenn der Schlüssel komplett nicht vorhanden ist. Ein alleinstehendes isset() in Verbindung mit einem Array sollte einen also immer misstrauisch machen. Je nach Fall sollte ein is_array() ergänzt oder ein array_key_exists() bevorzugt werden.

Grep für Application-Logs

Bei der Fehlersuche bieten einem die Application-Logs oftmals einen guten Einstiegspunkt. Doch die Suche darin kann zum Teil recht mühsam sein. Vor allem wenn man nicht nach der Fehlermeldung selbst suchen will, sondern zum Beispiel alle Fehler haben will, die einen bestimmten Methodenaufruf im Stacktrace haben. Und dann wollte ich auch nicht nur die Zeile haben, sondern den ganzen Log-Eintrag, inklusive Message und komplettem Stacktrace.

Ich habe mir dafür ein kleines PHP-Skript geschrieben. Vielleicht findet es ja sonst jemand nützlich:

<?php
// loggrep.php 2011-12-08
if (2 !== $argc && 3 !== $argc) {
    echo "USAGE: {$argv[0]} <pcre-pattern> <file>\n";
    exit(1);
}
 
$filename = 'php://stdin';
if (isset($argv[2])) {
    $filename = $argv[2];
}
$fp = fopen($filename, 'r');
 
// "2011-11-22T02:16:34+01:00 - "
$startPattern = '#^\d{4}(-\d{2}){2}T\d{2}(:\d{2}){2}\+\d{2}:\d{2} - #';
$searchPattern = $argv[1];
 
$found = false;
 
while ($line = fgets($fp)) {
    if (preg_match($startPattern, $line)) {
        if (true === $found) {
            foreach ($buffer as $bufferedLine) {
                echo $bufferedLine;
            }
        }
 
        $buffer = array();
        $found = false;
    }
 
    if (preg_match($searchPattern, $line)) {
        $found = true;
    }
 
    $buffer[] = $line;
}

Der Aufruf sieht dann so aus:

$ php loggrep "#Mage_Core_Block_Template#" exception.log

Wichtig ist, dass der Suchbegriff mit PCRE-Syntax (inklusive Delimiter) angegeben wird.

Update 08.11.2011

Jetzt neu: Falls keine Datei angegeben wird, kann das Skript auch von STDIN lesen. Damit kann man auch einfach in gepackten Dateien suchen:

$ zcat exception.log.1.gz | php loggrep "#Mage_Core_Block_Template#"

Zend_Cache Backends

Dieser Artikel erschien zuerst auf flagbit.de.

Hier war in den letzten Monaten mal wieder Beitrags-Flaute, was vor allem daran liegt, dass ich seit etwa vier Monaten an einem Zend-Framework-Projekt entwickle und nur noch sporadisch mit Magento zu tun habe. Deshalb geht es auch heute nicht um Magento direkt, sondern um die Cache-Komponente von Zend Framework. Magento-Entwickler müssen jetz nicht gleich aufhören zu lesen, denn Zend_Cache bildet auch die Basis für den Cache in Magento.

Read More

Verkürzte URLs auflösen

Die verkürzten URLs von tinyurl und Konsorten mögen ja ganz nett sein, wenn man twittern will, aber ansonsten gleicht das meine Meinung nach eher einem Minenfeld: keiner weiß so genau was einen da erwartet.

Genau dieses Problem geht das Firefox-Addon Long URL Please an. Damit werden die verkürzten URLs aufgelöst und in der Originalform angezeigt.

Wer übrigens behauptet, das wäre auch praktisch um das Layout bei langen Links zu erhalten verwendet nur schlechte Software. MusicBrainz! verkürzt den angezeigten Link nämlich einfach, was der Funktion und Transparenz keinen Abbruch tut, da der vollständige Link ja immernoch vor'm draufklicken in der Statusleiste angezeigt wird.

Verlauf für neue Tabs

Mit ging es schon oft so, dass ich einen Artikel oder ähnliches gelesen habe und dabei die interessanten Links in neuen Tabs im Hintergrund geöffnet habe. Am Ende des Artikels schließe ich dann den ursprünglichen Tab und lese in den neuen Tabs weiter. Wenn ich dann nochmal zurück will, geht das normalerweise nur über den "normalen" Verlauf und nicht über die Zurück-Taste im Browserfenster, weil die Tabs ja neu sind und somit keinen Verlauf haben.

Genau dieses Verhalten wird durch die Firefox-Erweiterung Tab History geändert. Neue Tags "erben" damit den Verlauf des öffnenden Tabs und somit lässt sich auch in den neuen Tabs Vergangenes einfach über den Zurück-Knopf zurückholen.