Best Practice: Alphabetische Liste in PHP (z. B. für ein Glossar)
Geschrieben vor elf Jahre und zehn Monate.
Die Lesezeit beträgt etwa drei Minuten und zwölf Sekunden.
Für ein Projekt benötige ich eine alphabetische Liste, wie man sie gerne in einem Glossar benutzt. Im Code des von mir favorisierten Plug-ins bin ich auf ein „Problem“ gestoßen, welches ich vor einiger Zeit erst meinem Auszubildenden erklärt habe.
Die Aufgabe
Wir benötigen ein Array, welches den ersten Buchstaben aller in einer Datenbank stehenden Namen in alphabetischer Reihenfolge enthält. Mit diesen Daten wird eine alphabetische Liste ähnlich der folgenden von Bundestag.de realisiert:
Der Code
In $oSql
haben wir ein mysqli-Objekt mit einer erfolgreichen Verbindung zur Datenbank zur Verfügung. PDO funktioniert natürlich auch.
Der folgende Code ist nicht das Original. Zur besseren Ansicht habe ich diesen vereinfacht. Das Vorgehen entspricht jedoch exakt dem des Plug-ins:
if ($oRes = $oSql->query('SELECT name FROM users')) {
$aAlphabet = array();
while ($aRow = $oRes->fetch_object()) {
array_push($aAlphabet, substr($aRow->name, 0, 1));
}
}
$aAlphabet = array_unique($aAlphabet);
sort($aAlphabet);
print_r($aAlphabet);
Der Autor der Erweiterung holt alle Namen aus der Datenbank. Anschließend wird ein leeres Array $aAlphabet
definiert. Bei jedem Durchlauf des Ergebnisses der Datenbankabfrage fügt er mit array_push
dem zuvor definierten Array den ersten Buchstaben des Ergebnisses hinzu. Dieser Buchstabe wird via substr()
abgeschnitten. Danach wird das Array via array_unique()
von doppelten Einträgen befreit und via sort()
sortiert.
Mit meinen Testdaten (nur 100 Namen in der Datenbank) dauern 100.000 Durchläufe 84.3804 Sekunden.
Nun läuft dieser Code normalerweise nur ein Mal und nicht 100.000 Mal durch. Was bedeutet, der Autor ist hier zwar am Ziel und hat die Aufgabe gelöst, aber man kann die Sache wie immer verbessern. Die folgende Variante lagert die meiste Arbeit in die Datenbankabfrage aus und spart außerdem einige Zeilen Code:
if ($oRes = $oSql->query('SELECT DISTINCT(LEFT(UPPER(name), 1)) AS alphabet FROM users ORDER BY name ASC')) {
$aAlphabet = array();
while ($aRow = $oRes->fetch_object()) {
$aAlphabet[] = $aRow->alphabet;
}
}
print_r($aAlphabet);
Statt die vollständige Namen holen wir mit LEFT(name, 1)
nur den ersten Buchstaben aus der Datenbank. Dies ersetzt die PHP-Funktion substr()
. UPPER()
sorgt dafür, dass das Ergebnis in Großbuchstaben zurückgegeben wird. DISTINCT()
entfernt uns die doppelten Einträge und erspart ein array_unique()
. ORDER BY name ASC
sortiert direkt nach dem Alphabet und macht sort()
überflüssig. Außerdem verzichten wir auf die Funktion array_push()
und nutzen $aAlphabet[] =
, um das Element dem Array anzuhängen.
Diese Variante dauerte 59.7660 Sekunden und ist damit knapp 41 % schneller als die vorherige Version.
Und wenn wir schon dabei sind …
Immer mal wieder sehe ich folgende Zeile im Code (ebenfalls im erwähnten Plug-in):
$aAlphabet = array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z');
Hier kann man sich einiges an Schreibarbeit sparen, wenn folgende Funktion genutzt wird:
$aAlphabet = range('A', 'Z');
Hier sei erwähnt, dass ein Funktionsaufruf von range()
langsamer ist als das direkte Definieren des Arrays. Dies ist aber minimal, außerdem wird solch ein Array normalerweise nur ein Mal definiert.