diff --git a/backend/config.py b/backend/config.py index 52125c9..0fc1c7b 100644 --- a/backend/config.py +++ b/backend/config.py @@ -10,6 +10,7 @@ ALLOWED_FORMATS = [ "OnePager", "Cheatsheet", "MiniGuide", + "Guide", "BeginnerGuide", "IntermediateGuide", "ExtendedGuide", @@ -19,6 +20,7 @@ FORMAT_META = { "OnePager": {"pages": "1 Seite", "time": "~5 Min"}, "Cheatsheet": {"pages": "1 Seite", "time": "~10 Min"}, "MiniGuide": {"pages": "3-4 Seiten", "time": "~15 Min"}, + "Guide": {"pages": "15-250 Seiten", "time": "variabel"}, "BeginnerGuide": {"pages": "35-40 Seiten", "time": "~3h"}, "IntermediateGuide": {"pages": "42-50 Seiten", "time": "~4h"}, "ExtendedGuide": {"pages": "47-60 Seiten", "time": "~5h"}, diff --git a/backend/models.py b/backend/models.py index 3b2a598..33c441b 100644 --- a/backend/models.py +++ b/backend/models.py @@ -5,6 +5,7 @@ FormatType = Literal[ "OnePager", "Cheatsheet", "MiniGuide", + "Guide", "BeginnerGuide", "IntermediateGuide", "ExtendedGuide", diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 96b70a5..5ac44b4 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -77,7 +77,7 @@ async function loadGuides() { } } -const FORMAT_ORDER = ['OnePager', 'Cheatsheet', 'MiniGuide', 'BeginnerGuide', 'IntermediateGuide', 'ExtendedGuide'] +const FORMAT_ORDER = ['OnePager', 'Cheatsheet', 'MiniGuide', 'Guide', 'BeginnerGuide', 'IntermediateGuide', 'ExtendedGuide'] function autoPreview() { const map = doneByFormat.value diff --git a/frontend/src/components/TopicSidebar.vue b/frontend/src/components/TopicSidebar.vue index 7d651e5..f50a219 100644 --- a/frontend/src/components/TopicSidebar.vue +++ b/frontend/src/components/TopicSidebar.vue @@ -26,6 +26,7 @@ const formats = [ { key: 'OnePager', label: 'OnePager' }, { key: 'Cheatsheet', label: 'Cheatsheet' }, { key: 'MiniGuide', label: 'MiniGuide' }, + { key: 'Guide', label: 'Guide' }, { key: 'BeginnerGuide', label: 'BeginnerGuide' }, { key: 'IntermediateGuide', label: 'IntermediateGuide' }, { key: 'ExtendedGuide', label: 'ExtendedGuide' }, diff --git a/templates/Format/Guide.md b/templates/Format/Guide.md new file mode 100644 index 0000000..84fbc8e --- /dev/null +++ b/templates/Format/Guide.md @@ -0,0 +1,760 @@ +``` +# Guide Style System — Authoring & Build Specification + +This document is a **complete, self-contained specification** for producing a polished, book-style guide as a single HTML file that renders to a clean A4 PDF. It is **topic-neutral**: use it for programming subjects (PHP, Godot, Blender) and equally for non-technical ones (nutrition, finance, psychology, communication, language learning, …). + +You will normally be given two things: this specification and possibly one reference HTML file built with it. From those alone you must be able to: + +1. Gather the subject knowledge yourself (research as needed). +2. Decide a structure (parts → chapters). +3. Write a single HTML file that embeds the CSS from this document verbatim. +4. Convert that HTML to PDF. + +Follow this spec exactly. The visual identity depends on small details (spacing, weights, the single accent color), so do not improvise the CSS. You **do** have full freedom over content, structure, length, and which optional building blocks you use. + +--- + +## 1. Output contract + +- **One HTML file**, self-contained: a single ` + + + + +
+ + +
+ + +
+
+
+ + + +
+
+ + + + +``` + +You may generate this HTML however you like — by hand, or with a small script that concatenates strings. A script helps for long guides because it keeps each chapter in its own readable chunk. If you use a script, **the script is throwaway**; the deliverable is the HTML (and the PDF), not the script. + +--- + +## 6. The building blocks + +Below is the exact markup for every block. Copy these shapes; fill in content. + +### 6.1 Cover + +```html +
+ +

MAIN TITLESubtitle line

+
One or two sentences describing what the guide covers and who it is for.
+
+ N Parts · M Chapters + Edition / version · Year + Focus: … +
+
+
+``` + +- **`.cover-logo`** — 1–4 characters or a single symbol that evokes the topic. Examples: `php`, `gd` (Godot), a Blender-style `b`, `€` (finance), `Ψ` (psychology), `EN` (English), `🍎` is **not** allowed (no emoji in the logo — keep it crisp). Prefer short letterforms or a geometric glyph (`◆ ● ▲ ■`). +- **`.cover h1`** — the big title. The `` is an optional lighter, smaller second line (e.g. `PHP` then `The Complete Guide`). Drop the span for a single-line title. +- **`.cover-deco`** — a giant, very faint background glyph in the bottom-right. Pick something topic-flavored: a code fragment (``s. Use it to state scope (parts/chapters), edition/year, and the focus. This is where you set the reader's expectations about length and depth. + +### 6.2 Table of contents + +List every part, and under it every chapter, numbered sequentially across the whole guide. There are **no real page numbers** (WeasyPrint does not back-fill them here), so the `.toc-page` element is optional — omit it, or use it only if you maintain numbers yourself. The dotted leader still looks right without a trailing number. + +```html +
+

Contents

+ +
Part 1 · Fundamentals
+
+ 1 + First chapter title + +
+
+ 2 + Second chapter title + +
+ +
Part 2 · Going Deeper
+
+ 3 + + +
+
+``` + +### 6.3 Part divider + +One full page that introduces a part. The chapter list mirrors the TOC entries for that part. + +```html +
+
Part 1
+

Fundamentals

+
Italic one-liner describing the arc of this part.
+
+ 1 · First chapter + 2 · Second chapter + 3 · Third chapter +
+
+``` + +### 6.4 Chapter + +The `.chapter-head` is special: its text is captured into the **running header** at the top-right of every page in that chapter (via `string-set: chaptertitle content()`). Note that `content()` concatenates the text of **all** children, so the chapter-number span and the title run together in the header (e.g. "Chapter 1Title"). For a clean separator, end the `.chapter-num` text with a trailing separator such as `Chapter 1 ·` (trailing space + middot) — or accept the run-on; both are acceptable. Either way keep the chapter `

` reasonably short so it fits on one header line. + +```html +
+
+ Chapter 1 +

Chapter title

+
+ +

Framing sentence(s) — why this matters.

+ +

A section

+

Body text. Use bold for the key term in a sentence. Inline monospace + like term works for any short literal — + a command, a key name, a nutrient, a chord, a German case.

+ +

A sub-section

+

+ + +
+``` + +### 6.5 Callouts + +Four flavors. Each has a short uppercase label as the **first ``** inside `.callout-body`, then body text. The icon column holds one glyph. + +```html +
+
+
LABELBody text giving a practical tip.
+
+ +
+
!
+
LABELBody text warning about a trap.
+
+ +
+
i
+
LABELBody text for a neutral side note.
+
+ +
+
+
LABELBody text for an optional deeper dive.
+
+``` + +Meaning of each flavor (topic-independent): + +|Flavor|Color|Use for| +|---|---|---| +|`tip`|green, ✓|best practice, a recommendation, a shortcut| +|`warn`|red, !|a common mistake, risk, or thing to avoid| +|`note`|accent, i|a neutral aside, clarification, context| +|`deep`|gold, ◆|optional depth: history, edge case, advanced detail| + +Standard icon entities: tip `✓` (✓), warn `!`, note `i`, deep `◆` (◆). You may substitute a more fitting single glyph, but keep it one character. + +### 6.6 Tables + +Plain `` with a header row. The styling (accent header, zebra rows) is automatic. Keep tables to a few columns so they fit A4 width. + +```html +
+ + + +
Column AColumn B
valuevalue
valuevalue
+``` + +Tables are one of the most useful blocks for **non-technical** topics too: nutrient comparisons, vocabulary lists, pros/cons, decision matrices, dosage/timing, term glossaries. + +--- + +## 7. Code blocks — optional, technical topics only + +The dark `
` block and the highlight span classes exist for subjects that genuinely involve code or other monospaced literal text (config, shell commands, formulas). **For non-technical guides, do not use `
` blocks at all** — they would look out of place. Use tables, lists, callouts, and worked examples instead. (`code.inline` is fine everywhere for short literals.)
+
+When you do use code, you **hand-write the highlighting** by wrapping tokens in spans. The classes are intentionally generic so they map onto any language:
+
+|Class|Generic meaning|Typical use|
+|---|---|---|
+|`.c`|de-emphasized|comments|
+|`.k`|keyword / control word|`if`, `function`, `class`, `return`|
+|`.s`|literal value|strings, numbers|
+|`.f`|callable name|function / method names|
+|`.t`|type / tag / class|type names, HTML tags, class names|
+|`.v`|identifier|variables|
+|`.a`|annotation|attributes, decorators, directives|
+
+Rules for code blocks:
+
+- Keep snippets **short** (roughly 2–12 lines). They must fit on one page — `page-break-inside: avoid` is set, so an oversized block can overflow. Split long examples into several captioned blocks.
+- **Escape HTML inside code**: write `<`, `>`, `&`. This is the most common rendering bug.
+- Inside the dark block, a comment uses ``. Indentation is literal spaces (the block is `white-space: pre`).
+- You do not need to highlight every token — highlight the ones that aid reading (keywords, strings, names). Plain text inside `
` is fine and renders in the default light color.
+
+Example (PHP-flavored, but the pattern is language-agnostic):
+
+```html
+
function greet(string $name): string {
+    return "Hello, $name";   // interpolation
+}
+``` + +--- + +## 8. Pitfalls — read before building (these caused real bugs) + +1. **Escape `<`, `>`, `&` inside `
` and ``.** Unescaped angle brackets silently swallow content or break layout. Always `< > &`.
+2. **Typographic quotes in prose, straight quotes in attributes.** In body text use the target language's real quotation marks (German `„ … "`, English `" … "`). Never let a quote character inside running text collide with HTML attribute quotes. If you generate the HTML from a script, be careful that closing typographic quotes are not accidentally written as escaped ASCII quotes — that corrupts both the string and any nearby `class="…"`. The safe approach: type real `„ … "` / `" … "` glyphs in prose, and reserve `"` strictly for HTML attributes.
+3. **`--footer-label` must be a quoted CSS string**, e.g. `--footer-label: "Nutrition Guide";`. An unquoted value breaks the `@page` rule.
+4. **Keep code blocks and callouts short enough to fit one page.** `page-break-inside: avoid` prevents splitting but cannot shrink an oversized block; it will overflow the page. Break large blocks up.
+5. **Multibyte text is fine** (umlauts, accents, CJK, symbols) — the fonts and UTF-8 charset handle it. But if you ever measure string length in a generator script, count characters, not bytes.
+6. **Chapter `

` feeds the running header.** Keep it short; a very long chapter title wraps awkwardly in the 8pt header. +7. **Don't add web fonts or external assets.** The look depends on the system-font stack already specified. Adding fonts changes metrics and spacing. +8. **One accent, three shades.** Don't introduce extra brand colors. Variety comes from the callout colors (green/red/gold), which are fixed and meaningful — not decorative. +9. **Tables and code don't split** (`page-break-inside: avoid`). If a table is very long, either let it be its own short section or split it into two. + +--- + +## 9. Build & verify + +1. Write `guide.html` (single file, embedded ` + + + +
+ +

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:

+
    +
  • Exception – „normale" Fehler, die man abfangen sollte
  • +
  • Error – schwere PHP-Fehler (Typfehler, fehlende Funktion)
  • +
  • Spezialisierte Klassen wie RuntimeException, LogicException
  • +
+
+
!
+
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

+
    +
  • Aufrufe nicht existierender Methoden oder Eigenschaften
  • +
  • Typfehler – ein string, wo ein int erwartet wird
  • +
  • null-Zugriffe, die zur Laufzeit krachen würden
  • +
  • toter Code, unerreichbare Zweige
  • +
+ +

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.
+
+
+ + +``` \ No newline at end of file