``` PHP – Der Komplett-Guide

PHPDer Komplett-Guide

Von den ersten Zeilen bis zu Architektur, Patterns und Experten-Nischen. Modernes PHP 8.5 – gründlich erklärt, mit kurzem, lauffähigem Code.
8 Teile · 46 Kapitel PHP 8.5 · Stand 2026 Schwerpunkt: Sprache & OOP
<?php

Inhalt

Teil 1 · Grundlagen
1PHP einrichten & ausführen
2Variablen & Datentypen
3Operatoren & Ausdrücke
4Strings im Detail
5Bedingungen & Verzweigungen
6Schleifen
7Arrays
8Funktionen
Teil 2 · Struktur & Werkzeug
9Code aufteilen: include & require
10Fehler & Exceptions
11Datum & Zeit
12Dateien lesen & schreiben
13JSON & Datenformate
14Composer & Autoloading
Teil 3 · Typsystem & moderne Features
15Typen & strict_types
16Union-, Nullable- & spezielle Typen
17Enums
18Moderne Syntax-Schmankerl
Teil 4 · Objektorientierung
19Klassen & Objekte
20Sichtbarkeit & Kapselung
21Konstruktoren modern
22Vererbung
23Abstrakte Klassen & Interfaces
24Traits
25Statisches & Konstanten
26Magische Methoden
Teil 5 · Fortgeschrittene Sprache
27Generics-Denken & Collections
28Iteratoren & Generatoren
29Closures & Bindung
30Attribute
31Reflection
32Namespaces im Detail
Teil 6 · Architektur & Patterns
33SOLID-Prinzipien
34Dependency Injection
35Häufige Entwurfsmuster
36Wertobjekte & DTOs
37Fehlerbehandlung als Architektur
Teil 7 · Qualität & Profi-Werkzeug
38Testen mit PHPUnit
39Statische Analyse
40Code-Style & Tooling
41Debugging & Xdebug
Teil 8 · Experten & Nischen
42Performance & OPcache
43Speicher & Referenzen
44Prozesse, FFI & Fibers
45CLI-Programme bauen
46Sicherheit: die Klassiker
Teil 1

Grundlagen

Vom ersten echo bis zu Arrays, Schleifen und eigenen Funktionen. Alles, was du brauchst, um echte kleine Programme zu schreiben.
1 · PHP einrichten & ausführen2 · Variablen & Datentypen3 · Operatoren & Ausdrücke4 · Strings im Detail5 · Bedingungen & Verzweigungen6 · Schleifen7 · Arrays8 · Funktionen
Kapitel 1

PHP einrichten & ausführen

PHP ist eine Skriptsprache, die Code in Ausgaben verwandelt – Text, HTML, JSON. Sie läuft auf deinem Rechner und auf Webservern. Bevor wir programmieren, brauchst du eine lauffähige Installation.

Installation

PHP ist auf jedem Betriebssystem mit wenigen Befehlen installiert. Die aktuelle stabile Version ist PHP 8.5 (Stand 2026); für neue Projekte ist das die richtige Wahl, PHP 8.4 der konservative Fallback.

SystemBefehl
macOS (Homebrew)brew install php
Ubuntu / Debiansudo apt install php-cli
Fedorasudo dnf install php-cli
WindowsWSL2 + Ubuntu, dann apt install php-cli

Prüfe danach im Terminal, ob alles läuft:

php --version
# PHP 8.5.6 (cli) ...

Die erste Datei

PHP-Code lebt in Dateien mit der Endung .php. Lege hallo.php an:

<?php

echo "Hallo Welt!";

Die Zeile <?php öffnet einen PHP-Block. echo gibt Text aus. Der Text steht in Anführungszeichen und heißt String. Jede Anweisung endet mit einem Semikolon.

Ausführen:

php hallo.php
# Hallo Welt!

Der eingebaute Webserver

PHP bringt einen Entwicklungs-Webserver mit. Damit testest du Web-Code ohne Apache oder Nginx:

php -S localhost:8000

Öffne http://localhost:8000 im Browser. Jede .php-Datei im Ordner wird nun ausgeführt und das Ergebnis ausgeliefert.

PHP eingebettet in HTML

PHP wurde fürs Web erfunden. Du kannst PHP-Blöcke direkt in HTML einstreuen. Alles außerhalb von <?php ... ?> wird unverändert ausgegeben:

<h1>Meine Seite</h1>
<?php echo "Heute ist " . date("d.m.Y"); ?>
i
Reine PHP-DateienIn Dateien, die nur PHP enthalten (z. B. Klassen), lässt man das schließende ?> bewusst weg. Das verhindert versehentliche Leerzeichen in der Ausgabe – ein verbreiteter Standard (PSR-12).
Kapitel 2

Variablen & Datentypen

Eine Variable ist ein benannter Speicherplatz für einen Wert – wie eine beschriftete Box. In PHP beginnen Variablennamen immer mit einem Dollar-Zeichen.

Zuweisung

$name = "Marek";
$alter = 34;
$groesse = 1.82;
$istAktiv = true;

Das einfache = bedeutet nicht „ist gleich", sondern „speichere den rechten Wert links". Das nennt man Zuweisung. Variablennamen sind frei wählbar, beginnen mit Buchstabe oder Unterstrich und sind case-sensitive: $name und $Name sind verschieden.

Die wichtigsten Datentypen

TypBedeutungBeispiel
stringText"Hallo"
intGanzzahl42
floatKommazahl3.14
boolWahrheitswerttrue / false
arrayListe von Werten[1, 2, 3]
null„kein Wert"null

Typ herausfinden

PHP ist dynamisch typisiert: Eine Variable kann ihren Typ wechseln. Mit gettype() oder var_dump() siehst du, was drinsteckt:

$x = 42;
var_dump($x);        // int(42)

$x = "jetzt Text";
var_dump($x);        // string(10) "jetzt Text"

Konstanten

Werte, die sich nie ändern, speicherst du in Konstanten. Sie haben kein $ und werden traditionell GROSS geschrieben:

const MWST = 0.19;
echo MWST;        // 0.19
i
Das Dollar-ZeichenDas $ vor jeder Variable ist eine PHP-Besonderheit. Es erlaubt dem Interpreter, Variablen sofort von Schlüsselwörtern wie echo zu unterscheiden, und macht sie in eingebettetem HTML klar erkennbar.
Kapitel 3

Operatoren & Ausdrücke

Operatoren verknüpfen Werte zu neuen Werten. Sie sind das Handwerkszeug für jede Berechnung und jeden Vergleich.

Arithmetik

echo 7 + 3;     // 10
echo 7 - 3;     // 4
echo 7 * 3;     // 21
echo 7 / 2;     // 3.5
echo 7 % 3;     // 1  (Rest der Division)
echo 2 ** 8;    // 256 (Potenz)

Zuweisungs-Kurzformen

Statt $x = $x + 5 schreibt man kürzer:

$x += 5;   // erhöhen um 5
$x -= 2;   // verringern um 2
$x *= 3;   // multiplizieren
$x++;      // um 1 erhöhen
$x--;      // um 1 verringern

Vergleiche

OperatorBedeutung
==gleich (Wert)
===gleich (Wert und Typ)
!= / !==ungleich / streng ungleich
< > <= >=kleiner, größer, …
<=>Spaceship: -1, 0 oder 1

Logik

$a = true;  $b = false;
var_dump($a && $b);   // false (und)
var_dump($a || $b);   // true  (oder)
var_dump(!$a);        // false (nicht)
!
== gegen ===== vergleicht nur den Wert und wandelt Typen vorher um: 0 == "text" kann überraschende Ergebnisse liefern. === prüft Wert und Typ und ist fast immer die sichere Wahl. Gewöhne dir === als Standard an.
Kapitel 4

Strings im Detail

Text ist allgegenwärtig. PHP bietet viele Wege, Strings zu bauen, zu kombinieren und zu durchsuchen.

Anführungszeichen: einfach vs. doppelt

In doppelten Anführungszeichen werden Variablen direkt eingesetzt (Interpolation). In einfachen nicht:

$name = "Anna";
echo "Hallo $name";   // Hallo Anna
echo 'Hallo $name';   // Hallo $name

Bei zusammengesetzten Ausdrücken nutzt du geschweifte Klammern zur Abgrenzung:

echo "Summe: {$preis €}";

Verketten

Der Punkt-Operator klebt Strings zusammen:

$gruss = "Hallo, " . $name . "!";

Nützliche String-Funktionen

FunktionZweck
strlen($s)Länge in Bytes
mb_strlen($s)Länge in Zeichen (Umlaute!)
strtoupper / strtolowerGroß-/Kleinschreibung
trim($s)Leerzeichen am Rand entfernen
str_replace(a, b, $s)ersetzen
str_contains($s, $t)enthält? (bool)
explode(",", $s)String → Array
implode(",", $arr)Array → String
$mail = "  Marek@example.com  ";
echo strtolower(trim($mail));
// marek@example.com

printf & sprintf

Für formatierte Ausgaben mit Platzhaltern:

printf("%s ist %d Jahre alt.\n", $name, $alter);
$preis = sprintf("%.2f €", 3.5);  // "3.50 €"

Heredoc für lange Texte

$html = <<<HTML
<h1>$name</h1>
<p>Willkommen!</p>
HTML;
mb_-Funktionen bei UmlautenBei Texten mit Umlauten oder Emojis nutze die mb_*-Varianten (Multibyte). strlen("Größe") zählt Bytes, mb_strlen("Größe") zählt Zeichen – das willst du fast immer.
Kapitel 5

Bedingungen & Verzweigungen

Programme entscheiden: „Wenn X, dann Y, sonst Z." Verzweigungen steuern, welcher Code unter welchen Umständen läuft.

if / elseif / else

$punkte = 75;

if ($punkte >= 90) {
    echo "Sehr gut";
} elseif ($punkte >= 50) {
    echo "Bestanden";
} else {
    echo "Durchgefallen";
}

Die Bedingung in der Klammer muss einen Wahrheitswert ergeben. Der erste zutreffende Block läuft, der Rest wird übersprungen.

match – die moderne Alternative

Seit PHP 8 gibt es match: kompakt, typsicher (===) und es liefert einen Wert zurück:

$tag = 3;
$name = match($tag) {
    1, 2, 3, 4, 5 => "Werktag",
    6, 7          => "Wochenende",
    default      => "ungültig",
};

Der ternäre Operator

Eine kurze if/else-Form für einfache Fälle:

$status = $alter >= 18 ? "erwachsen" : "minderjährig";

Null-Coalescing

Liefert den ersten Wert, der existiert und nicht null ist – praktisch für Standardwerte:

$name = $_GET['name'] ?? "Gast";
!
= statt == in BedingungenEin einzelnes = in einer Bedingung speichert einen Wert, statt ihn zu prüfen – und ergibt fast immer „wahr”. Das ist ein klassischer Anfängerfehler. In Bedingungen gehören mindestens zwei Gleichheitszeichen.
Kapitel 6

Schleifen

Schleifen wiederholen Code, bis eine Bedingung erfüllt ist. Sie ersparen dir, dieselbe Anweisung hundertmal zu tippen.

while – solange etwas gilt

$i = 1;
while ($i <= 3) {
    echo $i;     // 123
    $i++;
}

for – feste Anzahl

Drei Teile in einer Zeile: Start, Bedingung, Schritt.

for ($i = 0; $i < 5; $i++) {
    echo $i;     // 01234
}

foreach – über Listen

Die wichtigste Schleife in der Praxis: Sie geht jedes Element eines Arrays durch.

$obst = ["Apfel", "Birne", "Kirsche"];
foreach ($obst as $frucht) {
    echo $frucht . "\n";
}

Mit Schlüssel und Wert zugleich:

foreach ($preise as $produkt => $preis) {
    echo "$produkt: $preis €\n";
}

break & continue

break bricht die Schleife ganz ab, continue springt zum nächsten Durchlauf:

foreach ($zahlen as $z) {
    if ($z < 0) continue;   // negative überspringen
    if ($z > 100) break;    // ab 100 aufhören
    echo $z;
}
foreach ist dein StandardIn der Praxis ist foreach die mit Abstand häufigste Schleife. for brauchst du nur, wenn du den Zähler selbst kontrollieren musst; while, wenn die Anzahl der Durchläufe vorher unbekannt ist.
Kapitel 7

Arrays

Ein Array speichert mehrere Werte unter einem Namen. In PHP ist es extrem flexibel: Liste, Schlüssel-Wert-Sammlung und Verschachtelung in einem.

Indizierte Arrays

Werte mit automatischen Nummern (beginnend bei 0):

$farben = ["rot", "grün", "blau"];
echo $farben[0];      // rot
$farben[] = "gelb";    // anhängen

Assoziative Arrays

Werte mit eigenen Schlüsseln – ideal für strukturierte Daten:

$person = [
    "name"  => "Marek",
    "alter" => 34,
    "stadt" => "Kaltenkirchen",
];
echo $person["name"];   // Marek

Verschachtelung

$team = [
    ["name" => "Anna", "rolle" => "Dev"],
    ["name" => "Ben",  "rolle" => "Design"],
];
echo $team[0]["name"];   // Anna

Wichtige Array-Funktionen

FunktionZweck
count($a)Anzahl Elemente
in_array($x, $a)enthält Wert?
array_keys / array_valuesSchlüssel / Werte
sort / rsortauf-/absteigend sortieren
array_mapjeden Wert transformieren
array_filterWerte herausfiltern
array_mergeArrays zusammenfügen
$zahlen = [1, 2, 3, 4];
$quadrate = array_map(fn($n) => $n ** 2, $zahlen);
// [1, 4, 9, 16]
$gerade = array_filter($zahlen, fn($n) => $n % 2 === 0);
// [2, 4]
i
Ein Typ, viele RollenIn vielen Sprachen sind Liste und Wörterbuch getrennte Typen. PHP hat nur einen Array-Typ, der beides kann. Das ist praktisch, aber sei dir bewusst, ob deine Schlüssel Zahlen oder Strings sind – das beeinflusst Sortierung und Iteration.
Kapitel 8

Funktionen

Eine Funktion bündelt Code, den du benennen und wiederverwenden kannst. Sie nimmt Eingaben (Parameter) und liefert oft ein Ergebnis (Rückgabewert).

Definition & Aufruf

function begruessen($name) {
    echo "Hallo, $name!\n";
}
begruessen("Marek");
begruessen("Anna");

Rückgabewerte

return beendet die Funktion und liefert einen Wert an den Aufrufer zurück:

function addiere($a, $b) {
    return $a + $b;
}
$summe = addiere(3, 5);   // 8

Unterschied: echo gibt etwas auf dem Bildschirm aus, return gibt einen Wert zurück, mit dem du weiterrechnen kannst.

Typdeklarationen

Modernes PHP gibt Parametern und Rückgabe feste Typen. Das macht Code sicherer und selbsterklärend:

function addiere(int $a, int $b): int {
    return $a + $b;
}

Standardwerte & benannte Argumente

function verbinde(string $host, int $port = 5432): string {
    return "$host:$port";
}
verbinde("db.local");              // db.local:5432
verbinde("db.local", port: 5433);  // benanntes Argument

Anonyme Funktionen & Arrow Functions

Funktionen ohne Namen, oft als Argument für andere Funktionen:

$verdopple = fn($x) => $x * 2;
echo $verdopple(21);   // 42
Typen von Anfang anAuch wenn PHP Typen nicht erzwingt: Schreib sie hin. Typdeklarationen fangen Fehler früh ab, dokumentieren deine Absicht und sind die Grundlage für gute Editor-Unterstützung. Wir vertiefen das in Teil 3.
Teil 2

Struktur & Werkzeug

Code auf mehrere Dateien verteilen, Fehler sauber behandeln, mit Datum, Dateien und JSON arbeiten – und Composer als Paketmanager nutzen.
9 · Code aufteilen: include & require10 · Fehler & Exceptions11 · Datum & Zeit12 · Dateien lesen & schreiben13 · JSON & Datenformate14 · Composer & Autoloading
Kapitel 9

Code aufteilen: include & require

Sobald ein Programm wächst, willst du es auf mehrere Dateien verteilen – nach Themen geordnet. PHP bindet Dateien mit vier Schlüsselwörtern ein.

include und require

Beide fügen den Inhalt einer anderen Datei an dieser Stelle ein. Der Unterschied liegt im Fehlerfall:

BefehlWenn Datei fehlt
includeWarnung, Programm läuft weiter
requirefataler Fehler, Programm stoppt
include_once / require_oncebindet nur einmal ein
// funktionen.php
function gruss($n) { return "Hi $n"; }

// app.php
require_once "funktionen.php";
echo gruss("Marek");

Warum _once?

Bindest du dieselbe Datei zweimal ein, würde eine Funktion doppelt definiert – das ist ein Fehler. require_once merkt sich, was schon geladen wurde, und verhindert das.

i
In der Praxis: AutoloadingHeute bindet man Dateien selten von Hand ein. Composer (Kapitel 14) lädt Klassen automatisch, sobald sie gebraucht werden. require sieht man vor allem noch für den zentralen autoload.php-Einstieg.
Kapitel 10

Fehler & Exceptions

Fehler passieren: eine Datei fehlt, eine Eingabe ist ungültig, ein Server antwortet nicht. Exceptions sind PHPs strukturierter Weg, damit umzugehen.

try / catch

Du umschließt riskanten Code mit try. Tritt ein Fehler auf, springt PHP in den passenden catch-Block – das Programm stürzt nicht ab:

try {
    $wert = 10 / $teiler;
} catch (DivisionByZeroError $e) {
    echo "Division durch Null!";
}

Eigene Exceptions werfen

Mit throw löst du selbst einen Fehler aus, wenn etwas nicht stimmt:

function alterPruefen(int $alter): void {
    if ($alter < 0) {
        throw new InvalidArgumentException("Alter negativ");
    }
}

finally

Der finally-Block läuft immer – egal ob ein Fehler auftrat. Ideal zum Aufräumen:

try {
    $datei = fopen("log.txt", "a");
    // ... schreiben ...
} finally {
    fclose($datei);   // immer schließen
}

Die Exception-Hierarchie

Alle Fehlerklassen erben von Throwable. Wichtig zu wissen:

!
Nicht alles wegfangenEin leeres catch, das Fehler verschluckt, ist gefährlich – du merkst nie, dass etwas schiefging. Fange nur Fehler ab, auf die du sinnvoll reagieren kannst, und logge oder melde den Rest.
Kapitel 11

Datum & Zeit

Mit Datum und Zeit zu rechnen ist erstaunlich fehleranfällig – Zeitzonen, Schaltjahre, Sommerzeit. PHP nimmt dir das mit der DateTime-Klasse ab.

Schnelle Ausgabe mit date()

echo date("d.m.Y");        // 29.05.2026
echo date("H:i");          // 14:30

DateTime – der robuste Weg

$jetzt = new DateTime();
$termin = new DateTime("2026-12-24 18:00");

echo $termin->format("d.m.Y");   // 24.12.2026

Rechnen mit Intervallen

$heute = new DateTime();
$heute->modify("+2 weeks");

$diff = $heute->diff(new DateTime("2026-12-31"));
echo $diff->days . " Tage";

Zeitzonen

$tz = new DateTimeZone("Europe/Berlin");
$d = new DateTime("now", $tz);
Immer DateTimeImmutableEs gibt DateTime und DateTimeImmutable. Bei DateTime verändert modify() das Objekt selbst – das führt zu Überraschungen. Nutze besser DateTimeImmutable: Operationen geben ein neues Objekt zurück und lassen das Original unangetastet.
Kapitel 12

Dateien lesen & schreiben

Daten dauerhaft speichern heißt oft: in eine Datei schreiben. PHP bietet dafür angenehm kurze Funktionen.

Komplette Datei – die einfachen Funktionen

// ganze Datei lesen
$inhalt = file_get_contents("notizen.txt");

// ganze Datei schreiben (überschreibt)
file_put_contents("notizen.txt", "Hallo\n");

// anhängen statt überschreiben
file_put_contents("log.txt", "Eintrag\n", FILE_APPEND);

Zeile für Zeile

Bei großen Dateien liest du nicht alles auf einmal in den Speicher, sondern Zeile für Zeile:

$f = fopen("gross.csv", "r");
while (($zeile = fgets($f)) !== false) {
    echo trim($zeile);
}
fclose($f);

Prüfen, ob etwas existiert

if (file_exists("config.php")) { /* ... */ }
if (is_dir("uploads"))       { /* ... */ }

Verzeichnisse

$dateien = glob("bilder/*.jpg");
foreach ($dateien as $pfad) {
    echo basename($pfad) . "\n";
}
!
Pfade & RechteSchreibzugriff scheitert oft an Dateirechten oder falschen Pfaden. Nutze absolute Pfade über __DIR__ (das Verzeichnis der aktuellen Datei), z. B. __DIR__ . "/data/log.txt", statt dich auf das Arbeitsverzeichnis zu verlassen.
Kapitel 13

JSON & Datenformate

JSON ist das Standardformat für den Datenaustausch im Web. PHP wandelt Arrays und Objekte mit zwei Funktionen hin und her.

PHP → JSON

$daten = [
    "name" => "Marek",
    "tags" => ["php", "godot"],
];
$json = json_encode($daten, JSON_PRETTY_PRINT);
echo $json;
{
    "name": "Marek",
    "tags": ["php", "godot"]
}

JSON → PHP

Mit true als zweitem Argument bekommst du ein assoziatives Array statt eines Objekts:

$arr = json_decode($json, true);
echo $arr["name"];   // Marek

Fehler erkennen

Ist das JSON kaputt, liefert json_decode null. Sauberer: per Flag eine Exception werfen lassen:

try {
    $arr = json_decode($json, true, flags: JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    echo "Ungültiges JSON";
}
i
Weitere FormateFür CSV gibt es fgetcsv() und fputcsv(). XML liest man am robustesten mit SimpleXML oder der DOM-Erweiterung. JSON ist aber im modernen Web der Normalfall.
Kapitel 14

Composer & Autoloading

Composer ist PHPs Paketmanager – das Tor zur riesigen Bibliothekswelt. Er installiert fremden Code und lädt deine eigenen Klassen automatisch.

Ein Projekt starten

composer init
# beantwortet ein paar Fragen, erzeugt composer.json

Pakete installieren

composer require guzzlehttp/guzzle
# lädt das Paket + Abhängigkeiten nach vendor/

Composer legt zwei Dateien an: composer.json (was du willst) und composer.lock (welche Versionen exakt installiert sind). Beide gehören ins Git-Repository, der Ordner vendor/ nicht.

Autoloading – nie wieder require

Das Herzstück: Du bindest eine Datei ein, und alle Klassen laden sich bei Bedarf selbst:

require "vendor/autoload.php";

$client = new GuzzleHttp\Client();

Eigene Klassen automatisch laden (PSR-4)

In composer.json verknüpfst du einen Namespace mit einem Ordner:

{
  "autoload": {
    "psr-4": { "App\\": "src/" }
  }
}

Danach einmal composer dump-autoload – und die Klasse App\Service\Mailer wird automatisch in src/Service/Mailer.php gesucht.

Die zwei Befehle, die du brauchstIm Alltag reichen oft composer require paket zum Hinzufügen und composer install zum Einrichten eines geklonten Projekts. Mit --dev markierst du Werkzeuge (Tests, Linter), die nur in der Entwicklung gebraucht werden.
Teil 3

Typsystem & moderne Features

Das, was modernes PHP von altem unterscheidet: ein ausdrucksstarkes Typsystem, Enums, Match, der Pipe-Operator und benannte Argumente.
15 · Typen & strict_types16 · Union-, Nullable- & spezielle Typen17 · Enums18 · Moderne Syntax-Schmankerl
Kapitel 15

Typen & strict_types

PHP ist dynamisch typisiert, aber du kannst – und solltest – Typen explizit angeben. Das macht Code sicherer und für Editoren verständlich.

Skalare Typen

Die Grundtypen für Parameter, Rückgaben und Eigenschaften:

TypBeispielwert
int42
float3.14
string"text"
booltrue
array[1, 2]
function rabatt(float $preis, int $prozent): float {
    return $preis * (1 - $prozent / 100);
}

strict_types

Standardmäßig wandelt PHP Typen weich um: Ein String "5" wird für einen int-Parameter zu 5. Mit dieser Zeile ganz oben in der Datei schaltest du das ab:

<?php
declare(strict_types=1);

Jetzt muss der Typ exakt passen, sonst gibt es einen TypeError. Das deckt Fehler früh auf.

Typen für Eigenschaften

class Konto {
    public float $saldo = 0.0;
    public string $inhaber;
}
strict_types in jede DateiMach declare(strict_types=1); zur Gewohnheit – als erste Zeile jeder PHP-Datei. Die wenigen Stellen, an denen es dich zwingt, einen Wert bewusst zu casten, sind genau die Stellen, an denen sonst stille Bugs entstehen.
Kapitel 16

Union-, Nullable- & spezielle Typen

Das Typsystem kann mehr als einzelne Grundtypen: Es drückt aus „dieses oder jenes", „dieses oder nichts" und besondere Fälle wie „gibt nie zurück".

Nullable: Wert oder null

Ein ? vor dem Typ erlaubt zusätzlich null – typisch für „nicht gefunden":

function finde(int $id): ?User {
    // gibt User oder null zurück
}

Union: mehrere erlaubte Typen

function id(int|string $wert): string {
    return (string) $wert;
}

Spezielle Rückgabetypen

TypBedeutung
voidgibt nichts zurück
neverkehrt nie zurück (wirft / beendet)
mixedjeder beliebige Typ
self / staticdie eigene Klasse
function abbruch(string $msg): never {
    throw new RuntimeException($msg);
}

Intersection-Typen

Seit PHP 8.1: ein Wert, der mehrere Interfaces zugleich erfüllt:

function verarbeite(Countable&Iterator $x): void { /* ... */ }
i
mixed sparsam einsetzenmixed bedeutet „alles erlaubt” und schaltet damit den Schutz des Typsystems faktisch ab. Es ist gelegentlich nötig (z. B. bei generischen Containern), sollte aber die Ausnahme bleiben – je präziser dein Typ, desto mehr Fehler fängt PHP für dich ab.
Kapitel 17

Enums

Ein Enum ist ein eigener Typ mit einer festen, abgeschlossenen Menge möglicher Werte. Statt loser Strings wie "aktiv" bekommst du echte, typsichere Optionen.

Das Problem ohne Enums

Früher übergab man Status als String – fehleranfällig, weil Tippfehler erst zur Laufzeit auffallen:

$status = "aktif";   // Tippfehler – PHP merkt nichts

Reines Enum

enum Status {
    case Aktiv;
    case Pausiert;
    case Gesperrt;
}

function setze(Status $s): void { /* ... */ }
setze(Status::Aktiv);   // nur gültige Werte möglich

Backed Enum – mit Wert hinterlegt

Wenn der Wert in einer Datenbank oder API auftaucht, hinterlegst du ihn:

enum Rolle: string {
    case Admin  = "admin";
    case Editor = "editor";
    case Gast   = "guest";
}

$r = Rolle::from("admin");   // aus DB-Wert
echo $r->value;             // "admin"
echo $r->name;              // "Admin"

Methoden im Enum

Enums dürfen Methoden haben – ideal für ableitbare Eigenschaften:

enum Ampel: string {
    case Rot  = "rot";
    case Gruen = "gruen";

    public function darfFahren(): bool {
        return $this === Ampel::Gruen;
    }
}
Enum + match = unschlagbarEnums spielen perfekt mit match zusammen: Da die Werte abgeschlossen sind, kann dein Editor warnen, wenn du einen Fall vergisst. Ersetze lose String-Konstanten in deinem Code nach und nach durch Enums.
Kapitel 18

Moderne Syntax-Schmankerl

PHP 8 hat die Sprache spürbar moderner gemacht. Diese Features schreiben kürzeren, klareren Code – und sind heute Best Practice.

Benannte Argumente

Argumente per Namen übergeben – die Reihenfolge wird egal, optionale Werte überspringbar:

erstelle(name: "Box", hoehe: 10, farbe: "blau");

Der Nullsafe-Operator

?-> bricht eine Kette ab, sobald ein Glied null ist – statt einen Fehler zu werfen:

$land = $user?->adresse()?->land;
// null, falls user oder adresse() null ist

Der Pipe-Operator (PHP 8.5)

Ganz neu: |> reicht einen Wert durch eine Kette von Funktionen – von links nach rechts lesbar statt verschachtelt:

// vorher: tief verschachtelt
$r = array_sum(array_map(fn($x) => $x * 2, $zahlen));

// mit Pipe: von links nach rechts
$r = $zahlen
    |> fn($a) => array_map(fn($x) => $x * 2, $a)
    |> array_sum(...);

First-class Callable Syntax

Eine Funktion als Wert weiterreichen, ohne sie aufzurufen – das (...) macht's:

$fn = strtoupper(...);
echo $fn("hallo");   // HALLO

$gross = array_map(strtoupper(...), $woerter);

Destructuring

Array-Werte in einem Schritt auf Variablen verteilen:

[$jahr, $monat, $tag] = [2026, 5, 29];
["name" => $name] = $person;
Pipe-Operator: brandneuDer |>-Operator kam erst mit PHP 8.5 (Ende 2025). Er ist großartig für Datentransformationen, aber prüfe vor dem Einsatz, dass deine Zielumgebung wirklich auf 8.5 läuft – auf älteren Versionen ist es schlicht ein Syntaxfehler.
Teil 4

Objektorientierung

Klassen und Objekte sind das Rückgrat größerer PHP-Programme. Von der ersten Klasse über Vererbung und Interfaces bis zu Traits und der Magie hinter den Kulissen.
19 · Klassen & Objekte20 · Sichtbarkeit & Kapselung21 · Konstruktoren modern22 · Vererbung23 · Abstrakte Klassen & Interfaces24 · Traits25 · Statisches & Konstanten26 · Magische Methoden
Kapitel 19

Klassen & Objekte

Eine Klasse ist ein Bauplan, ein Objekt das fertige Ding. Die Klasse beschreibt, welche Daten (Eigenschaften) und welche Fähigkeiten (Methoden) etwas hat.

Erste Klasse

class Hund {
    public string $name;

    public function bellen(): string {
        return $this->name . " sagt Wuff!";
    }
}

Objekte erzeugen

Mit new erstellst du eine konkrete Instanz. Auf Eigenschaften und Methoden greifst du mit -> zu:

$bello = new Hund();
$bello->name = "Bello";
echo $bello->bellen();   // Bello sagt Wuff!

$this – das Objekt selbst

Innerhalb einer Methode verweist $this auf das aktuelle Objekt. So greift eine Methode auf die Eigenschaften ihres eigenen Objekts zu.

Mehrere unabhängige Objekte

$a = new Hund(); $a->name = "Rex";
$b = new Hund(); $b->name = "Luna";
// $a und $b sind komplett getrennt
i
Klasse vs. ObjektDie Klasse Hund ist der Bauplan – sie existiert einmal. Jedes mit new erzeugte Objekt ist eine eigenständige Instanz mit eigenen Daten. Aus einem Bauplan baust du beliebig viele Häuser.
Kapitel 20

Sichtbarkeit & Kapselung

Nicht jeder Teil eines Objekts soll von außen erreichbar sein. Sichtbarkeits-Modifikatoren schützen die innere Logik – das nennt man Kapselung.

Die drei Stufen

ModifikatorZugriff von …
publicüberall
protectedKlasse + Unterklassen
privatenur dieser Klasse selbst

Warum kapseln?

Eine private Eigenschaft kann nicht von außen in einen ungültigen Zustand gebracht werden. Du steuerst den Zugriff über Methoden:

class Konto {
    private float $saldo = 0;

    public function einzahlen(float $betrag): void {
        if ($betrag <= 0) {
            throw new InvalidArgumentException("> 0 nötig");
        }
        $this->saldo += $betrag;
    }

    public function saldo(): float {
        return $this->saldo;
    }
}

Von außen kann niemand $konto->saldo = -999 setzen – der Weg führt nur über einzahlen(), das prüft.

Asymmetrische Sichtbarkeit (PHP 8.4)

Neu: von außen lesbar, aber nur intern schreibbar – in einer Zeile:

class User {
    public private(set) string $id;
}
// $user->id lesen: ok / setzen von außen: Fehler
Standard: so privat wie möglichEine gute Faustregel: Mach alles private, bis du einen Grund hast, es zu öffnen. Je kleiner die öffentliche Oberfläche einer Klasse, desto leichter kannst du ihre Interna später ändern, ohne anderen Code zu brechen.
Kapitel 21

Konstruktoren modern

Der Konstruktor läuft automatisch beim Erzeugen eines Objekts. PHP 8 hat ihn drastisch verkürzt – das spart viel Tipparbeit.

Klassischer Konstruktor

class Punkt {
    public float $x;
    public float $y;

    public function __construct(float $x, float $y) {
        $this->x = $x;
        $this->y = $y;
    }
}

Constructor Property Promotion

Dasselbe in modern: Sichtbarkeit direkt in die Parameterliste schreiben – Eigenschaft und Zuweisung entstehen automatisch:

class Punkt {
    public function __construct(
        public float $x,
        public float $y,
    ) {}
}
$p = new Punkt(3, 4);
echo $p->x;   // 3

Beide Versionen sind exakt gleichwertig – die zweite ist nur viel kürzer.

readonly für Unveränderliches

Eine readonly-Eigenschaft darf nach dem Setzen im Konstruktor nicht mehr geändert werden – perfekt für Wertobjekte:

class Geld {
    public function __construct(
        public readonly int $cent,
        public readonly string $waehrung,
    ) {}
}
$preis = new Geld(999, "EUR");
// $preis->cent = 0;  -> Error
readonly classes (PHP 8.2)Statt jede Eigenschaft einzeln als readonly zu markieren, kannst du seit 8.2 die ganze Klasse so deklarieren: readonly class Geld { ... }. Ideal für DTOs und Wertobjekte, die nach Erzeugung unveränderlich bleiben sollen.
Kapitel 22

Vererbung

Vererbung lässt eine Klasse die Eigenschaften und Methoden einer anderen übernehmen – und erweitern. So vermeidest du Wiederholung bei verwandten Typen.

extends

class Tier {
    public function __construct(public string $name) {}
    public function geraeusch(): string { return "..."; }
}

class Katze extends Tier {
    public function geraeusch(): string { return "Miau"; }
}

$mieze = new Katze("Minka");
echo $mieze->name;          // von Tier geerbt
echo $mieze->geraeusch();    // Miau (überschrieben)

parent:: – die Elternversion aufrufen

Beim Überschreiben kannst du die ursprüngliche Methode trotzdem mitnutzen:

class Hund extends Tier {
    public function __construct(string $name, public string $rasse) {
        parent::__construct($name);
    }
}

final – Vererbung stoppen

final verhindert, dass eine Klasse erweitert oder eine Methode überschrieben wird:

final class Uuid { /* niemand darf erben */ }
!
Vererbung mit BedachtTiefe Vererbungsbäume werden schnell unübersichtlich. Eine erprobte Regel lautet „Komposition vor Vererbung”: Statt von einer Klasse zu erben, gib deinem Objekt das andere Objekt als Eigenschaft mit. Vererbung passt nur, wenn wirklich eine „ist ein”-Beziehung besteht (eine Katze ist ein Tier).
Kapitel 23

Abstrakte Klassen & Interfaces

Beide definieren Verträge: „Wer das sein will, muss diese Methoden bieten." Interfaces beschreiben reine Fähigkeiten, abstrakte Klassen liefern zusätzlich teilweise Umsetzung.

Interface – der reine Vertrag

Ein Interface listet Methoden ohne Rumpf. Jede Klasse, die es implements, muss sie ausfüllen:

interface Zahlbar {
    public function betrag(): int;
}

class Rechnung implements Zahlbar {
    public function betrag(): int { return 4200; }
}

Der Gewinn: Du programmierst gegen den Vertrag, nicht gegen eine konkrete Klasse. Eine Funktion kann jedes Zahlbar entgegennehmen:

function verbuche(Zahlbar $x): void { /* ... */ }

Mehrere Interfaces

class Bestellung implements Zahlbar, JsonSerializable { /* ... */ }

Abstrakte Klasse – Vertrag plus Basis

Sie kann fertige Methoden mitbringen und abstrakte erzwingen. Selbst instanziieren lässt sie sich nicht:

abstract class Form {
    abstract public function flaeche(): float;

    public function beschreibung(): string {
        return "Fläche: " . $this->flaeche();
    }
}

class Kreis extends Form {
    public function __construct(private float $r) {}
    public function flaeche(): float { return 3.14159 * $this->r ** 2; }
}
i
Wann was?Faustregel: Interface, wenn du nur eine Fähigkeit beschreibst und Klassen ganz unterschiedlicher Herkunft sie erfüllen sollen. Abstrakte Klasse, wenn verwandte Klassen gemeinsamen Code teilen und einen Pflichtteil haben. Eine Klasse kann viele Interfaces, aber nur eine (abstrakte) Elternklasse haben.
Kapitel 24

Traits

Ein Trait ist ein Stück wiederverwendbarer Code, das du in mehrere Klassen „hineinkopierst" – ohne Vererbung. Er löst das Problem, dass eine Klasse nur von einer Klasse erben kann.

Trait definieren und nutzen

trait Zeitstempel {
    public ?DateTimeImmutable $erstellt = null;

    public function jetztSetzen(): void {
        $this->erstellt = new DateTimeImmutable();
    }
}

class Artikel {
    use Zeitstempel;
}
class Kommentar {
    use Zeitstempel;   // gleiche Funktionalität, keine Vererbung
}

Mehrere Traits kombinieren

class Post {
    use Zeitstempel, Sluggable;
}

Namenskonflikte auflösen

Bringen zwei Traits eine gleichnamige Methode mit, wählst du explizit aus:

class Seite {
    use A, B {
        A::hallo insteadof B;
        B::hallo as halloB;
    }
}
!
Traits sparsam einsetzenTraits sind mächtig, verwischen aber die Herkunft von Code – eine Klasse kann plötzlich Methoden aus drei Dateien haben. Nutze sie für klar abgegrenzte, querschnittliche Fähigkeiten (Zeitstempel, Logging). Für echte Typ-Beziehungen sind Interfaces die sauberere Wahl.
Kapitel 25

Statisches & Konstanten

Manche Daten und Methoden gehören zur Klasse selbst, nicht zu einzelnen Objekten. Dafür gibt es statische Eigenschaften, Methoden und Klassen-Konstanten.

Statische Eigenschaften & Methoden

Zugriff erfolgt über den Klassennamen mit ::, ohne ein Objekt zu erzeugen:

class Zaehler {
    public static int $anzahl = 0;

    public static function hoch(): void {
        self::$anzahl++;
    }
}
Zaehler::hoch();
echo Zaehler::$anzahl;   // 1

Klassen-Konstanten

class Http {
    const OK = 200;
    const NOT_FOUND = 404;
}
echo Http::NOT_FOUND;   // 404

Typisierte Konstanten (PHP 8.3)

class Config {
    const string ENV = "prod";
}

Das Named Constructor Pattern

Statische Methoden als sprechende Alternativen zu new:

class Temperatur {
    private function __construct(public readonly float $celsius) {}

    public static function ausFahrenheit(float $f): static {
        return new self(($f - 32) * 5 / 9);
    }
}
$t = Temperatur::ausFahrenheit(98.6);
i
static sparsamStatischer Zustand (veränderliche statische Eigenschaften) ist im Grunde globaler Zustand und erschwert Tests. Statische Methoden als Named Constructors oder reine Hilfsfunktionen sind dagegen völlig in Ordnung.
Kapitel 26

Magische Methoden

„Magische" Methoden beginnen mit zwei Unterstrichen und werden von PHP automatisch in bestimmten Situationen aufgerufen – etwa wenn ein Objekt als String genutzt wird.

__toString

Definiert, wie ein Objekt zu Text wird:

class Geld {
    public function __construct(public int $cent) {}
    public function __toString(): string {
        return number_format($this->cent / 100, 2) . " €";
    }
}
echo new Geld(1999);   // 19,99 €

__get und __set

Fangen Zugriffe auf nicht existierende Eigenschaften ab – Basis vieler Frameworks:

class Bag {
    private array $data = [];
    public function __get(string $k) { return $this->data[$k] ?? null; }
    public function __set(string $k, $v) { $this->data[$k] = $v; }
}
$b = new Bag();
$b->titel = "Test";   // __set
echo $b->titel;        // __get -> Test

__call und __invoke

__call fängt Aufrufe nicht existierender Methoden ab; __invoke macht ein Objekt aufrufbar wie eine Funktion:

class Verdoppler {
    public function __invoke(int $x): int { return $x * 2; }
}
$f = new Verdoppler();
echo $f(21);   // 42
!
Magie versteckt LogikMagische Methoden sind elegant, aber sie machen Code schwerer nachvollziehbar – Editoren erkennen die so erzeugten Eigenschaften und Methoden oft nicht. Setze sie gezielt ein (etwa __toString für Wertobjekte) und bevorzuge sonst explizite, deklarierte Methoden.
Teil 5

Fortgeschrittene Sprache

Tieferes Sprach-Handwerk: Collections, Generatoren, Closures mit Bindung, Attribute und Reflection – die Werkzeuge hinter Frameworks.
27 · Collections & Generics-Denken28 · Iteratoren & Generatoren29 · Closures & Bindung30 · Attribute31 · Reflection32 · Namespaces im Detail
Kapitel 27

Collections & Generics-Denken

PHP hat keine echten Generics in der Sprache – aber das Denken in typisierten Sammlungen lohnt sich. Mit ein wenig Disziplin und Werkzeugen bekommst du fast denselben Komfort.

Das Problem mit nackten Arrays

Ein array sagt nichts darüber, was drinsteckt. Eine Funktion, die User[] erwartet, kann das im Typsystem nicht ausdrücken:

function namen(array $users): array {
    // array von WAS? Editor weiß es nicht
}

Lösung 1: PHPDoc-Generics

Statische Analyse-Werkzeuge (Teil 7) verstehen Generics in Kommentaren – die Sprache ignoriert sie, der Editor nicht:

/** @param User[] $users @return string[] */
function namen(array $users): array { /* ... */ }

Lösung 2: Eine eigene Collection-Klasse

Du kapselst das Array in einer Klasse, die nur den gewünschten Typ akzeptiert:

final class UserListe implements IteratorAggregate {
    private array $items = [];

    public function add(User $u): void {
        $this->items[] = $u;   // nur User möglich
    }
    public function getIterator(): Iterator {
        return new ArrayIterator($this->items);
    }
}

Jetzt ist foreach über die Liste möglich, und niemand kann versehentlich einen String hineinlegen.

i
Warum keine echten Generics?Echte Generics würden tief in die PHP-Laufzeit eingreifen und kosteten Performance. Die Community löst das pragmatisch über PHPDoc plus statische Analyse – in der Praxis bekommst du damit fast die volle Typsicherheit, ohne Laufzeit-Kosten.
Kapitel 28

Iteratoren & Generatoren

Manchmal willst du über etwas iterieren, ohne alle Werte gleichzeitig im Speicher zu halten – etwa Millionen Zeilen aus einer Datei. Generatoren machen das mit minimalem Code.

Der yield-Befehl

Eine Funktion mit yield ist ein Generator: Sie liefert Werte einen nach dem anderen, pausiert dazwischen und merkt sich ihren Zustand:

function zaehleBis(int $max): Generator {
    for ($i = 1; $i <= $max; $i++) {
        yield $i;
    }
}
foreach (zaehleBis(3) as $n) {
    echo $n;   // 123
}

Der Speicher-Vorteil

Eine riesige Datei zeilenweise verarbeiten, ohne sie komplett zu laden:

function zeilen(string $pfad): Generator {
    $f = fopen($pfad, "r");
    while (($z = fgets($f)) !== false) {
        yield trim($z);
    }
    fclose($f);
}
// verbraucht konstant wenig Speicher – egal wie groß die Datei

Schlüssel mitliefern

yield $key => $value;

Das Iterator-Interface von Hand

Generatoren decken 95 % der Fälle ab. Für komplexe Iteration implementierst du Iterator direkt – mit current(), next(), valid(), key(), rewind().

Generatoren für große DatenmengenImmer wenn du ein großes Array baust, nur um einmal darüber zu iterieren, ist ein Generator die bessere Wahl: gleicher foreach-Komfort, aber konstanter Speicherverbrauch statt linear wachsendem.
Kapitel 29

Closures & Bindung

Eine Closure ist eine anonyme Funktion, die sich Werte aus ihrer Umgebung „merkt". Sie ist die Grundlage von Callbacks, Event-Handlern und vielem Framework-Code.

use – Werte einfangen

Mit use nimmt eine anonyme Funktion Variablen von außen mit hinein:

$faktor = 3;
$mal = function($x) use ($faktor) {
    return $x * $faktor;
};
echo $mal(5);   // 15

Arrow Functions fangen automatisch

fn braucht kein use – es sieht die umgebenden Variablen automatisch (nur lesend):

$mal = fn($x) => $x * $faktor;

by-reference einfangen

Mit & teilt die Closure dieselbe Variable, statt eine Kopie zu nehmen:

$summe = 0;
$add = function($n) use (&$summe) {
    $summe += $n;
};
$add(10); $add(5);
echo $summe;   // 15

$this binden

Closures kennen das Objekt, in dem sie erzeugt wurden – mit bindTo() kannst du sie an ein anderes binden. Das nutzen Frameworks für Routing-Definitionen und Templating.

$closure = function() { return $this->name; };
$gebunden = Closure::bind($closure, $objekt, Klasse::class);
i
Closure vs. Arrow Functionfn ist kompakt und fängt automatisch ein, kann aber nur einen Ausdruck enthalten. Die längere function() use(...)-Form erlaubt mehrere Zeilen und das Einfangen per Referenz. Für einfache Callbacks fn, für alles andere die lange Form.
Kapitel 30

Attribute

Attribute sind strukturierte Metadaten, die du direkt an Klassen, Methoden oder Eigenschaften heftest. Frameworks lesen sie aus, um Verhalten zu steuern – ganz ohne Konfigurationsdateien.

Syntax

Attribute stehen in #[ ] direkt über dem Element:

#[Route("/users", methods: ["GET"])]
public function liste(): Response { /* ... */ }

Ein eigenes Attribut

Ein Attribut ist nichts weiter als eine Klasse mit dem Marker #[Attribute]:

#[Attribute(Attribute::TARGET_METHOD)]
class Route {
    public function __construct(
        public string $pfad,
        public array $methods = ["GET"],
    ) {}
}

Attribute auslesen

Über Reflection (nächstes Kapitel) liest ein Framework die Attribute zur Laufzeit aus:

$r = new ReflectionMethod($controller, "liste");
foreach ($r->getAttributes(Route::class) as $attr) {
    $route = $attr->newInstance();
    echo $route->pfad;   // /users
}
i
Wo du Attribute triffstSymfony nutzt Attribute für Routing und Validierung, Doctrine für das Mapping von Klassen auf Datenbanktabellen, PHPUnit für Test-Markierungen. Du wirst sie also oft nutzen, bevor du eigene schreibst – und genau dafür ist es gut, das Prinzip zu verstehen.
Kapitel 31

Reflection

Reflection erlaubt einem Programm, sich selbst zu untersuchen: Welche Methoden hat diese Klasse? Welche Parameter diese Funktion? Es ist die Magie hinter Dependency-Injection-Containern und Frameworks.

Eine Klasse inspizieren

$r = new ReflectionClass(User::class);

echo $r->getName();              // User
foreach ($r->getMethods() as $m) {
    echo $m->getName() . "\n";
}

Parameter einer Methode lesen

So findet ein DI-Container heraus, welche Abhängigkeiten ein Konstruktor braucht:

$ctor = (new ReflectionClass(Service::class))->getConstructor();
foreach ($ctor->getParameters() as $p) {
    echo $p->getType();   // der Typ des Parameters
}

Objekte dynamisch erzeugen

$obj = (new ReflectionClass($klassenName))
    ->newInstanceArgs($argumente);
!
Reflection ist langsam & mächtigReflection umgeht private und ist deutlich langsamer als direkter Code. In Anwendungscode brauchst du es fast nie – es ist Framework-Handwerk. Gut zu verstehen, um zu wissen, wie deine Werkzeuge funktionieren; im Alltag aber selten selbst zu schreiben.
Kapitel 32

Namespaces im Detail

Namespaces verhindern Namenskollisionen, wenn Code aus vielen Quellen zusammenkommt. Sie sind die Ordnerstruktur des Codes – und die Basis für Autoloading.

Deklaration

Ein Namespace steht als erste Anweisung der Datei:

<?php
namespace App\Service;

class Mailer { /* voll: App\Service\Mailer */ }

use – Klassen importieren

Statt überall den vollen Pfad zu schreiben, importierst du oben einmal:

use App\Service\Mailer;
use App\Model\User;

$m = new Mailer();   // kurz statt voll qualifiziert

Aliasse bei Kollisionen

Heißen zwei Klassen gleich, gibst du einer per as einen anderen Namen:

use App\Pdf\Writer as PdfWriter;
use App\Csv\Writer as CsvWriter;

Funktionen und Konstanten importieren

use function App\Helpers\slugify;
use const App\Config\VERSION;

Der führende Backslash

Ein \ am Anfang meint „ab dem globalen Namespace". Innerhalb eines eigenen Namespaces brauchst du es, um auf eingebaute Klassen zuzugreifen:

namespace App;
$d = new \DateTime();   // global, nicht App\DateTime
PSR-4: Namespace = OrdnerHalte dich an die PSR-4-Konvention: Der Namespace spiegelt den Ordnerpfad. App\Service\Mailer liegt in src/Service/Mailer.php. Dann findet Composers Autoloader jede Klasse automatisch – du schreibst nie wieder ein require für Klassen.
Teil 6

Architektur & Patterns

Wie man Code so strukturiert, dass er wachsen, sich ändern und testen lässt. SOLID, Dependency Injection, die wichtigsten Entwurfsmuster und Wertobjekte.
33 · SOLID-Prinzipien34 · Dependency Injection35 · Häufige Entwurfsmuster36 · Wertobjekte & DTOs37 · Fehlerbehandlung als Architektur
Kapitel 33

SOLID-Prinzipien

SOLID sind fünf Faustregeln für Klassen, die langlebig und änderbar bleiben. Sie sind keine Gesetze, sondern Werkzeuge gegen den schleichenden Verfall großer Codebasen.

S – Single Responsibility

Eine Klasse sollte genau einen Grund haben, sich zu ändern. Eine Klasse, die Daten lädt und formatiert und verschickt, ändert sich aus drei Richtungen – das ist eine zu viel.

O – Open/Closed

Offen für Erweiterung, geschlossen für Änderung. Neues Verhalten fügst du durch neue Klassen hinzu, nicht durch das Aufbohren bestehender:

interface Rabatt {
    public function anwenden(float $preis): float;
}
// neuer Rabatt = neue Klasse, alter Code bleibt unberührt
class Weihnachtsrabatt implements Rabatt { /* ... */ }

L – Liskov Substitution

Eine Unterklasse muss überall einsetzbar sein, wo die Oberklasse erwartet wird – ohne Überraschungen. Wenn eine Unterklasse plötzlich Ausnahmen wirft, wo die Basis es nicht tut, ist das verletzt.

I – Interface Segregation

Lieber viele kleine, spezifische Interfaces als ein großes. Eine Klasse soll nicht Methoden implementieren müssen, die sie gar nicht braucht.

D – Dependency Inversion

Hänge von Abstraktionen ab, nicht von konkreten Klassen. Statt im Code new MySQLConnection() zu schreiben, nimm ein Connection-Interface entgegen – das ist die Brücke zu Dependency Injection.

i
SOLID mit AugenmaßSOLID hilft, aber dogmatisch angewandt führt es zu einer Flut winziger Klassen. Die Prinzipien zahlen sich dort aus, wo sich Code oft ändert oder von mehreren Stellen genutzt wird. Bei kurzlebigem oder trivialem Code ist Pragmatismus die bessere Tugend.
Kapitel 34

Dependency Injection

Dependency Injection (DI) heißt schlicht: Ein Objekt bekommt seine Abhängigkeiten von außen gereicht, statt sie selbst zu erzeugen. Das macht Code testbar und flexibel.

Das Problem

Erzeugt eine Klasse ihre Helfer selbst, ist sie fest verdrahtet – nicht austauschbar, nicht testbar:

class Bestellung {
    public function __construct() {
        $this->mailer = new SmtpMailer();   // fest verdrahtet
    }
}

Die Lösung: von außen reichen

class Bestellung {
    public function __construct(
        private Mailer $mailer,   // Interface, injiziert
    ) {}
}
// Produktion:
new Bestellung(new SmtpMailer());
// Test:
new Bestellung(new FakeMailer());

Der DI-Container

Bei vielen verschachtelten Abhängigkeiten verdrahtest du nicht alles von Hand. Ein Container baut Objekte samt ihrer Abhängigkeiten automatisch zusammen:

$container->get(Bestellung::class);
// Container erkennt: braucht Mailer, baut SmtpMailer, ...

Container lesen die nötigen Typen über Reflection (Kapitel 31) aus den Konstruktor-Signaturen. Genau deshalb sind Typdeklarationen so wichtig.

Constructor Injection als StandardReiche Abhängigkeiten über den Konstruktor herein – nicht über Setter oder globale Zugriffe. Dann ist an der Signatur sofort ablesbar, was eine Klasse braucht, und ein unvollständig konfiguriertes Objekt kann gar nicht erst entstehen.
Kapitel 35

Häufige Entwurfsmuster

Entwurfsmuster sind bewährte Lösungen für wiederkehrende Probleme. Du musst sie nicht auswendig kennen – aber die Namen helfen, über Architektur zu reden.

Strategy

Austauschbare Algorithmen hinter einem gemeinsamen Interface – das Open/Closed-Prinzip in Aktion:

interface SortierStrategie {
    public function sortiere(array $daten): array;
}
// QuickSort, MergeSort … jeweils eine Klasse, frei tauschbar

Factory

Eine Methode, die Objekte erzeugt und die Entscheidung kapselt, welche konkrete Klasse:

class ZahlungsFactory {
    public static function erstelle(string $art): Zahlung {
        return match($art) {
            "paypal" => new PayPal(),
            "karte"  => new Kreditkarte(),
        };
    }
}

Observer

Objekte abonnieren Ereignisse und werden benachrichtigt – die Basis von Event-Systemen:

interface Beobachter {
    public function benachrichtigt(Ereignis $e): void;
}

Repository

Kapselt den Datenzugriff hinter einer sammlungsartigen Schnittstelle – der Code dahinter (SQL, API, Datei) bleibt verborgen:

interface UserRepository {
    public function finde(int $id): ?User;
    public function speichere(User $u): void;
}
!
Muster sind Mittel, kein ZielDer häufigste Fehler mit Entwurfsmustern ist, sie überall einzusetzen, weil man sie gerade gelernt hat. Ein Muster ist eine Antwort auf ein konkretes Problem. Hast du das Problem nicht, brauchst du das Muster nicht – einfacher Code schlägt clevere Architektur ohne Anlass.
Kapitel 36

Wertobjekte & DTOs

Statt nackte Strings und Zahlen durch den Code zu reichen, kapselst du Bedeutung in kleine Typen. Das macht Fehler unmöglich, die sonst erst zur Laufzeit auffallen.

Das Problem mit Primitiven

function versende(string $email): void { /* ... */ }
versende("kein-email");   // kompiliert, kracht erst später

Ein Wertobjekt

Validiere einmal bei der Erzeugung – danach ist die Gültigkeit garantiert. readonly macht es unveränderlich:

final readonly class Email {
    public function __construct(public string $wert) {
        if (!filter_var($wert, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException("Ungültige E-Mail");
        }
    }
}
function versende(Email $email): void { /* immer gültig */ }

DTO – Data Transfer Object

Ein DTO bündelt zusammengehörige Daten in einem typisierten Objekt – etwa Formulareingaben oder API-Antworten. Kein Verhalten, nur Struktur:

final readonly class RegistrierDaten {
    public function __construct(
        public string $name,
        public Email $email,
        public int $alter,
    ) {}
}
Primitive Obsession bekämpfenDas Durchreichen von rohen Strings und Ints für Dinge mit Bedeutung (E-Mail, Geldbetrag, ID) nennt man „Primitive Obsession”. Ein kleines Wertobjekt kostet wenige Zeilen, verlagert aber eine ganze Klasse von Fehlern von der Laufzeit in den Moment der Erzeugung.
Kapitel 37

Fehlerbehandlung als Architektur

Wie eine Anwendung mit Fehlern umgeht, ist eine Architektur-Entscheidung. Gut gemacht, bleiben Fehler nachvollziehbar und die Geschäftslogik sauber.

Eigene Exception-Typen

Spezifische Exceptions erlauben gezieltes Abfangen und sprechende Fehler:

class KontoUeberzogen extends DomainException {
    public static function bei(float $fehlbetrag): self {
        return new self("Es fehlen $fehlbetrag €");
    }
}

Wo fangen, wo durchreichen?

Eine bewährte Regel: Fange Exceptions möglichst weit oben – an der „Grenze" der Anwendung (Controller, CLI-Einstieg). Die Geschäftslogik wirft nur, sie behandelt nicht. So bleibt sie frei von try/catch-Rauschen:

// Controller – die eine zentrale Stelle
try {
    $service->verarbeite($daten);
} catch (DomainException $e) {
    return fehlerSeite($e->getMessage());
}

Result statt Exception

Für erwartbare Fehlschläge (Validierung) sind Exceptions manchmal zu schwer. Eine Alternative ist ein Ergebnis-Objekt, das Erfolg oder Fehler trägt:

if ($ergebnis->istErfolg()) {
    $wert = $ergebnis->wert();
} else {
    echo $ergebnis->fehler();
}
i
Exceptions für AusnahmenDer Name sagt es: Exceptions sind für Ausnahmen gedacht – Dinge, die nicht im normalen Ablauf liegen. Ein fehlgeschlagenes Login ist erwartbar und gehört eher als Ergebnis modelliert; eine verlorene Datenbankverbindung ist eine echte Ausnahme. Diese Unterscheidung hält den Kontrollfluss klar.
Teil 7

Qualität & Profi-Werkzeug

Was professionellen Code von Bastelei trennt: automatisierte Tests, statische Analyse, einheitlicher Stil und systematisches Debugging.
38 · Testen mit PHPUnit39 · Statische Analyse40 · Code-Style & Tooling41 · Debugging & Xdebug
Kapitel 38

Testen mit PHPUnit

Automatisierte Tests prüfen bei jeder Änderung, ob dein Code noch das Richtige tut. PHPUnit ist der De-facto-Standard dafür in der PHP-Welt.

Installation

composer require --dev phpunit/phpunit

Ein erster Test

Ein Test ist eine Methode, die etwas ausführt und mit assert-Aufrufen das erwartete Ergebnis prüft:

use PHPUnit\Framework\TestCase;

final class RechnerTest extends TestCase {
    public function testAddition(): void {
        $r = new Rechner();
        $this->assertSame(5, $r->addiere(2, 3));
    }
}

Ausführen:

vendor/bin/phpunit
# OK (1 test, 1 assertion)

Die wichtigsten Assertions

AssertionPrüft
assertSame($a, $b)identisch (===)
assertEquals($a, $b)gleich (==)
assertTrue / assertFalseWahrheitswert
assertNullist null
assertCount(3, $arr)Anzahl Elemente
expectException(...)Fehler wird geworfen

Data Provider – ein Test, viele Fälle

Statt fünf fast gleiche Tests zu schreiben, fütterst du einen Test mit Datensätzen:

#[DataProvider("zahlen")]
public function testQuadrat(int $ein, int $aus): void {
    $this->assertSame($aus, quadrat($ein));
}
public static function zahlen(): array {
    return [[2, 4], [3, 9], [5, 25]];
}

Mocks – Abhängigkeiten ersetzen

Um eine Klasse isoliert zu testen, ersetzt du ihre Abhängigkeiten durch kontrollierte Attrappen:

$mailer = $this->createMock(Mailer::class);
$mailer->expects($this->once())->method("sende");
Erst der Test, dann der FehlerWenn du einen Bug findest, schreib zuerst einen Test, der ihn reproduziert – er schlägt fehl. Dann reparierst du den Code, bis der Test grün ist. So weißt du sicher, dass der Fehler weg ist und nie unbemerkt zurückkehrt.
Kapitel 39

Statische Analyse

Statische Analyse findet Fehler, ohne den Code auszuführen – allein durch das Lesen. Werkzeuge wie PHPStan und Psalm fangen ganze Fehlerklassen ab, bevor sie je zur Laufzeit auftreten.

Was sie finden

PHPStan einsetzen

composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse src --level 6

Die Level reichen von 0 (locker) bis 10 (sehr streng). Ein guter Weg: niedrig starten und Stufe für Stufe erhöhen, während du die gemeldeten Probleme abarbeitest.

Generics über PHPDoc

Hier zahlt sich das Generics-Denken aus Kapitel 27 aus. PHPStan versteht die Annotationen und prüft sie:

/** @return list<User> */
public function alle(): array { /* ... */ }

Baseline für Altprojekte

In bestehendem Code meldet PHPStan oft hunderte Probleme. Eine Baseline friert die aktuellen Funde ein, sodass nur neue Fehler auffallen:

vendor/bin/phpstan analyse --generate-baseline
Statische Analyse in CILass PHPStan automatisch bei jedem Push laufen (Continuous Integration). Dann kommt fehlerhafter Code gar nicht erst in den Hauptzweig. In Kombination mit declare(strict_types=1) und Tests bildet das ein engmaschiges Sicherheitsnetz.
Kapitel 40

Code-Style & Tooling

Einheitlicher Stil macht Code lesbar und beendet sinnlose Diskussionen über Klammern und Einrückung. Werkzeuge erledigen die Formatierung automatisch.

PSR-12 – der Standard

Die PHP-Community hat sich auf einen gemeinsamen Stil geeinigt: PSR-12. Er regelt Einrückung (4 Leerzeichen), Klammersetzung, Import-Reihenfolge und mehr. Du musst ihn nicht auswendig lernen – Werkzeuge setzen ihn durch.

PHP-CS-Fixer

Formatiert deinen Code automatisch nach festgelegten Regeln:

composer require --dev friendsofphp/php-cs-fixer
vendor/bin/php-cs-fixer fix src

Composer-Skripte als Abkürzung

Häufige Befehle bündelst du in composer.json – ein Befehl statt vieler:

{
  "scripts": {
    "check": [
      "@php vendor/bin/phpunit",
      "@php vendor/bin/phpstan analyse src",
      "@php vendor/bin/php-cs-fixer fix --dry-run"
    ]
  }
}

Danach genügt composer check, um Tests, Analyse und Stilprüfung in einem Rutsch laufen zu lassen.

Der typische Werkzeugkasten

AufgabeWerkzeug
PaketeComposer
TestsPHPUnit / Pest
Statische AnalysePHPStan / Psalm
FormatierungPHP-CS-Fixer / PHPCS
DebuggingXdebug
i
Stil ist Konvention, nicht GeschmackWelcher Stil „besser” ist, spielt kaum eine Rolle – wichtig ist, dass ein Projekt einen hat und ihn automatisch durchsetzt. Mit einem Formatierer im Editor-Speichern-Hook denkst du nie wieder über Einrückung nach.
Kapitel 41

Debugging & Xdebug

Früher oder später läuft Code anders als gedacht. Statt mit echo zu raten, schaust du mit einem Debugger Schritt für Schritt zu, was wirklich passiert.

Der schnelle Weg: var_dump & dd

Für einen kurzen Blick reicht oft eine Ausgabe. var_dump zeigt Wert und Typ:

var_dump($daten);
// in Frameworks oft: dump($daten) oder dd($daten) (dump and die)

Xdebug – der richtige Debugger

Xdebug ist eine PHP-Erweiterung. Einmal eingerichtet, kannst du im Editor Breakpoints setzen: Das Programm hält dort an, und du untersuchst alle Variablen live.

# Installation (Beispiel Ubuntu)
sudo apt install php-xdebug

Im Editor (z. B. PHPStorm oder VS Code) aktivierst du „Listen for debug connections", setzt einen Breakpoint per Klick an den Zeilenrand und lädst die Seite. Der Ablauf stoppt – du siehst den kompletten Aufruf-Stack.

Die Werkzeuge im Debugger

AktionBedeutung
Step Overnächste Zeile, Funktionen am Stück
Step Intoin die aufgerufene Funktion hinein
Step Outaktuelle Funktion zu Ende
Watcheinen Ausdruck dauerhaft beobachten

Fehler sichtbar machen

In der Entwicklung sollten alle Fehler angezeigt werden – verstecke sie nie still:

error_reporting(E_ALL);
ini_set("display_errors", "1");
// In Produktion: display_errors aus, dafür ins Log schreiben
!
echo-Debugging hat GrenzenMit eingestreuten var_dump-Zeilen kommt man weit – aber bei verschachtelten Aufrufen oder Schleifen verliert man schnell den Überblick. Die halbe Stunde, Xdebug einmal einzurichten, spart über die Zeit viele Stunden Rätselraten.
Teil 8

Experten & Nischen

Die Themen jenseits des Alltags: Performance herausholen, Speicher verstehen, Prozesse und Fibers, robuste CLI-Programme und die Sicherheits-Klassiker.
42 · Performance & OPcache43 · Speicher & Referenzen44 · Prozesse, FFI & Fibers45 · CLI-Programme bauen46 · Sicherheit: die Klassiker
Kapitel 42

Performance & OPcache

PHP ist schnell – wenn man es lässt. Die größten Hebel liegen selten im Mikro-Tuning einzelner Zeilen, sondern in Caching, weniger Arbeit und dem richtigen Werkzeug.

OPcache – kompilierten Code wiederverwenden

Normalerweise übersetzt PHP jede Datei bei jedem Aufruf neu. OPcache speichert das Übersetzungsergebnis im Speicher – ein gewaltiger Gewinn, fast geschenkt:

; php.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000

In Produktion ist OPcache praktisch Pflicht. Es ist der wirkungsvollste einzelne Performance-Schalter.

Messen statt raten

Optimiere nie nach Bauchgefühl. Ein Profiler (Xdebug, Blackfire, SPX) zeigt, wo die Zeit wirklich verbraucht wird – oft an völlig anderen Stellen als vermutet:

$start = hrtime(true);
// ... Code ...
$ms = (hrtime(true) - $start) / 1_000_000;

Die häufigste Bremse: N+1

Ein klassischer Datenbank-Fehler: In einer Schleife für jeden Datensatz eine eigene Abfrage. 100 Nutzer = 101 Abfragen statt einer. Lade verwandte Daten gebündelt.

JIT – mit Augenmaß

Seit PHP 8 gibt es einen JIT-Compiler. Er beschleunigt rechenintensive Aufgaben (Bildverarbeitung, Mathematik) spürbar, bringt bei typischem Web-Code mit viel I/O aber kaum etwas.

Reihenfolge der OptimierungErst OPcache und einen Profiler einschalten, dann die teuersten Stellen finden, dann gezielt verbessern – meist sind es Datenbankabfragen oder fehlendes Caching. Mikro-Optimierungen einzelner Funktionen lohnen fast nie und machen den Code schwerer lesbar.
Kapitel 43

Speicher & Referenzen

PHP verwaltet Speicher automatisch, aber zu verstehen, wie Werte kopiert und freigegeben werden, hilft bei großen Datenmengen und kniffligen Bugs.

Copy-on-Write

Weist du eine Variable einer anderen zu, kopiert PHP den Wert nicht sofort – erst wenn einer der beiden geändert wird. Das spart Speicher, ohne dass du etwas tun musst:

$a = range(1, 1_000_000);
$b = $a;        // noch keine Kopie, beide teilen sich
$b[0] = 99;   // jetzt erst wird kopiert

Referenzen mit &

Eine Referenz ist ein zweiter Name für dieselbe Variable. Änderungen wirken auf beide:

$a = 1;
$b = &$a;     // $b ist $a
$b = 99;
echo $a;       // 99

Garbage Collection

PHP zählt, wie viele Namen auf einen Wert zeigen. Sinkt der Zähler auf null, wird der Speicher frei. Zirkuläre Referenzen (A zeigt auf B, B auf A) fängt ein zusätzlicher Collector ab.

Speicher im Blick behalten

echo memory_get_usage(true);        // aktuell
echo memory_get_peak_usage(true);   // Höchststand
!
Referenzen sind selten nötigAnfänger greifen oft zu &, um „Performance zu sparen” – dank Copy-on-Write ist das fast nie nötig und führt zu schwer auffindbaren Bugs, wenn unerwartet eine entfernte Variable mitverändert wird. Nutze Referenzen nur, wenn du sie wirklich brauchst (etwa sort(), das sein Argument verändert).
Kapitel 44

Prozesse, FFI & Fibers

Drei fortgeschrittene Wege, über die übliche Request-Verarbeitung hinauszugehen: externe Programme starten, C-Bibliotheken einbinden und kooperatives Multitasking.

Externe Programme starten

Manchmal ist das beste Werkzeug ein anderes Programm. proc_open gibt dir volle Kontrolle über dessen Ein- und Ausgabe:

$out = shell_exec("git rev-parse HEAD");
// für einfache Fälle; bei Nutzereingaben unbedingt escapen!

FFI – C-Bibliotheken aufrufen

Mit der Foreign Function Interface bindest du vorhandene C-Bibliotheken direkt ein, ohne eine PHP-Erweiterung zu schreiben:

$ffi = FFI::cdef(
    "int abs(int);",
    "libc.so.6"
);
echo $ffi->abs(-42);   // 42

FFI ist ein Nischenwerkzeug – nützlich, um Spezialbibliotheken anzuzapfen, aber selten im Alltag.

Fibers – kooperatives Multitasking

Seit PHP 8.1 gibt es Fibers: Funktionen, die sich selbst pausieren und später fortsetzen können. Sie sind die Grundlage moderner asynchroner Frameworks wie ReactPHP und Amp:

$fiber = new Fiber(function() {
    $wert = Fiber::suspend("pausiert");
    echo "weiter mit $wert";
});
$x = $fiber->start();   // "pausiert"
$fiber->resume("Daten");   // weiter mit Daten
i
Selten gebraucht, gut zu kennenFibers nutzt du fast nie direkt – die asynchronen Frameworks kapseln sie. Aber zu wissen, dass PHP kooperatives Multitasking beherrscht, hilft beim Verständnis, wie etwa Swoole oder Amp Tausende gleichzeitige Verbindungen in einem Prozess bewältigen.
Kapitel 45

CLI-Programme bauen

PHP ist nicht nur für Webseiten da. Kommandozeilen-Programme – für Wartung, Datenimport, Cronjobs – sind ein wichtiger Einsatzbereich.

Argumente lesen

Das Array $argv enthält die übergebenen Argumente, $argc ihre Anzahl:

// php import.php datei.csv --dry-run
echo $argv[1];   // datei.csv

Ein-/Ausgabe-Ströme

CLI-Programme nutzen drei Standard-Kanäle. Fehler gehören nach STDERR, damit sie sich von der normalen Ausgabe trennen lassen:

fwrite(STDOUT, "Fertig\n");
fwrite(STDERR, "Warnung!\n");
$eingabe = fgets(STDIN);   // vom Nutzer lesen

Exit-Codes

Ein Programm meldet Erfolg oder Misserfolg über seinen Rückgabewert: 0 = alles gut, alles andere = Fehler. Skripte und CI verlassen sich darauf:

if ($fehler) {
    fwrite(STDERR, "Abbruch\n");
    exit(1);
}
exit(0);

Komfort mit Symfony Console

Für ernsthafte CLI-Werkzeuge lohnt eine Bibliothek. Symfony Console liefert Argument-Parsing, Hilfetexte, Farben, Fortschrittsbalken und Tabellen:

composer require symfony/console

Damit definierst du Befehle als Klassen, bekommst --help automatisch und eine saubere Struktur für wachsende Werkzeuge.

Shebang für direkte AusführungBeginnt dein Skript mit #!/usr/bin/env php und ist es ausführbar (chmod +x), kannst du es ohne vorangestelltes php direkt aufrufen – wie jedes andere Kommandozeilen-Programm.
Kapitel 46

Sicherheit: die Klassiker

Die meisten Sicherheitslücken sind seit Jahren dieselben – und seit Jahren vermeidbar. Wer diese Klassiker kennt, schließt die gefährlichsten Türen.

SQL-Injection

Niemals Nutzereingaben in SQL einbauen. Prepared Statements trennen Befehl und Daten – die Eingabe kann nie als Code interpretiert werden:

// FALSCH – angreifbar
$db->query("SELECT * FROM users WHERE id = $id");

// RICHTIG – Prepared Statement
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);

XSS – Cross-Site Scripting

Gibst du Nutzereingaben in HTML aus, ohne sie zu maskieren, kann jemand Schadcode einschleusen. htmlspecialchars entschärft das:

echo htmlspecialchars($kommentar, ENT_QUOTES);

CSRF – gefälschte Anfragen

Ein Angreifer bringt den Browser eines eingeloggten Nutzers dazu, ungewollt Aktionen auszuführen. Schutz: ein geheimes, pro Formular einzigartiges Token, das der Server prüft.

Passwörter richtig speichern

Niemals im Klartext, niemals mit MD5 oder SHA1. PHP bringt die richtige Funktion mit – sie hasht, salzt und passt die Stärke automatisch an:

$hash = password_hash($passwort, PASSWORD_DEFAULT);

if (password_verify($eingabe, $hash)) {
    // Login korrekt
}

Grundregeln

RegelWarum
Eingaben validierentraue keiner Quelle von außen
Ausgaben maskierenverhindert XSS
Prepared Statementsverhindert SQL-Injection
password_hashsichere Passwörter
Abhängigkeiten aktuell haltencomposer audit
!
Sicherheit ist nicht optionalValidiere alles, was von außen kommt – Formulare, URLs, Header, hochgeladene Dateien. Die Faustregel lautet: niemals Nutzereingaben vertrauen. composer audit meldet zudem bekannte Lücken in deinen Abhängigkeiten – führe es regelmäßig aus.
```