PHP.de Wissenssammlung

Code-Smells

Es gibt eine Reihe von Unsitten, die offenbar in veralteten Lehrbüchern auftauchen oder sich durch Copy & Paste im Laufe der Jahre im Internet exponentiell verbreitet haben.

Dieser Artikel richtet sich an Spracheinsteiger, Fortgeschrittene können bei Wikipedia einige Beispiele zu Code-Smells finden.

Achtung! Der Artikel nutzt reduzierte Lehrbeispiele. Der Übersichtlichkeit halber können wichtige Funktionen zur Eingabevalidierung o. ä. weggelassen worden sein.

Leere Strings

Problem

Code-Smells mit leeren Strings

// unsinnig
$foo = '' . $bar;
$baz = "" . $bar . "";
$query = "SELECT myfield FROM mytable WHERE id=" . $id . "";

// halb-sinnig
$myString = '' . $myInt;

Ersatz

Leere Strings sind ausnahmslos zu streichen.

$foo = $bar;
$baz = $bar;
$query = "SELECT myfield FROM mytable WHERE id=" . $id;

Wenn es darum geht, andere Typen nach String zu casten, sollte explizites Typ-Casting verwendet werden.

// explizites Type-Casting
$myString = (string)$myInt;

Unnötiges Variablen-Parsing in Doppelquotes

PHP unterstützt die Verwendung von Variablen innnerhalb von doppelten Anführungszeichen. Dort befindliche Variablen werden in Ihren Wert aufgelöst:

$foo = 12;
echo "Mein Hut der hat $foo Ecken";

Problem

Code-Smells mit unnützen Stringsquotes

Weit verbreitet ist diese unsinnige Variante:

$foo = 'Zwerg';
echo "$foo"; // Zwerg

// halb-sinnig
$myInt = 17;
$stringvar = "$myInt";
var_dump($stringvar); //(string) 17

Die Stringbegrenzer erfüllen hier keinen Zweck - sie umschließen kein weiteres Zeichen außer dem Variableninhalt. Im Gegenteil veranlassen sie PHP zu unnötiger Arbeit, dem Einbetten einer Variable in einen String, der dann wiederum geparst wird.

Ersatz

Solche Konstrukte sind gegen die alleinstehende Variable auszutauschen.

$foo = 'Zwerg';
echo $foo; // Zwerg

$myInt = 17;
$stringvar = (string)$myInt;

Wenn es darum geht, andere Typen nach String zu casten, sollte explizites Typ-Casting verwendet werden (siehe oben).

SELECT *

Problem

Code-Smells mit *-Select

$query = "SELECT * FROM Personen";

Aus der Datenbanktabelle wird hier stets jedes Feld der Zeile abgefragt. Oft ist das gar nicht nötig, weil nur ein Teil der Felder verarbeitet wird. Zudem sagt das Statement nichts darüber aus, welche Werte es liefert. Kritisch wird es, wenn sich die Tabellenstruktur ändert - Folgefehler (Zugriff auf nicht mehr existente Feldnamen) oder das Auslesen von unnützen Daten (Text, Blob) kann die Folge der *-Konvention sein.

Ersatz

Es sind immer die Namen der Felder anzugeben. In Hinsicht auf Keyword-Probleme ist es sinnvoll, dabei Backticks zu verwenden.

Was dieses Select liefert, ist klar ersichtlich

$query = "SELECT `Id` , `Name` , `E-Mail` FROM Personen";

Übrigens existiert dieses Problem auch vertikal: Wer sich sicher ist, dass eine Zeile mit einer bestimmten WHERE-Bedingung nur einmal vorkommen kann, hat sicher kein Problem damit, ein LIMIT 1 zu ergänzen.

LIMIT vs. PHP-Counting

Problem

Code-Smells mit Limit

// Fehlerbehandlung wurde hier mal weggelassen
$mysqli = mysqli_connect('localhost', 'user', 'password', 'database');

$ress = mysqli_query($mysqli, "SELECT `Id` , `Name` FROM Personen");
$cnt = 0;
while ($data = mysqli_fetch_assoc($ress)) {
    if ($cnt > 3) {
        break;
    }
    echo $data['Id'] , ', ' , $data['Name'] , '<br />';
    $cnt++;
}

Hier bricht PHP nach 3 Ausgaben das Auslesen der Datenbank ab. Diese hat allerdings im Vorfeld alle Personendatensätze zusammengestellt, und seien es 50000.

Ersatz

Wo immer möglich ist ein LIMIT für die Querymenge anzugeben. Die Datenbank kann dann die Anfrage entsprechend optimieren, liefert auch immer gleich die passende Menge und erspart damit auch PHP-seitige Handstände.

Na, wer hat jetzt die Arbeit?

// Fehlerbehandlung wurde hier mal weggelassen
$mysqli = mysqli_connect('localhost', 'user', 'password', 'database');

$ress = mysqli_query($mysqli, "SELECT `Id` , `Name` FROM Personen LIMIT 3");
while ($data = mysqli_fetch_assoc($ress)) {
    echo $data['Id'] , ', ' , $data['Name'] , '<br />';
}

LIMIT und Schleife

Problem

Code-Smells mit auslesenden Schleifen

Bei Datenbankabfrage, die definitiv nur einen Datensatz liefern, wird oft die übliche Form des Auslesenes verwendet:

// Fehlerbehandlung wurde hier mal weggelassen
$mysqli = mysqli_connect('localhost', 'user', 'password', 'database');

$ress = mysqli_query($mysqli, "SELECT `ID` , `User` , `Password`
                FROM   Login
                WHERE  `User` = '" . mysqli_real_escape_string($mysqli, $_POST['user']) . "'
                LIMIT  1");

$auth = false;

while ($data = mysqli_fetch_assoc($ress)) {
    if ($_POST['pass'] == $data['Password']) {
        $auth = true;
    }
}

if (!$auth) {
    echo 'Die Anmeldung ist fehlgeschlagen.';
}

Sowohl LIMIT 1, als auch eine sinnvolle Scriptlogik - in einem Loginprozess sollte es nur einen, datenbankweit eindeutigen Nutzernamen geben können (Primärschlüssel) - begrenzen hier die maximale Menge an Datensätzen auf 1. while (.. fetch()) ist dagegen ein Codefragment, um alle Datensätze einer Anfrage auszulesen.

Ersatz

Die meisten Nutzer wissen gar nicht, was das while hier überhaupt tut. Kurz gesagt werden hier per Schleife solange Datensätze von der Datenbank angefordert, bis die Datenbank FALSE für “keine weiteren Datensätze” zurückliefert. Die Schleife läuft dadurch, dass diese Rückgabe die Schleifenbedingung bildet.

Mit diesem Wissen können wir einen Ersatz für Fälle schaffen, in denen definitiv nur ein Datensatz erwartet wird oder nur einer ausgelesen werden soll (vgl. dazu aber obige Aussagen zu LIMIT).

Hier ist eindeutiger, was passiert

// ... Anfrage von oben
$auth = false;

if ($data = mysqli_fetch_assoc($ress)) {
    if ($_POST['pass'] == $data['Password']) {
        $auth = true;
    }
}

if (!$auth) {
    echo 'Die Anmeldung ist fehlgeschlagen.';
}

Wir benutzen ein If, das hier einen ähnlichen Effekt wie die Schleife erfüllt: Der Datensatz wird geholt, wenn kein false geliefert wird, wird der Block durchlaufen und $data ausgewertet und verarbeitet. Die Lösung gaukelt uns jetzt keine Schleifenbedingung vor, die sowieso nur max. einmal durchläuft sondern bildet klar ersichtlich die Funktionalität ab.

Dieser Beitrag ist fertiggestellt und wurde zuletzt von hausl bearbeitet.

An diesem Beitrag waren bisher beteiligt: nikosch, hausl