9 Kontrollstrukturen

Kontrollstrukturen sind Sprachkonstrukte, die den Ausführungsfluss eines Programms steuern. Sie ermöglichen es, basierend auf bestimmten Bedingungen Code auszuführen, Code zu wiederholen oder zwischen verschiedenen Codeblöcken zu wählen. PHP bietet eine umfassende Sammlung von Kontrollstrukturen, die in diesem Abschnitt behandelt werden.

9.1 Bedingte Anweisungen

9.1.1 if-Anweisung

Die if-Anweisung ist die grundlegendste Kontrollstruktur und führt einen Codeblock aus, wenn eine Bedingung wahr ist:

<?php
$alter = 25;

if ($alter >= 18) {
    echo "Sie sind volljährig.";
}
?>

9.1.2 if-else-Anweisung

Mit else kann ein alternativer Codeblock ausgeführt werden, wenn die Bedingung falsch ist:

<?php
$alter = 15;

if ($alter >= 18) {
    echo "Sie sind volljährig.";
} else {
    echo "Sie sind minderjährig.";
}
?>

9.1.3 if-elseif-else-Anweisung

Mit elseif (oder else if) können mehrere Bedingungen nacheinander geprüft werden:

<?php
$alter = 15;

if ($alter < 13) {
    echo "Sie sind ein Kind.";
} elseif ($alter < 18) {
    echo "Sie sind ein Teenager.";
} elseif ($alter < 65) {
    echo "Sie sind ein Erwachsener.";
} else {
    echo "Sie sind ein Senior.";
}
?>

9.1.4 Alternative Syntax für Templates

PHP bietet eine alternative Syntax für Kontrollstrukturen, die besonders nützlich für Template-Dateien ist, in denen PHP und HTML gemischt werden:

<?php if ($loggedIn): ?>
    <h1>Willkommen zurück, <?= htmlspecialchars($username) ?>!</h1>
<?php elseif ($registered): ?>
    <h1>Bitte loggen Sie sich ein.</h1>
<?php else: ?>
    <h1>Bitte registrieren Sie sich.</h1>
<?php endif; ?>

Diese Syntax verwendet Doppelpunkte (:) anstelle der öffnenden Klammer und schließende Tags wie endif, endforeach, endwhile usw. anstelle der schließenden Klammer.

9.2 Switch-Anweisung

Die switch-Anweisung ist eine Alternative zu mehreren if-elseif-Bedingungen, wenn ein einzelner Wert mit verschiedenen Möglichkeiten verglichen wird:

<?php
$rolle = "redakteur";

switch ($rolle) {
    case "admin":
        echo "Vollständiger Zugriff";
        break;
    case "redakteur":
        echo "Kann Inhalte bearbeiten";
        break;
    case "autor":
        echo "Kann Inhalte erstellen";
        break;
    case "benutzer":
        echo "Kann Inhalte lesen";
        break;
    default:
        echo "Kein Zugriff";
        break;
}
?>

9.2.1 Wichtige Hinweise zu switch

  1. Jeder case-Block sollte mit einer break-Anweisung enden, sonst wird die Ausführung in den nächsten case fortgesetzt (Fall-Through).
  2. Der default-Block wird ausgeführt, wenn kein case übereinstimmt.
  3. switch verwendet lose Vergleiche (==), keine strikten Vergleiche (===).
  4. Mehrere case-Werte können für denselben Codeblock verwendet werden:
<?php
$tag = 5;

switch ($tag) {
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
        echo "Arbeitstag";
        break;
    case 6:
    case 7:
        echo "Wochenende";
        break;
    default:
        echo "Ungültiger Tag";
}
?>

9.3 Match-Ausdruck (ab PHP 8.0)

Der match-Ausdruck, eingeführt in PHP 8.0, ist eine moderne Alternative zu switch mit einigen wichtigen Unterschieden:

<?php
$status = 404;

$message = match ($status) {
    200, 201, 202 => "Erfolg",
    400, 401, 403 => "Client-Fehler",
    404 => "Nicht gefunden",
    500 => "Server-Fehler",
    default => "Unbekannter Status"
};

echo $message; // "Nicht gefunden"
?>

9.3.1 Unterschiede zwischen match und switch

  1. match verwendet strikte Vergleiche (===), während switch lose Vergleiche (==) verwendet.
  2. match ist ein Ausdruck und gibt einen Wert zurück.
  3. match benötigt keine break-Anweisungen.
  4. match wertet nur den passenden Arm aus.
  5. match wirft einen UnhandledMatchError, wenn kein Arm übereinstimmt und kein default vorhanden ist.

9.3.2 Ausdrücke in match-Bedingungen

Mit match können auch komplexere Bedingungen ausgewertet werden:

<?php
$alter = 25;

$kategorie = match (true) {
    $alter < 13 => "Kind",
    $alter < 18 => "Teenager",
    $alter < 65 => "Erwachsener",
    default => "Senior"
};

echo $kategorie; // "Erwachsener"
?>

9.4 Schleifen

Schleifen werden verwendet, um Code wiederholt auszuführen.

9.4.1 while-Schleife

Die while-Schleife führt einen Codeblock aus, solange eine Bedingung wahr ist:

<?php
$i = 1;

while ($i <= 5) {
    echo $i . " ";
    $i++;
}
// Ausgabe: 1 2 3 4 5
?>

9.4.2 do-while-Schleife

Die do-while-Schleife ist ähnlich wie die while-Schleife, aber die Bedingung wird erst nach der Ausführung des Codeblocks geprüft. Dadurch wird der Codeblock mindestens einmal ausgeführt:

<?php
$i = 1;

do {
    echo $i . " ";
    $i++;
} while ($i <= 5);
// Ausgabe: 1 2 3 4 5

// Auch wenn die Bedingung anfangs falsch ist, wird der Block einmal ausgeführt
$j = 10;
do {
    echo "Dieser Text wird ausgegeben, obwohl j > 5";
} while ($j <= 5);
?>

9.4.3 for-Schleife

Die for-Schleife ist kompakt und wird verwendet, wenn die Anzahl der Iterationen im Voraus bekannt ist:

<?php
for ($i = 1; $i <= 5; $i++) {
    echo $i . " ";
}
// Ausgabe: 1 2 3 4 5
?>

Die for-Schleife besteht aus drei Teilen: 1. Initialisierung ($i = 1) 2. Bedingung ($i <= 5) 3. Inkrement/Dekrement ($i++)

Jeder Teil kann leer sein oder mehrere Ausdrücke enthalten (getrennt durch Kommas):

<?php
for ($i = 0, $j = 10; $i < 10; $i++, $j--) {
    echo "i: $i, j: $j <br>";
}
?>

9.4.4 foreach-Schleife

Die foreach-Schleife ist speziell für die Iteration durch Arrays und objekte optimiert:

<?php
// Einfaches Array
$farben = ["rot", "grün", "blau"];

foreach ($farben as $farbe) {
    echo $farbe . " ";
}
// Ausgabe: rot grün blau

// Assoziatives Array mit Schlüssel-Wert-Paaren
$person = [
    "name" => "Max Mustermann",
    "alter" => 30,
    "beruf" => "Entwickler"
];

foreach ($person as $schlüssel => $wert) {
    echo "$schlüssel: $wert <br>";
}
// Ausgabe:
// name: Max Mustermann
// alter: 30
// beruf: Entwickler
?>

9.4.5 Verschachtelte Schleifen

Schleifen können ineinander verschachtelt werden:

<?php
// Multiplikationstabelle von 1 bis 5
for ($i = 1; $i <= 5; $i++) {
    for ($j = 1; $j <= 5; $j++) {
        echo $i * $j . "\t";
    }
    echo "<br>";
}
?>

9.5 Schleifensteuerung

PHP bietet Anweisungen zur Steuerung des Schleifenverhaltens:

9.5.1 break

Die break-Anweisung beendet die aktuelle Schleife sofort:

<?php
for ($i = 1; $i <= 10; $i++) {
    if ($i == 5) {
        break; // Beendet die Schleife, wenn $i 5 erreicht
    }
    echo $i . " ";
}
// Ausgabe: 1 2 3 4
?>

Bei verschachtelten Schleifen beendet break standardmäßig nur die innerste Schleife. Mit einem numerischen Argument kann jedoch angegeben werden, wie viele Schleifenebenen beendet werden sollen:

<?php
for ($i = 1; $i <= 3; $i++) {
    echo "i: $i <br>";
    for ($j = 1; $j <= 3; $j++) {
        if ($i == 2 && $j == 2) {
            break 2; // Beendet sowohl die innere als auch die äußere Schleife
        }
        echo "j: $j <br>";
    }
}
?>

9.5.2 continue

Die continue-Anweisung überspringt den Rest des aktuellen Schleifendurchlaufs und setzt mit dem nächsten Durchlauf fort:

<?php
for ($i = 1; $i <= 10; $i++) {
    if ($i % 2 == 0) {
        continue; // Überspringt gerade Zahlen
    }
    echo $i . " ";
}
// Ausgabe: 1 3 5 7 9
?>

Wie bei break kann auch bei continue ein numerisches Argument angegeben werden, um mehrere Schleifenebenen zu überspringen:

<?php
for ($i = 1; $i <= 3; $i++) {
    for ($j = 1; $j <= 3; $j++) {
        if ($j == 2) {
            continue 2; // Springt zur nächsten Iteration der äußeren Schleife
        }
        echo "$i-$j<br>";
    }
}
// Ausgabe:
// 1-1
// 2-1
// 3-1
?>

9.6 Alternative Kontrollstrukturen

9.6.1 goto (mit Vorsicht zu verwenden)

Die goto-Anweisung ermöglicht Sprünge im Code zu benannten Marken:

<?php
$i = 0;

start:
$i++;
echo $i . " ";

if ($i < 5) {
    goto start;
}
// Ausgabe: 1 2 3 4 5
?>

Hinweis: Die Verwendung von goto wird generell nicht empfohlen, da es die Lesbarkeit und Wartbarkeit des Codes beeinträchtigen kann. Es gibt fast immer bessere strukturierte Alternativen.

9.6.2 Bedingte Ausführung mit dem ternären Operator

Der ternäre Operator bietet eine kompakte Alternative zur if-else-Anweisung für einfache Zuweisungen:

<?php
$alter = 20;
$status = ($alter >= 18) ? "volljährig" : "minderjährig";
echo $status; // "volljährig"

// Verschachtelte ternäre Operatoren (mit Vorsicht zu verwenden)
$alter = 15;
$status = ($alter < 13) ? "Kind" : (($alter < 18) ? "Teenager" : "Erwachsener");
echo $status; // "Teenager"
?>

9.6.3 Null Coalescing Operator

Der Null Coalescing Operator (??) ist eine kompakte Alternative zur isset()-Prüfung:

<?php
// Früher:
$username = isset($_GET['user']) ? $_GET['user'] : 'Gast';

// Ab PHP 7:
$username = $_GET['user'] ?? 'Gast';

// Verkettung mehrerer Prüfungen
$username = $_GET['user'] ?? $_POST['user'] ?? $_SESSION['user'] ?? 'Gast';
?>

9.7 Kontrollstrukturen und HTML

Die Mischung von PHP-Kontrollstrukturen und HTML ist besonders im Web-Kontext üblich:

<!DOCTYPE html>
<html>
<head>
    <title>PHP Kontrollstrukturen mit HTML</title>
</head>
<body>
    <h1>Benutzerliste</h1>
    
    <?php if (empty($benutzer)): ?>
        <p>Keine Benutzer gefunden.</p>
    <?php else: ?>
        <ul>
            <?php foreach ($benutzer as $user): ?>
                <li>
                    <strong><?= htmlspecialchars($user['name']) ?></strong>
                    <?php if ($user['admin']): ?>
                        <span>(Administrator)</span>
                    <?php endif; ?>
                </li>
            <?php endforeach; ?>
        </ul>
    <?php endif; ?>
</body>
</html>

9.8 Rekursion

Rekursion ist eine Technik, bei der eine Funktion sich selbst aufruft. Dies kann eine elegante Lösung für bestimmte Probleme sein:

<?php
// Berechnung der Fakultät einer Zahl
function fakultaet($n) {
    if ($n <= 1) {
        return 1;
    } else {
        return $n * fakultaet($n - 1);
    }
}

echo fakultaet(5); // 120 (5 * 4 * 3 * 2 * 1)

// Rekursives Durchlaufen einer verschachtelten Array-Struktur
function arrayDurchlaufen($array, $indent = 0) {
    foreach ($array as $schluessel => $wert) {
        echo str_repeat("--", $indent) . " $schluessel: ";
        
        if (is_array($wert)) {
            echo "<br>";
            arrayDurchlaufen($wert, $indent + 1);
        } else {
            echo "$wert<br>";
        }
    }
}

$daten = [
    "Person" => [
        "Name" => "Max Mustermann",
        "Kontakt" => [
            "Email" => "max@example.com",
            "Telefon" => "0123456789"
        ]
    ],
    "Beruf" => "Entwickler"
];

arrayDurchlaufen($daten);
?>

Hinweis: Bei rekursiven Funktionen ist es wichtig, eine Abbruchbedingung zu definieren, um endlose Rekursionen zu vermeiden. PHP hat ein Standard-Limit für die Rekursionstiefe, das mit ini_set('xdebug.max_nesting_level', 200) angepasst werden kann.

9.9 Benutzerdefinierte Kontrollstrukturen mit Funktionen

Manchmal ist es sinnvoll, eigene “Kontrollstrukturen” durch Funktionen zu definieren:

<?php
// Wiederholte Ausführung mit bedingtem Abbruch
function retry($callback, $maxVersuche = 3, $pause = 1) {
    $versuche = 0;
    $ergebnis = null;
    $erfolg = false;
    
    while (!$erfolg && $versuche < $maxVersuche) {
        $versuche++;
        
        try {
            $ergebnis = $callback();
            $erfolg = true;
        } catch (Exception $e) {
            echo "Versuch $versuche fehlgeschlagen: " . $e->getMessage() . "<br>";
            
            if ($versuche < $maxVersuche) {
                sleep($pause);
            }
        }
    }
    
    if (!$erfolg) {
        throw new Exception("Alle Versuche fehlgeschlagen");
    }
    
    return $ergebnis;
}

// Verwendung
try {
    $daten = retry(function() {
        // Simulierte API-Anfrage
        if (rand(0, 1)) {
            throw new Exception("Netzwerkfehler");
        }
        return "Daten erfolgreich abgerufen";
    });
    
    echo $daten;
} catch (Exception $e) {
    echo "Fehler: " . $e->getMessage();
}
?>

9.10 Bewährte Praktiken

  1. Verschachtelung begrenzen: Tief verschachtelte Kontrollstrukturen machen den Code schwer lesbar. Extrahieren Sie komplexe Logik in separate Funktionen.

  2. Alternative Syntax in Templates: Verwenden Sie die alternative Syntax (if:, endif;, foreach:, endforeach; usw.) in Templates, um die Lesbarkeit zu verbessern.

  3. Frühe Rückgabe: Prüfen Sie ungültige Zustände am Anfang einer Funktion und kehren Sie früh zurück, um die Verschachtelungstiefe zu reduzieren.

    <?php
    // Vermeiden:
    function getUserName($userId) {
        if ($userId) {
            $user = fetchUser($userId);
            if ($user) {
                return $user->name;
            } else {
                return null;
            }
        } else {
            return null;
        }
    }
    
    // Besser:
    function getUserName($userId) {
        if (!$userId) {
            return null;
        }
    
        $user = fetchUser($userId);
        if (!$user) {
            return null;
        }
    
        return $user->name;
    }
    ?>
  4. Bedingungen extrahieren: Komplexe Bedingungen in Variablen oder Funktionen extrahieren, um die Lesbarkeit zu verbessern.

    <?php
    // Vermeiden:
    if ($user->status === 'active' && $user->email_verified && $user->hasRole('customer') && !$user->isBanned) {
        // ...
    }
    
    // Besser:
    $isEligibleCustomer = $user->status === 'active' && 
                          $user->email_verified && 
                          $user->hasRole('customer') && 
                          !$user->isBanned;
    
    if ($isEligibleCustomer) {
        // ...
    }
    ?>
  5. Sorgfältiger Umgang mit switch und goto: Vermeiden Sie Fall-Through-Verhalten bei switch (vergessene break-Anweisungen) und verwenden Sie goto nur, wenn es wirklich notwendig ist.

  6. Rekursionstiefe kontrollieren: Achten Sie bei rekursiven Funktionen auf die maximale Rekursionstiefe und bevorzugen Sie, wenn möglich, iterative Lösungen.