84 KiB
84 KiB
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>PHP Fortgeschritten-Guide</title>
<style>
@page {
size: A4;
margin: 22mm 20mm 20mm 20mm;
@bottom-center {
content: counter(page) " / " counter(pages);
font-family: -apple-system, "Segoe UI", sans-serif;
font-size: 8pt;
color: #888;
}
@bottom-right {
content: "PHP Fortgeschritten-Guide";
font-family: -apple-system, "Segoe UI", sans-serif;
font-size: 8pt;
color: #888;
}
}
@page :first {
margin: 0;
@bottom-center { content: none; }
@bottom-right { content: none; }
}
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--php: #777BB4;
--php-dark: #4F5B93;
--php-darker: #2C3E66;
--ink: #1a1a1a;
--muted: #5a6470;
--line: #d8dde3;
--bg-soft: #f5f5fb;
--code-bg: #1e2a3a;
--code-fg: #e6e6e6;
--plus: #2c8a3e;
--minus: #c0392b;
}
html, body {
font-family: Charter, "Source Serif Pro", Georgia, serif;
color: var(--ink);
font-size: 10.5pt;
line-height: 1.55;
}
/* ===== COVER ===== */
.cover {
width: 210mm;
height: 297mm;
padding: 35mm 25mm;
background: linear-gradient(135deg, var(--php-dark) 0%, var(--php-darker) 100%);
color: white;
display: flex;
flex-direction: column;
justify-content: space-between;
page-break-after: always;
}
.cover-top { display: flex; align-items: center; gap: 8mm; }
.cover-logo {
width: 28mm; height: 28mm;
background: linear-gradient(135deg, var(--php) 0%, white 100%);
border-radius: 7mm;
display: flex; align-items: center; justify-content: center;
font-family: -apple-system, sans-serif;
font-size: 22pt; font-weight: 800;
color: var(--php-dark);
}
.cover-meta {
font-family: -apple-system, sans-serif;
font-size: 9pt;
text-transform: uppercase;
letter-spacing: 2pt;
opacity: 0.8;
}
.cover-main h1 {
font-family: -apple-system, sans-serif;
font-size: 56pt;
font-weight: 800;
letter-spacing: -2pt;
line-height: 0.95;
margin-bottom: 8mm;
}
.cover-main h1 .accent { color: var(--php); }
.cover-main .subtitle {
font-size: 16pt;
font-weight: 400;
line-height: 1.3;
opacity: 0.9;
font-family: -apple-system, sans-serif;
max-width: 130mm;
}
.cover-bottom {
display: grid;
grid-template-columns: 1fr auto;
gap: 10mm;
align-items: end;
font-family: -apple-system, sans-serif;
}
.cover-promise {
font-size: 10pt;
line-height: 1.5;
opacity: 0.85;
max-width: 110mm;
padding-top: 4mm;
border-top: 1pt solid rgba(255,255,255,0.3);
}
.cover-promise b { color: var(--php); text-transform: uppercase; letter-spacing: 1pt; font-size: 8pt; display: block; margin-bottom: 2mm; }
.cover-tag {
background: var(--php);
color: white;
padding: 3mm 6mm;
border-radius: 2mm;
font-weight: 800;
font-size: 11pt;
}
/* ===== TOC ===== */
.toc { page-break-after: always; }
.toc h2 {
font-family: -apple-system, sans-serif;
font-size: 24pt;
font-weight: 800;
color: var(--php-dark);
margin-bottom: 6mm;
border-bottom: 2pt solid var(--php);
padding-bottom: 3mm;
}
.toc-section {
font-family: -apple-system, sans-serif;
font-size: 9pt;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1pt;
color: var(--muted);
margin: 6mm 0 2mm 0;
}
.toc-item {
display: grid;
grid-template-columns: 8mm 1fr auto;
gap: 4mm;
padding: 2.5mm 0;
border-bottom: 0.3pt solid var(--line);
align-items: baseline;
}
.toc-num {
font-family: -apple-system, sans-serif;
font-size: 14pt;
font-weight: 800;
color: var(--php);
line-height: 1;
}
.toc-text { font-family: -apple-system, sans-serif; }
.toc-text h3 {
font-size: 10.5pt;
font-weight: 700;
color: var(--ink);
margin-bottom: 0.5mm;
}
.toc-text p {
font-size: 8.5pt;
color: var(--muted);
margin: 0;
}
.toc-time {
font-family: -apple-system, sans-serif;
font-size: 7.5pt;
color: var(--muted);
background: var(--bg-soft);
padding: 0.8mm 2.5mm;
border-radius: 1.5mm;
white-space: nowrap;
}
/* ===== CHAPTER ===== */
.chapter { page-break-before: always; }
.chapter-head {
display: grid;
grid-template-columns: auto 1fr;
gap: 6mm;
align-items: center;
border-bottom: 2pt solid var(--ink);
padding-bottom: 4mm;
margin-bottom: 6mm;
}
.chapter-num {
font-family: -apple-system, sans-serif;
font-size: 42pt;
font-weight: 800;
color: var(--php);
line-height: 0.9;
width: 22mm;
text-align: center;
}
.chapter-title h1 {
font-family: -apple-system, sans-serif;
font-size: 22pt;
font-weight: 800;
color: var(--php-dark);
letter-spacing: -0.5pt;
line-height: 1.1;
}
.chapter-title .subtitle {
font-family: -apple-system, sans-serif;
font-size: 11pt;
color: var(--muted);
margin-top: 1.5mm;
}
/* ===== GAP ===== */
.gap {
background: var(--bg-soft);
border-left: 3pt solid var(--php);
padding: 4mm 5mm;
margin: 4mm 0 6mm 0;
font-style: italic;
font-size: 10.5pt;
}
.gap b {
font-style: normal;
color: var(--php-dark);
font-family: -apple-system, sans-serif;
text-transform: uppercase;
font-size: 8pt;
letter-spacing: 1pt;
display: block;
margin-bottom: 1.5mm;
}
/* ===== SECTIONS ===== */
.chapter h2 {
font-family: -apple-system, sans-serif;
font-size: 14pt;
font-weight: 700;
color: var(--php-dark);
margin: 7mm 0 3mm 0;
page-break-after: avoid;
}
.chapter h3 {
font-family: -apple-system, sans-serif;
font-size: 11pt;
font-weight: 700;
color: var(--ink);
margin: 5mm 0 2mm 0;
page-break-after: avoid;
}
.chapter p {
margin-bottom: 3mm;
text-align: justify;
hyphens: auto;
}
.chapter p b { color: var(--php-dark); }
.chapter ul, .chapter ol { margin: 2mm 0 4mm 6mm; }
.chapter li { margin-bottom: 1.5mm; }
/* ===== CODE ===== */
.chapter pre {
background: var(--code-bg);
color: var(--code-fg);
font-family: "SF Mono", Consolas, monospace;
font-size: 8.5pt;
line-height: 1.5;
padding: 3mm 4mm;
border-radius: 2mm;
margin: 3mm 0 4mm 0;
white-space: pre;
overflow: hidden;
page-break-inside: avoid;
}
.c { color: #6b8aae; font-style: italic; }
.k { color: #ff79c6; }
.s { color: #f1c40f; }
.f { color: #50fa7b; }
.t { color: #8be9fd; }
.v { color: #ffb86c; }
code.inline {
font-family: "SF Mono", Consolas, monospace;
font-size: 9pt;
background: var(--bg-soft);
padding: 0.3mm 1.5mm;
border-radius: 1mm;
color: var(--php-dark);
}
/* ===== CALLOUTS ===== */
.callout {
border-radius: 2mm;
padding: 3mm 4mm;
margin: 4mm 0;
font-size: 10pt;
page-break-inside: avoid;
display: grid;
grid-template-columns: 6mm 1fr;
gap: 3mm;
align-items: start;
}
.callout-icon {
font-family: -apple-system, sans-serif;
font-weight: 800;
font-size: 14pt;
line-height: 1;
text-align: center;
}
.callout-body > b:first-child {
font-family: -apple-system, sans-serif;
text-transform: uppercase;
font-size: 8pt;
letter-spacing: 1pt;
display: block;
margin-bottom: 1.5mm;
}
.callout.tip { background: #e8f4ea; border-left: 3pt solid var(--plus); }
.callout.tip .callout-icon, .callout.tip .callout-body > b:first-child { color: var(--plus); }
.callout.warn { background: #fdecea; border-left: 3pt solid var(--minus); }
.callout.warn .callout-icon, .callout.warn .callout-body > b:first-child { color: var(--minus); }
.callout.note { background: var(--bg-soft); border-left: 3pt solid var(--php); }
.callout.note .callout-icon, .callout.note .callout-body > b:first-child { color: var(--php-dark); }
/* ===== RECALL ===== */
.recall {
background: linear-gradient(135deg, var(--php-dark) 0%, var(--php-darker) 100%);
color: white;
padding: 5mm 6mm;
border-radius: 2mm;
margin: 6mm 0 0 0;
page-break-inside: avoid;
}
.recall b {
font-family: -apple-system, sans-serif;
display: block;
text-transform: uppercase;
letter-spacing: 1.5pt;
font-size: 8.5pt;
color: var(--php);
margin-bottom: 2.5mm;
}
.recall ol {
margin: 0;
padding-left: 5mm;
font-size: 10pt;
}
.recall li {
margin-bottom: 1.5mm;
color: rgba(255,255,255,0.95);
}
.recall li::marker { color: var(--php); font-weight: 700; }
/* ===== TABLES ===== */
.chapter table {
width: 100%;
border-collapse: collapse;
margin: 3mm 0 4mm 0;
font-size: 9.5pt;
font-family: -apple-system, sans-serif;
}
.chapter th {
background: var(--php-dark);
color: white;
padding: 2mm 3mm;
text-align: left;
font-weight: 700;
font-size: 9pt;
text-transform: uppercase;
letter-spacing: 0.5pt;
}
.chapter td {
padding: 2mm 3mm;
border-bottom: 0.5pt solid var(--line);
vertical-align: top;
}
.chapter td code {
font-family: "SF Mono", Consolas, monospace;
font-size: 8.5pt;
color: var(--php-dark);
}
/* ===== ENDING ===== */
.ending { page-break-before: always; }
.ending h1 {
font-family: -apple-system, sans-serif;
font-size: 28pt;
font-weight: 800;
color: var(--php-dark);
margin-bottom: 6mm;
border-bottom: 2pt solid var(--php);
padding-bottom: 3mm;
}
.ending h2 {
font-family: -apple-system, sans-serif;
font-size: 14pt;
font-weight: 700;
color: var(--php-dark);
margin: 7mm 0 3mm 0;
}
.spaced-plan {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 3mm;
margin: 4mm 0;
}
.spaced-day {
background: var(--bg-soft);
border-top: 3pt solid var(--php);
padding: 4mm 3mm;
border-radius: 2mm;
}
.spaced-day b {
font-family: -apple-system, sans-serif;
display: block;
color: var(--php-dark);
font-size: 9pt;
text-transform: uppercase;
letter-spacing: 0.5pt;
margin-bottom: 2mm;
}
.spaced-day p {
font-size: 9pt;
margin: 0;
text-align: left;
}
</style>
</head>
<body>
<!-- ===== COVER ===== -->
<section class="cover">
<div class="cover-top">
<div class="cover-logo">php</div>
<div class="cover-meta">Fortgeschritten-Guide · 3h · Stand 2026</div>
</div>
<div class="cover-main">
<h1>PHP<br><span class="accent">tiefer.</span></h1>
<p class="subtitle">Von Interfaces und Generators bis Tests – Production-PHP, das hält.</p>
</div>
<div class="cover-bottom">
<div class="cover-promise">
<b>Was du danach kannst</b>
OOP-Patterns idiomatisch einsetzen · funktional mit Closures und Generators arbeiten · Attribute für saubere Metadaten nutzen · Datenbanken mit PDO sicher anbinden · APIs konsumieren · mit PHPStan und PHPUnit produktionsreifen Code schreiben.
</div>
<div class="cover-tag">PHP 8.4</div>
</div>
</section>
<!-- ===== TOC ===== -->
<section class="toc">
<h2>Inhalt</h2>
<div class="toc-section">Teil 1 · OOP-Patterns</div>
<div class="toc-item">
<div class="toc-num">1</div>
<div class="toc-text">
<h3>Interfaces und abstrakte Klassen</h3>
<p>Kontrakte definieren, Implementierungen trennen</p>
</div>
<div class="toc-time">15 Min</div>
</div>
<div class="toc-item">
<div class="toc-num">2</div>
<div class="toc-text">
<h3>Traits</h3>
<p>Code-Reuse jenseits klassischer Vererbung</p>
</div>
<div class="toc-time">15 Min</div>
</div>
<div class="toc-item">
<div class="toc-num">3</div>
<div class="toc-text">
<h3>Enums richtig nutzen</h3>
<p>Backed Enums, Methods, Pattern-Matching</p>
</div>
<div class="toc-time">15 Min</div>
</div>
<div class="toc-item">
<div class="toc-num">4</div>
<div class="toc-text">
<h3>Attribute (PHP 8)</h3>
<p>Metadaten typensicher statt Annotations</p>
</div>
<div class="toc-time">15 Min</div>
</div>
<div class="toc-section">Teil 2 · Funktional & Generators</div>
<div class="toc-item">
<div class="toc-num">5</div>
<div class="toc-text">
<h3>Closures und Arrow Functions</h3>
<p>First-Class Callables, use-Capture</p>
</div>
<div class="toc-time">15 Min</div>
</div>
<div class="toc-item">
<div class="toc-num">6</div>
<div class="toc-text">
<h3>Higher-Order Functions</h3>
<p>array_map, array_filter, array_reduce in der Praxis</p>
</div>
<div class="toc-time">15 Min</div>
</div>
<div class="toc-item">
<div class="toc-num">7</div>
<div class="toc-text">
<h3>Iterators und Generators</h3>
<p>Lazy Evaluation mit yield</p>
</div>
<div class="toc-time">15 Min</div>
</div>
<div class="toc-item">
<div class="toc-num">8</div>
<div class="toc-text">
<h3>Generics via PHPDoc</h3>
<p>Templates ohne native Generics</p>
</div>
<div class="toc-time">15 Min</div>
</div>
<div class="toc-section">Teil 3 · Production-Tools</div>
<div class="toc-item">
<div class="toc-num">9</div>
<div class="toc-text">
<h3>PDO und sichere Datenbanken</h3>
<p>Prepared Statements, Transaktionen</p>
</div>
<div class="toc-time">15 Min</div>
</div>
<div class="toc-item">
<div class="toc-num">10</div>
<div class="toc-text">
<h3>HTTP-Requests mit Guzzle</h3>
<p>APIs konsumieren, async Requests</p>
</div>
<div class="toc-time">15 Min</div>
</div>
<div class="toc-item">
<div class="toc-num">11</div>
<div class="toc-text">
<h3>PHPStan und Static Analysis</h3>
<p>Bugs vor der Ausführung finden</p>
</div>
<div class="toc-time">15 Min</div>
</div>
<div class="toc-item">
<div class="toc-num">12</div>
<div class="toc-text">
<h3>Testen mit PHPUnit</h3>
<p>Unit-Tests, Mocks, Data Providers</p>
</div>
<div class="toc-time">15 Min</div>
</div>
</section>
<!-- ===== KAPITEL 1 ===== -->
<section class="chapter">
<div class="chapter-head">
<div class="chapter-num">01</div>
<div class="chapter-title">
<h1>Interfaces und abstrakte Klassen</h1>
<div class="subtitle">Kontrakte definieren, Implementierungen trennen</div>
</div>
</div>
<div class="gap">
<b>Frage zum Einstieg</b>
Du hast drei verschiedene Logger – Datei, Datenbank, Sentry. Sie sollen austauschbar sein, ohne dass der aufrufende Code etwas merkt. Wie zwingst du sie zu einer gemeinsamen API, ohne sie zu Geschwistern in einer Klassen-Hierarchie zu machen? Interfaces sind die Antwort.
</div>
<h2>Interface als reiner Kontrakt</h2>
<p>Ein <b>Interface</b> definiert nur, <i>was</i> Methoden tun, nicht <i>wie</i>. Klassen, die das Interface implementieren, müssen alle Methoden bereitstellen:</p>
<pre><span class="k">interface</span> <span class="t">LoggerInterface</span> {
<span class="k">public function</span> <span class="f">info</span>(<span class="t">string</span> <span class="v">$message</span>): <span class="t">void</span>;
<span class="k">public function</span> <span class="f">error</span>(<span class="t">string</span> <span class="v">$message</span>, <span class="t">array</span> <span class="v">$context</span> = []): <span class="t">void</span>;
}
<span class="k">class</span> <span class="t">FileLogger</span> <span class="k">implements</span> <span class="t">LoggerInterface</span> {
<span class="k">public function</span> <span class="f">__construct</span>(<span class="k">private readonly</span> <span class="t">string</span> <span class="v">$path</span>) {}
<span class="k">public function</span> <span class="f">info</span>(<span class="t">string</span> <span class="v">$message</span>): <span class="t">void</span> {
<span class="f">file_put_contents</span>(<span class="v">$this</span>->path, <span class="s">"[INFO] </span><span class="v">$message</span><span class="s">\n"</span>, FILE_APPEND);
}
<span class="k">public function</span> <span class="f">error</span>(<span class="t">string</span> <span class="v">$message</span>, <span class="t">array</span> <span class="v">$context</span> = []): <span class="t">void</span> {
<span class="f">file_put_contents</span>(<span class="v">$this</span>->path, <span class="s">"[ERROR] </span><span class="v">$message</span><span class="s">\n"</span>, FILE_APPEND);
}
}</pre>
<p>Der Gewinn: jede Funktion kann den Typ <code class="inline">LoggerInterface</code> erwarten und akzeptiert dadurch <i>jede</i> Implementierung – ohne deren Klasse zu kennen.</p>
<h2>Dependency Injection in Aktion</h2>
<pre><span class="k">class</span> <span class="t">OrderService</span> {
<span class="k">public function</span> <span class="f">__construct</span>(
<span class="k">private readonly</span> <span class="t">LoggerInterface</span> <span class="v">$logger</span>
) {}
<span class="k">public function</span> <span class="f">place</span>(<span class="t">Order</span> <span class="v">$order</span>): <span class="t">void</span> {
<span class="v">$this</span>->logger-><span class="f">info</span>(<span class="s">"Order </span><span class="v">$order</span><span class="s">->id placed"</span>);
<span class="c">// ...</span>
}
}
<span class="c">// In Tests: Fake-Logger reinreichen</span>
<span class="c">// In Production: FileLogger oder SentryLogger</span>
<span class="v">$service</span> = <span class="k">new</span> <span class="t">OrderService</span>(<span class="k">new</span> <span class="t">FileLogger</span>(<span class="s">'/var/log/app.log'</span>));</pre>
<h2>Abstrakte Klassen</h2>
<p>Wenn du <b>gemeinsamen Code</b> teilen willst, aber bestimmte Methoden offen lassen, nimmst du eine abstrakte Klasse. Sie kann Properties, fertige Methoden und abstrakte Methoden mischen:</p>
<pre><span class="k">abstract class</span> <span class="t">Notification</span> {
<span class="k">public function</span> <span class="f">__construct</span>(<span class="k">protected readonly</span> <span class="t">string</span> <span class="v">$recipient</span>) {}
<span class="c">// Fertige Methode</span>
<span class="k">public function</span> <span class="f">send</span>(<span class="t">string</span> <span class="v">$message</span>): <span class="t">void</span> {
<span class="v">$formatted</span> = <span class="v">$this</span>-><span class="f">format</span>(<span class="v">$message</span>);
<span class="v">$this</span>-><span class="f">deliver</span>(<span class="v">$formatted</span>);
}
<span class="c">// Muss von Subklassen implementiert werden</span>
<span class="k">abstract protected function</span> <span class="f">format</span>(<span class="t">string</span> <span class="v">$msg</span>): <span class="t">string</span>;
<span class="k">abstract protected function</span> <span class="f">deliver</span>(<span class="t">string</span> <span class="v">$msg</span>): <span class="t">void</span>;
}</pre>
<h2>Interface oder abstrakte Klasse?</h2>
<table>
<tr><th>Use Case</th><th>Wähle</th></tr>
<tr><td>Reiner Kontrakt</td><td>Interface</td></tr>
<tr><td>Mehrere "Verträge" erfüllen</td><td>Interface (mehrfach implementierbar)</td></tr>
<tr><td>Gemeinsame Code-Basis teilen</td><td>Abstrakte Klasse</td></tr>
<tr><td>Template Method Pattern</td><td>Abstrakte Klasse</td></tr>
</table>
<div class="callout tip">
<div class="callout-icon">✓</div>
<div class="callout-body">
<b>PSR-Interfaces nutzen</b>
Die PHP-FIG hat Standard-Interfaces für Logger, Container, HTTP und mehr definiert: <code class="inline">Psr\Log\LoggerInterface</code>, <code class="inline">Psr\Container\ContainerInterface</code>. Wenn du deine Klassen daran ausrichtest, passen sie zu jedem Framework.
</div>
</div>
<div class="recall">
<b>Recall</b>
<ol>
<li>Was ist der Hauptunterschied zwischen Interface und abstrakter Klasse?</li>
<li>Wie viele Interfaces darf eine Klasse implementieren?</li>
<li>Was ist Dependency Injection in einem Satz?</li>
</ol>
</div>
</section>
<!-- ===== KAPITEL 2 ===== -->
<section class="chapter">
<div class="chapter-head">
<div class="chapter-num">02</div>
<div class="chapter-title">
<h1>Traits</h1>
<div class="subtitle">Code-Reuse jenseits klassischer Vererbung</div>
</div>
</div>
<div class="gap">
<b>Frage zum Einstieg</b>
PHP unterstützt nur Einfach-Vererbung – eine Klasse hat genau eine Eltern-Klasse. Aber was, wenn zwei nicht verwandte Klassen denselben Code brauchen? Logging-Methoden in <code>User</code> und <code>Order</code> kopieren? Traits lösen das eleganter.
</div>
<h2>Trait: wiederverwendbarer Code-Block</h2>
<p>Ein <b>Trait</b> ist wie eine Klasse, aber du erstellst keine Instanz davon. Stattdessen wirst du in andere Klassen "einkopiert". Mehrere Traits pro Klasse sind erlaubt:</p>
<pre><span class="k">trait</span> <span class="t">Timestampable</span> {
<span class="k">private</span> ?\DateTimeImmutable <span class="v">$createdAt</span> = <span class="k">null</span>;
<span class="k">private</span> ?\DateTimeImmutable <span class="v">$updatedAt</span> = <span class="k">null</span>;
<span class="k">public function</span> <span class="f">touch</span>(): <span class="t">void</span> {
<span class="v">$this</span>->updatedAt = <span class="k">new</span> \DateTimeImmutable();
<span class="v">$this</span>->createdAt ??= <span class="v">$this</span>->updatedAt;
}
<span class="k">public function</span> <span class="f">getCreatedAt</span>(): ?\DateTimeImmutable {
<span class="k">return</span> <span class="v">$this</span>->createdAt;
}
}
<span class="k">class</span> <span class="t">Article</span> {
<span class="k">use</span> <span class="t">Timestampable</span>;
<span class="k">public function</span> <span class="f">__construct</span>(<span class="k">public</span> <span class="t">string</span> <span class="v">$title</span>) {
<span class="v">$this</span>-><span class="f">touch</span>();
}
}
<span class="k">class</span> <span class="t">Comment</span> {
<span class="k">use</span> <span class="t">Timestampable</span>; <span class="c">// gleiche Methoden, ohne Doppelung</span>
}</pre>
<h2>Mehrere Traits kombinieren</h2>
<pre><span class="k">trait</span> <span class="t">SoftDeletable</span> {
<span class="k">private</span> ?\DateTimeImmutable <span class="v">$deletedAt</span> = <span class="k">null</span>;
<span class="k">public function</span> <span class="f">delete</span>(): <span class="t">void</span> {
<span class="v">$this</span>->deletedAt = <span class="k">new</span> \DateTimeImmutable();
}
<span class="k">public function</span> <span class="f">isDeleted</span>(): <span class="t">bool</span> {
<span class="k">return</span> <span class="v">$this</span>->deletedAt !== <span class="k">null</span>;
}
}
<span class="k">class</span> <span class="t">Article</span> {
<span class="k">use</span> <span class="t">Timestampable</span>, <span class="t">SoftDeletable</span>; <span class="c">// beide Sets von Methoden</span>
}</pre>
<h2>Konflikt-Auflösung</h2>
<p>Wenn zwei Traits Methoden mit gleichem Namen haben, gibt es einen Konflikt. PHP zwingt dich, explizit zu wählen:</p>
<pre><span class="k">trait</span> <span class="t">A</span> { <span class="k">public function</span> <span class="f">hello</span>(): <span class="t">string</span> { <span class="k">return</span> <span class="s">'A'</span>; } }
<span class="k">trait</span> <span class="t">B</span> { <span class="k">public function</span> <span class="f">hello</span>(): <span class="t">string</span> { <span class="k">return</span> <span class="s">'B'</span>; } }
<span class="k">class</span> <span class="t">X</span> {
<span class="k">use</span> <span class="t">A</span>, <span class="t">B</span> {
A::hello <span class="k">insteadof</span> B; <span class="c">// nimm A's hello, ignoriere B's</span>
B::hello <span class="k">as</span> helloFromB; <span class="c">// B's hello als 'helloFromB' verfügbar</span>
}
}</pre>
<div class="callout warn">
<div class="callout-icon">!</div>
<div class="callout-body">
<b>Traits sind kein Multi-Inheritance-Ersatz</b>
Traits machen <b>Code-Reuse</b>, keinen <b>Typ-Polymorphismus</b>. Eine Klasse mit Trait ist nicht "vom Typ Trait" – nutze dafür Interfaces. Faustregel: Trait für Implementierung, Interface für Vertrag.
</div>
</div>
<h2>Wann Trait, wann Komposition?</h2>
<p>Traits sind verlockend, aber sie binden Code <i>statisch</i> in Klassen ein. Eine moderne Alternative ist <b>Komposition</b>: ein Service-Objekt als Property, das die Logik kapselt. Faustregel:</p>
<ul>
<li>Trait, wenn die Logik <i>Teil des Objekts ist</i> (Timestamps, Soft-Delete)</li>
<li>Komposition, wenn die Logik <i>austauschbar</i> sein soll (verschiedene Logger, Strategien)</li>
</ul>
<div class="recall">
<b>Recall</b>
<ol>
<li>Was ist der Unterschied zwischen Trait und abstrakter Klasse?</li>
<li>Wie löst du Methoden-Konflikte zwischen zwei Traits?</li>
<li>Wann nutzt du Komposition statt Trait?</li>
</ol>
</div>
</section>
<!-- ===== KAPITEL 3 ===== -->
<section class="chapter">
<div class="chapter-head">
<div class="chapter-num">03</div>
<div class="chapter-title">
<h1>Enums richtig nutzen</h1>
<div class="subtitle">Backed Enums, Methods, Pattern-Matching</div>
</div>
</div>
<div class="gap">
<b>Frage zum Einstieg</b>
Früher waren Status-Werte oft Strings ("draft", "published") oder Klassen-Konstanten – fehleranfällig und ohne Typ-Sicherheit. PHP 8.1 hat echte <b>Enums</b> gebracht. Wie nutzt du sie idiomatisch?
</div>
<h2>Einfache Enums</h2>
<pre><span class="k">enum</span> <span class="t">Status</span> {
<span class="k">case</span> <span class="t">Draft</span>;
<span class="k">case</span> <span class="t">Published</span>;
<span class="k">case</span> <span class="t">Archived</span>;
}
<span class="v">$status</span> = <span class="t">Status</span>::<span class="t">Draft</span>;
<span class="k">if</span> (<span class="v">$status</span> === <span class="t">Status</span>::<span class="t">Draft</span>) {
<span class="k">echo</span> <span class="s">'noch nicht veröffentlicht'</span>;
}</pre>
<p>Die Cases sind Singletons – jede Verwendung von <code class="inline">Status::Draft</code> ist dasselbe Objekt. Vergleich mit <code class="inline">===</code> funktioniert wie erwartet.</p>
<h2>Backed Enums (mit Wert)</h2>
<p>Für Serialisierung – etwa in Datenbank oder JSON – brauchst du <b>Backed Enums</b> mit konkretem String oder Int als Backing-Value:</p>
<pre><span class="k">enum</span> <span class="t">Status</span>: <span class="t">string</span> {
<span class="k">case</span> <span class="t">Draft</span> = <span class="s">'draft'</span>;
<span class="k">case</span> <span class="t">Published</span> = <span class="s">'published'</span>;
<span class="k">case</span> <span class="t">Archived</span> = <span class="s">'archived'</span>;
}
<span class="c">// Wert auslesen</span>
<span class="t">Status</span>::<span class="t">Draft</span>->value; <span class="c">// 'draft'</span>
<span class="c">// Aus Wert zurückbauen</span>
<span class="t">Status</span>::<span class="f">from</span>(<span class="s">'draft'</span>); <span class="c">// Status::Draft</span>
<span class="t">Status</span>::<span class="f">tryFrom</span>(<span class="s">'unknown'</span>); <span class="c">// null (statt Exception)</span></pre>
<h2>Methoden in Enums</h2>
<p>Enums dürfen Methoden haben. Damit packst du Verhalten direkt zum Wert, statt es in Helper-Funktionen zu verstecken:</p>
<pre><span class="k">enum</span> <span class="t">Status</span>: <span class="t">string</span> {
<span class="k">case</span> <span class="t">Draft</span> = <span class="s">'draft'</span>;
<span class="k">case</span> <span class="t">Published</span> = <span class="s">'published'</span>;
<span class="k">case</span> <span class="t">Archived</span> = <span class="s">'archived'</span>;
<span class="k">public function</span> <span class="f">label</span>(): <span class="t">string</span> {
<span class="k">return match</span>(<span class="v">$this</span>) {
<span class="t">self</span>::<span class="t">Draft</span> => <span class="s">'Entwurf'</span>,
<span class="t">self</span>::<span class="t">Published</span> => <span class="s">'Veröffentlicht'</span>,
<span class="t">self</span>::<span class="t">Archived</span> => <span class="s">'Archiv'</span>,
};
}
<span class="k">public function</span> <span class="f">isPublic</span>(): <span class="t">bool</span> {
<span class="k">return</span> <span class="v">$this</span> === <span class="t">self</span>::<span class="t">Published</span>;
}
}
<span class="t">Status</span>::<span class="t">Draft</span>-><span class="f">label</span>(); <span class="c">// 'Entwurf'</span>
<span class="t">Status</span>::<span class="t">Draft</span>-><span class="f">isPublic</span>(); <span class="c">// false</span></pre>
<h2>Alle Cases iterieren</h2>
<pre><span class="k">foreach</span> (<span class="t">Status</span>::<span class="f">cases</span>() <span class="k">as</span> <span class="v">$status</span>) {
<span class="k">echo</span> <span class="v">$status</span>-><span class="f">label</span>() . <span class="s">"\n"</span>;
}
<span class="c">// Entwurf</span>
<span class="c">// Veröffentlicht</span>
<span class="c">// Archiv</span></pre>
<p>Praktisch für Dropdowns, Validierung oder Migration zwischen Status.</p>
<div class="callout tip">
<div class="callout-icon">✓</div>
<div class="callout-body">
<b>Statt String-Constants immer Enum</b>
Wenn du heute <code class="inline">const STATUS_DRAFT = 'draft'</code> schreibst, ist es fast immer besser, ein Enum zu nehmen. Typ-Sicherheit, Methoden direkt am Wert, IDE-Vervollständigung, exhaustive match-Checks.
</div>
</div>
<div class="recall">
<b>Recall</b>
<ol>
<li>Was ist der Unterschied zwischen einfachem Enum und Backed Enum?</li>
<li>Wie konvertierst du einen String sicher zu einem Enum-Case?</li>
<li>Was gibt <code style="color:#777BB4">Status::cases()</code> zurück?</li>
</ol>
</div>
</section>
<!-- ===== KAPITEL 4 ===== -->
<section class="chapter">
<div class="chapter-head">
<div class="chapter-num">04</div>
<div class="chapter-title">
<h1>Attribute (PHP 8)</h1>
<div class="subtitle">Metadaten typensicher statt Annotations</div>
</div>
</div>
<div class="gap">
<b>Frage zum Einstieg</b>
Bevor PHP 8 nutzten Frameworks wie Symfony und Doctrine <b>Doc-Block-Annotations</b> für Metadaten: <code>/** @Route("/users") */</code>. Kommentare, die geparst werden – brüchig, ohne Typ-Check. PHP 8 brachte native <b>Attribute</b>. Wie schreibst und nutzt du sie?
</div>
<h2>Attribut definieren</h2>
<p>Ein Attribut ist eine Klasse, die mit <code class="inline">#[Attribute]</code> markiert ist. Sie kann Konstruktor-Parameter haben wie jede andere Klasse:</p>
<pre><span class="k">#[\Attribute(\Attribute::TARGET_METHOD)]</span>
<span class="k">class</span> <span class="t">Route</span> {
<span class="k">public function</span> <span class="f">__construct</span>(
<span class="k">public readonly</span> <span class="t">string</span> <span class="v">$path</span>,
<span class="k">public readonly</span> <span class="t">string</span> <span class="v">$method</span> = <span class="s">'GET'</span>,
) {}
}</pre>
<p>Das <code class="inline">TARGET_METHOD</code> sagt: dieses Attribut darf nur auf Methoden. Andere Optionen: <code class="inline">TARGET_CLASS</code>, <code class="inline">TARGET_PROPERTY</code>, <code class="inline">TARGET_PARAMETER</code>.</p>
<h2>Attribut anwenden</h2>
<pre><span class="k">class</span> <span class="t">UserController</span> {
<span class="k">#[Route(<span class="s">'/users'</span>)]</span>
<span class="k">public function</span> <span class="f">list</span>(): Response { <span class="c">/* ... */</span> }
<span class="k">#[Route(<span class="s">'/users/{id}'</span>, method: <span class="s">'GET'</span>)]</span>
<span class="k">public function</span> <span class="f">show</span>(<span class="t">int</span> <span class="v">$id</span>): Response { <span class="c">/* ... */</span> }
<span class="k">#[Route(<span class="s">'/users'</span>, method: <span class="s">'POST'</span>)]</span>
<span class="k">public function</span> <span class="f">create</span>(): Response { <span class="c">/* ... */</span> }
}</pre>
<h2>Attribute zur Laufzeit auslesen</h2>
<p>Mit der Reflection-API liest du Attribute aus Klassen, Methoden oder Properties aus – das ist die Grundlage für Frameworks wie Symfony, Doctrine oder ORM-Libraries:</p>
<pre><span class="v">$reflection</span> = <span class="k">new</span> \<span class="t">ReflectionClass</span>(<span class="t">UserController</span>::<span class="k">class</span>);
<span class="k">foreach</span> (<span class="v">$reflection</span>-><span class="f">getMethods</span>() <span class="k">as</span> <span class="v">$method</span>) {
<span class="k">foreach</span> (<span class="v">$method</span>-><span class="f">getAttributes</span>(<span class="t">Route</span>::<span class="k">class</span>) <span class="k">as</span> <span class="v">$attr</span>) {
<span class="v">$route</span> = <span class="v">$attr</span>-><span class="f">newInstance</span>(); <span class="c">// Route-Instanz</span>
<span class="k">echo</span> <span class="s">"</span><span class="v">$route</span><span class="s">->method </span><span class="v">$route</span><span class="s">->path → "</span> . <span class="v">$method</span>->name . <span class="s">"\n"</span>;
}
}
<span class="c">// Output:</span>
<span class="c">// GET /users → list</span>
<span class="c">// GET /users/{id} → show</span>
<span class="c">// POST /users → create</span></pre>
<h2>Typische Anwendungsfälle</h2>
<table>
<tr><th>Framework</th><th>Beispiel-Attribute</th></tr>
<tr><td>Symfony</td><td><code>#[Route]</code>, <code>#[AsCommand]</code></td></tr>
<tr><td>Doctrine</td><td><code>#[ORM\Entity]</code>, <code>#[ORM\Column]</code></td></tr>
<tr><td>PHPUnit</td><td><code>#[DataProvider]</code>, <code>#[Test]</code></td></tr>
<tr><td>Validator</td><td><code>#[Assert\NotBlank]</code></td></tr>
</table>
<div class="callout note">
<div class="callout-icon">i</div>
<div class="callout-body">
<b>Attribute statt Doc-Block-Annotations</b>
Alter Code mit <code class="inline">/** @Route("/users") */</code> funktioniert noch, aber das Doctrine/Symfony-Ökosystem migriert auf native Attribute. Bei Neuentwicklungen: immer Attribute, nie Annotations.
</div>
</div>
<div class="recall">
<b>Recall</b>
<ol>
<li>Was ist der Vorteil von Attributen gegenüber Doc-Block-Annotations?</li>
<li>Wie liest du Attribute zur Laufzeit aus?</li>
<li>Welche Frameworks nutzen Attribute intensiv?</li>
</ol>
</div>
</section>
<!-- ===== KAPITEL 5 ===== -->
<section class="chapter">
<div class="chapter-head">
<div class="chapter-num">05</div>
<div class="chapter-title">
<h1>Closures und Arrow Functions</h1>
<div class="subtitle">First-Class Callables, use-Capture</div>
</div>
</div>
<div class="gap">
<b>Frage zum Einstieg</b>
Funktionen als Werte – das ist seit Jahren in JavaScript Standard. Auch PHP kann das, mit Closures, Arrow Functions und seit PHP 8.1 First-Class Callable Syntax. Wann nutzt du was?
</div>
<h2>Closures: anonyme Funktionen</h2>
<p>Eine <b>Closure</b> ist eine Funktion ohne Namen, die in einer Variable lebt. Du übergibst sie als Argument oder gibst sie zurück:</p>
<pre><span class="v">$double</span> = <span class="k">function</span>(<span class="t">int</span> <span class="v">$x</span>): <span class="t">int</span> {
<span class="k">return</span> <span class="v">$x</span> * <span class="s">2</span>;
};
<span class="k">echo</span> <span class="v">$double</span>(<span class="s">5</span>); <span class="c">// 10</span>
<span class="c">// Als Argument an array_map</span>
<span class="v">$numbers</span> = [<span class="s">1</span>, <span class="s">2</span>, <span class="s">3</span>];
<span class="v">$doubled</span> = <span class="f">array_map</span>(<span class="v">$double</span>, <span class="v">$numbers</span>); <span class="c">// [2, 4, 6]</span></pre>
<h2>Variablen aus dem Kontext: use</h2>
<p>Im Gegensatz zu normalen Funktionen haben Closures Zugriff auf den umgebenden Scope – aber nur, wenn du es explizit mit <code class="inline">use</code> erlaubst:</p>
<pre><span class="v">$prefix</span> = <span class="s">'Mr. '</span>;
<span class="v">$greet</span> = <span class="k">function</span>(<span class="t">string</span> <span class="v">$name</span>) <span class="k">use</span> (<span class="v">$prefix</span>): <span class="t">string</span> {
<span class="k">return</span> <span class="v">$prefix</span> . <span class="v">$name</span>;
};
<span class="k">echo</span> <span class="v">$greet</span>(<span class="s">'Marek'</span>); <span class="c">// 'Mr. Marek'</span>
<span class="c">// use kopiert standardmäßig: spätere Änderung wirkt nicht</span>
<span class="v">$prefix</span> = <span class="s">'Dr. '</span>;
<span class="k">echo</span> <span class="v">$greet</span>(<span class="s">'Marek'</span>); <span class="c">// immer noch 'Mr. Marek'</span>
<span class="c">// use mit Referenz: spätere Änderung wirkt</span>
<span class="v">$counter</span> = <span class="s">0</span>;
<span class="v">$increment</span> = <span class="k">function</span>() <span class="k">use</span> (&<span class="v">$counter</span>): <span class="t">void</span> {
<span class="v">$counter</span>++;
};
<span class="v">$increment</span>();
<span class="v">$increment</span>();
<span class="k">echo</span> <span class="v">$counter</span>; <span class="c">// 2</span></pre>
<h2>Arrow Functions: kompakt</h2>
<p>Für einzeilige Closures gibt es seit PHP 7.4 die kürzere <b>Arrow Function Syntax</b>. Sie capturet Variablen automatisch (kein <code class="inline">use</code> nötig):</p>
<pre><span class="v">$multiplier</span> = <span class="s">3</span>;
<span class="v">$multiply</span> = <span class="k">fn</span>(<span class="t">int</span> <span class="v">$x</span>): <span class="t">int</span> => <span class="v">$x</span> * <span class="v">$multiplier</span>;
<span class="k">echo</span> <span class="v">$multiply</span>(<span class="s">5</span>); <span class="c">// 15</span>
<span class="c">// Praktisch in array_map / array_filter</span>
<span class="v">$names</span> = [<span class="s">'marek'</span>, <span class="s">'anna'</span>, <span class="s">'tom'</span>];
<span class="v">$upper</span> = <span class="f">array_map</span>(<span class="k">fn</span>(<span class="v">$n</span>) => <span class="f">strtoupper</span>(<span class="v">$n</span>), <span class="v">$names</span>);</pre>
<h2>First-Class Callable Syntax</h2>
<p>Seit PHP 8.1 kannst du jede Funktion oder Methode als Closure referenzieren – ohne sie aufzurufen:</p>
<pre><span class="c">// Globale Funktion</span>
<span class="v">$upper</span> = <span class="f">strtoupper</span>(...);
<span class="v">$upper</span>(<span class="s">'hallo'</span>); <span class="c">// 'HALLO'</span>
<span class="c">// Methode</span>
<span class="v">$user</span> = <span class="k">new</span> <span class="t">User</span>(<span class="s">'Marek'</span>);
<span class="v">$greet</span> = <span class="v">$user</span>->greet(...);
<span class="v">$greet</span>(); <span class="c">// 'Hallo, Marek'</span>
<span class="c">// Static Methode</span>
<span class="v">$square</span> = <span class="t">Math</span>::<span class="f">square</span>(...);
<span class="v">$square</span>(<span class="s">5</span>); <span class="c">// 25</span>
<span class="c">// Direkt an array_map übergeben</span>
<span class="v">$upper</span> = <span class="f">array_map</span>(<span class="f">strtoupper</span>(...), <span class="v">$names</span>);</pre>
<div class="callout tip">
<div class="callout-icon">✓</div>
<div class="callout-body">
<b>Arrow für einfach, Closure für komplex</b>
Faustregel: Arrow Function (<code class="inline">fn</code>) wenn der Body ein einzelner Ausdruck ist. Volle Closure (<code class="inline">function</code>) wenn du mehrere Statements brauchst oder explizites Variablen-Capturing willst.
</div>
</div>
<div class="recall">
<b>Recall</b>
<ol>
<li>Was ist der Unterschied zwischen <code style="color:#777BB4">use ($x)</code> und <code style="color:#777BB4">use (&$x)</code>?</li>
<li>Was capturen Arrow Functions automatisch?</li>
<li>Wofür nutzt du First-Class Callable Syntax <code style="color:#777BB4">strtoupper(...)</code>?</li>
</ol>
</div>
</section>
<!-- ===== KAPITEL 6 ===== -->
<section class="chapter">
<div class="chapter-head">
<div class="chapter-num">06</div>
<div class="chapter-title">
<h1>Higher-Order Functions</h1>
<div class="subtitle">array_map, array_filter, array_reduce in der Praxis</div>
</div>
</div>
<div class="gap">
<b>Frage zum Einstieg</b>
Du willst aus einer Liste von Bestellungen die Beträge aller bezahlten zusammenrechnen. Klassisch mit <code>foreach</code>, Zwischenvariable, Bedingung – fünf Zeilen. Mit Higher-Order Functions wird daraus ein lesbarer Einzeiler. Wie?
</div>
<h2>array_map: transformieren</h2>
<p><code class="inline">array_map</code> wendet eine Funktion auf jedes Element an und gibt ein neues Array zurück:</p>
<pre><span class="v">$prices</span> = [<span class="s">10</span>, <span class="s">25</span>, <span class="s">99</span>];
<span class="v">$withTax</span> = <span class="f">array_map</span>(<span class="k">fn</span>(<span class="v">$p</span>) => <span class="v">$p</span> * <span class="s">1.19</span>, <span class="v">$prices</span>);
<span class="c">// [11.9, 29.75, 117.81]</span>
<span class="c">// Objekte transformieren</span>
<span class="v">$users</span> = [<span class="k">new</span> <span class="t">User</span>(<span class="s">'Marek'</span>), <span class="k">new</span> <span class="t">User</span>(<span class="s">'Anna'</span>)];
<span class="v">$names</span> = <span class="f">array_map</span>(<span class="k">fn</span>(<span class="t">User</span> <span class="v">$u</span>) => <span class="v">$u</span>->name, <span class="v">$users</span>);
<span class="c">// ['Marek', 'Anna']</span></pre>
<h2>array_filter: filtern</h2>
<p><code class="inline">array_filter</code> behält nur Elemente, für die die Funktion <code class="inline">true</code> zurückgibt:</p>
<pre><span class="v">$numbers</span> = [<span class="s">1</span>, <span class="s">2</span>, <span class="s">3</span>, <span class="s">4</span>, <span class="s">5</span>, <span class="s">6</span>];
<span class="v">$even</span> = <span class="f">array_filter</span>(<span class="v">$numbers</span>, <span class="k">fn</span>(<span class="v">$n</span>) => <span class="v">$n</span> % <span class="s">2</span> === <span class="s">0</span>);
<span class="c">// [2, 4, 6]</span>
<span class="c">// Achtung: Keys werden beibehalten!</span>
<span class="c">// $even ist [1 => 2, 3 => 4, 5 => 6]</span>
<span class="c">// Re-indexen mit array_values</span>
<span class="v">$even</span> = <span class="f">array_values</span>(<span class="f">array_filter</span>(<span class="v">$numbers</span>, <span class="k">fn</span>(<span class="v">$n</span>) => <span class="v">$n</span> % <span class="s">2</span> === <span class="s">0</span>));
<span class="c">// [2, 4, 6] mit Keys 0, 1, 2</span></pre>
<h2>array_reduce: zusammenfassen</h2>
<p><code class="inline">array_reduce</code> reduziert ein Array zu einem einzelnen Wert. Der Akkumulator startet mit einem Initialwert und wird mit jedem Element aktualisiert:</p>
<pre><span class="v">$prices</span> = [<span class="s">10</span>, <span class="s">25</span>, <span class="s">99</span>];
<span class="v">$total</span> = <span class="f">array_reduce</span>(
<span class="v">$prices</span>,
<span class="k">fn</span>(<span class="t">float</span> <span class="v">$sum</span>, <span class="t">int</span> <span class="v">$price</span>) => <span class="v">$sum</span> + <span class="v">$price</span>,
<span class="s">0.0</span> <span class="c">// Initialwert</span>
);
<span class="c">// 134.0</span></pre>
<h2>Verkettung in der Praxis</h2>
<p>Das volle Potenzial entfaltet sich, wenn du mehrere Funktionen kombinierst – z.B. Beträge der bezahlten Bestellungen aufsummieren:</p>
<pre><span class="v">$paidTotal</span> = <span class="f">array_reduce</span>(
<span class="f">array_filter</span>(<span class="v">$orders</span>, <span class="k">fn</span>(<span class="v">$o</span>) => <span class="v">$o</span>->status === <span class="t">Status</span>::<span class="t">Paid</span>),
<span class="k">fn</span>(<span class="v">$sum</span>, <span class="v">$o</span>) => <span class="v">$sum</span> + <span class="v">$o</span>->amount,
<span class="s">0.0</span>
);
<span class="c">// Mit Zwischen-Variablen leichter zu lesen</span>
<span class="v">$paidOrders</span> = <span class="f">array_filter</span>(<span class="v">$orders</span>, <span class="k">fn</span>(<span class="v">$o</span>) => <span class="v">$o</span>-><span class="f">isPaid</span>());
<span class="v">$amounts</span> = <span class="f">array_map</span>(<span class="k">fn</span>(<span class="v">$o</span>) => <span class="v">$o</span>->amount, <span class="v">$paidOrders</span>);
<span class="v">$total</span> = <span class="f">array_sum</span>(<span class="v">$amounts</span>);</pre>
<p>Letzteres ist oft lesbarer als verschachtelte Aufrufe – nicht immer ist die kompakteste Version die beste.</p>
<div class="callout note">
<div class="callout-icon">i</div>
<div class="callout-body">
<b>illuminate/collections für komplexere Fälle</b>
Wenn du oft mit verketteten Operationen arbeitest, lohnt sich Laravels Collection-Library (auch standalone nutzbar): <code class="inline">collect($users)->filter(...)->map(...)->sum()</code>. Lesbarer als verschachtelte PHP-Standardfunktionen.
</div>
</div>
<div class="recall">
<b>Recall</b>
<ol>
<li>Was ist die Reihenfolge der Argumente bei <code style="color:#777BB4">array_map</code> vs. <code style="color:#777BB4">array_filter</code>?</li>
<li>Welches Detail bei <code style="color:#777BB4">array_filter</code> überrascht oft?</li>
<li>Wann ist eine verschachtelte Verkettung weniger lesbar als Zwischenvariablen?</li>
</ol>
</div>
</section>
<!-- ===== KAPITEL 7 ===== -->
<section class="chapter">
<div class="chapter-head">
<div class="chapter-num">07</div>
<div class="chapter-title">
<h1>Iterators und Generators</h1>
<div class="subtitle">Lazy Evaluation mit yield</div>
</div>
</div>
<div class="gap">
<b>Frage zum Einstieg</b>
Du sollst die Zeilen einer 5-GB-Logdatei verarbeiten. Komplett ins Array laden? Speicher reicht nicht. Klassische File-Funktionen mit Schleife? Funktioniert, aber Code wird unhandlich, wenn er weiterverarbeitet werden soll. <b>Generators</b> lösen das elegant.
</div>
<h2>yield: Werte häppchenweise produzieren</h2>
<p>Ein <b>Generator</b> ist eine Funktion, die Werte mit <code class="inline">yield</code> ausgibt statt mit <code class="inline">return</code>. Sie pausiert zwischen den Werten – Speicher bleibt frei:</p>
<pre><span class="k">function</span> <span class="f">readLines</span>(<span class="t">string</span> <span class="v">$path</span>): \<span class="t">Generator</span> {
<span class="v">$handle</span> = <span class="f">fopen</span>(<span class="v">$path</span>, <span class="s">'r'</span>);
<span class="k">while</span> ((<span class="v">$line</span> = <span class="f">fgets</span>(<span class="v">$handle</span>)) !== <span class="k">false</span>) {
<span class="k">yield</span> <span class="f">rtrim</span>(<span class="v">$line</span>);
}
<span class="f">fclose</span>(<span class="v">$handle</span>);
}
<span class="c">// Aufruf liefert sofort einen Generator – Datei wird noch nicht gelesen</span>
<span class="v">$lines</span> = <span class="f">readLines</span>(<span class="s">'/var/log/huge.log'</span>);
<span class="c">// Erst die Iteration liest tatsächlich</span>
<span class="k">foreach</span> (<span class="v">$lines</span> <span class="k">as</span> <span class="v">$line</span>) {
<span class="k">if</span> (<span class="f">str_contains</span>(<span class="v">$line</span>, <span class="s">'ERROR'</span>)) {
<span class="k">echo</span> <span class="v">$line</span>;
}
}</pre>
<p>Der Speicher-Vorteil: zu jedem Zeitpunkt ist nur <i>eine</i> Zeile geladen, egal wie groß die Datei ist.</p>
<h2>yield with key</h2>
<pre><span class="k">function</span> <span class="f">readCsv</span>(<span class="t">string</span> <span class="v">$path</span>): \<span class="t">Generator</span> {
<span class="v">$handle</span> = <span class="f">fopen</span>(<span class="v">$path</span>, <span class="s">'r'</span>);
<span class="v">$headers</span> = <span class="f">fgetcsv</span>(<span class="v">$handle</span>);
<span class="v">$rowNumber</span> = <span class="s">0</span>;
<span class="k">while</span> ((<span class="v">$row</span> = <span class="f">fgetcsv</span>(<span class="v">$handle</span>)) !== <span class="k">false</span>) {
<span class="k">yield</span> <span class="v">$rowNumber</span>++ => <span class="f">array_combine</span>(<span class="v">$headers</span>, <span class="v">$row</span>);
}
<span class="f">fclose</span>(<span class="v">$handle</span>);
}
<span class="k">foreach</span> (<span class="f">readCsv</span>(<span class="s">'users.csv'</span>) <span class="k">as</span> <span class="v">$num</span> => <span class="v">$row</span>) {
<span class="k">echo</span> <span class="s">"Zeile </span><span class="v">$num</span><span class="s">: "</span> . <span class="v">$row</span>[<span class="s">'name'</span>] . <span class="s">"\n"</span>;
}</pre>
<h2>Unendliche Sequenzen</h2>
<p>Da Generators lazy sind, kannst du theoretisch unendliche Sequenzen erzeugen – solange du sie nicht komplett auswertest:</p>
<pre><span class="k">function</span> <span class="f">fibonacci</span>(): \<span class="t">Generator</span> {
<span class="v">$a</span> = <span class="s">0</span>; <span class="v">$b</span> = <span class="s">1</span>;
<span class="k">while</span> (<span class="k">true</span>) {
<span class="k">yield</span> <span class="v">$a</span>;
[<span class="v">$a</span>, <span class="v">$b</span>] = [<span class="v">$b</span>, <span class="v">$a</span> + <span class="v">$b</span>];
}
}
<span class="c">// Erste 10 Fibonacci-Zahlen</span>
<span class="v">$count</span> = <span class="s">0</span>;
<span class="k">foreach</span> (<span class="f">fibonacci</span>() <span class="k">as</span> <span class="v">$n</span>) {
<span class="k">echo</span> <span class="v">$n</span> . <span class="s">' '</span>;
<span class="k">if</span> (++<span class="v">$count</span> >= <span class="s">10</span>) <span class="k">break</span>;
}
<span class="c">// 0 1 1 2 3 5 8 13 21 34</span></pre>
<h2>yield from: Generators verschachteln</h2>
<p>Mit <code class="inline">yield from</code> delegierst du an einen anderen Generator – nützlich, um Logik zu kapseln:</p>
<pre><span class="k">function</span> <span class="f">range1to3</span>(): \<span class="t">Generator</span> {
<span class="k">yield</span> <span class="s">1</span>;
<span class="k">yield</span> <span class="s">2</span>;
<span class="k">yield</span> <span class="s">3</span>;
}
<span class="k">function</span> <span class="f">range1to6</span>(): \<span class="t">Generator</span> {
<span class="k">yield from</span> <span class="f">range1to3</span>(); <span class="c">// gibt 1, 2, 3</span>
<span class="k">yield</span> <span class="s">4</span>;
<span class="k">yield</span> <span class="s">5</span>;
<span class="k">yield</span> <span class="s">6</span>;
}</pre>
<div class="callout warn">
<div class="callout-icon">!</div>
<div class="callout-body">
<b>Generator nur einmal durchlaufen</b>
Generatoren sind <b>nicht rewindable</b>. Wenn du sie zweimal mit <code class="inline">foreach</code> durchläufst, ist der zweite Durchlauf leer. Brauchst du Mehrfach-Zugriff, konvertiere mit <code class="inline">iterator_to_array(<span class="v">$gen</span>)</code> zu einem Array.
</div>
</div>
<div class="recall">
<b>Recall</b>
<ol>
<li>Welcher Vorteil von Generators bei großen Datenmengen?</li>
<li>Was passiert, wenn du einen Generator zweimal mit <code style="color:#777BB4">foreach</code> durchläufst?</li>
<li>Wofür ist <code style="color:#777BB4">yield from</code> da?</li>
</ol>
</div>
</section>
<!-- ===== KAPITEL 8 ===== -->
<section class="chapter">
<div class="chapter-head">
<div class="chapter-num">08</div>
<div class="chapter-title">
<h1>Generics via PHPDoc</h1>
<div class="subtitle">Templates ohne native Generics</div>
</div>
</div>
<div class="gap">
<b>Frage zum Einstieg</b>
PHP hat keine nativen Generics wie TypeScript oder Java. Wenn du eine Collection-Klasse schreibst, weiß die IDE nicht, ob da User oder Order drin sind – jeder Zugriff wird zu <code>mixed</code>. Die Lösung: PHPDoc-Generics mit PHPStan oder Psalm.
</div>
<h2>Das Problem ohne Generics</h2>
<pre><span class="k">class</span> <span class="t">Collection</span> {
<span class="k">private array</span> <span class="v">$items</span> = [];
<span class="k">public function</span> <span class="f">add</span>(<span class="t">mixed</span> <span class="v">$item</span>): <span class="t">void</span> {
<span class="v">$this</span>->items[] = <span class="v">$item</span>;
}
<span class="k">public function</span> <span class="f">first</span>(): <span class="t">mixed</span> {
<span class="k">return</span> <span class="v">$this</span>->items[<span class="s">0</span>] ?? <span class="k">null</span>;
}
}
<span class="v">$users</span> = <span class="k">new</span> <span class="t">Collection</span>();
<span class="v">$users</span>-><span class="f">add</span>(<span class="k">new</span> <span class="t">User</span>(<span class="s">'Marek'</span>));
<span class="v">$user</span> = <span class="v">$users</span>-><span class="f">first</span>(); <span class="c">// IDE: mixed, keine Autocompletion</span>
<span class="v">$user</span>->name; <span class="c">// keine Hilfe von IDE oder PHPStan</span></pre>
<h2>Generics in PHPDoc</h2>
<p>Mit <b>PHPDoc-Annotationen</b> kannst du Templates definieren. PHPStan und Psalm verstehen sie und prüfen Typen statisch:</p>
<pre><span class="c">/**
* @template T
*/</span>
<span class="k">class</span> <span class="t">Collection</span> {
<span class="c">/** @var array<T> */</span>
<span class="k">private array</span> <span class="v">$items</span> = [];
<span class="c">/**
* @param T $item
*/</span>
<span class="k">public function</span> <span class="f">add</span>(<span class="t">mixed</span> <span class="v">$item</span>): <span class="t">void</span> {
<span class="v">$this</span>->items[] = <span class="v">$item</span>;
}
<span class="c">/**
* @return T|null
*/</span>
<span class="k">public function</span> <span class="f">first</span>(): <span class="t">mixed</span> {
<span class="k">return</span> <span class="v">$this</span>->items[<span class="s">0</span>] ?? <span class="k">null</span>;
}
}</pre>
<h2>Konkreten Typ angeben</h2>
<p>Bei der Verwendung gibst du den konkreten Typ als PHPDoc-Annotation an. PHPStan ersetzt dann <code class="inline">T</code> intern:</p>
<pre><span class="c">/** @var Collection<User> $users */</span>
<span class="v">$users</span> = <span class="k">new</span> <span class="t">Collection</span>();
<span class="v">$users</span>-><span class="f">add</span>(<span class="k">new</span> <span class="t">User</span>(<span class="s">'Marek'</span>));
<span class="v">$user</span> = <span class="v">$users</span>-><span class="f">first</span>(); <span class="c">// PHPStan weiß: User|null</span>
<span class="v">$user</span>->name; <span class="c">// OK, IDE und PHPStan kennen User</span>
<span class="v">$users</span>-><span class="f">add</span>(<span class="s">'string'</span>); <span class="c">// PHPStan-Fehler: erwartet User, nicht string</span></pre>
<h2>Constraints und Bounds</h2>
<pre><span class="c">/**
* @template T of \Throwable
*/</span>
<span class="k">class</span> <span class="t">ErrorCollection</span> {
<span class="c">/** @var array<T> */</span>
<span class="k">private array</span> <span class="v">$errors</span> = [];
<span class="c">/**
* @param T $error
*/</span>
<span class="k">public function</span> <span class="f">add</span>(\<span class="t">Throwable</span> <span class="v">$error</span>): <span class="t">void</span> {
<span class="v">$this</span>->errors[] = <span class="v">$error</span>;
}
}
<span class="c">// Erlaubt nur Throwables und Subklassen</span>
<span class="c">/** @var ErrorCollection<\RuntimeException> $errors */</span>
<span class="v">$errors</span> = <span class="k">new</span> <span class="t">ErrorCollection</span>();</pre>
<h2>array-shape für strukturierte Arrays</h2>
<p>PHP-Arrays sind oft "halb-Objekte" mit festen Keys. PHPStan kennt <b>Array-Shapes</b>:</p>
<pre><span class="c">/**
* @return array{name: string, age: int, email: string|null}
*/</span>
<span class="k">function</span> <span class="f">getUser</span>(<span class="t">int</span> <span class="v">$id</span>): <span class="t">array</span> {
<span class="k">return</span> [<span class="s">'name'</span> => <span class="s">'Marek'</span>, <span class="s">'age'</span> => <span class="s">34</span>, <span class="s">'email'</span> => <span class="k">null</span>];
}
<span class="v">$user</span> = <span class="f">getUser</span>(<span class="s">42</span>);
<span class="k">echo</span> <span class="v">$user</span>[<span class="s">'name'</span>]; <span class="c">// PHPStan weiß: string</span>
<span class="k">echo</span> <span class="v">$user</span>[<span class="s">'phone'</span>]; <span class="c">// PHPStan-Fehler: Key existiert nicht</span></pre>
<div class="callout note">
<div class="callout-icon">i</div>
<div class="callout-body">
<b>Native Generics kommen vielleicht</b>
Es gibt seit Jahren Diskussionen um native Generics in PHP. Ein RFC ist mehrfach gescheitert wegen Implementierungs-Komplexität (Runtime-Type-Erasure vs. Reified). Bis dahin sind PHPDoc-Generics mit PHPStan/Psalm der Standard.
</div>
</div>
<div class="recall">
<b>Recall</b>
<ol>
<li>Welche Tools verstehen PHPDoc-Generics?</li>
<li>Wie deklarierst du eine generische Klasse?</li>
<li>Wofür ist <code style="color:#777BB4">array{name: string, age: int}</code>?</li>
</ol>
</div>
</section>
<!-- ===== KAPITEL 9 ===== -->
<section class="chapter">
<div class="chapter-head">
<div class="chapter-num">09</div>
<div class="chapter-title">
<h1>PDO und sichere Datenbanken</h1>
<div class="subtitle">Prepared Statements, Transaktionen</div>
</div>
</div>
<div class="gap">
<b>Frage zum Einstieg</b>
SQL-Injection ist seit 20 Jahren die häufigste Web-Sicherheitslücke. Sie ist trivial zu verhindern – mit <b>Prepared Statements</b>. PDO ist PHPs eingebaute, datenbank-agnostische Schnittstelle dafür. Wie nutzt du sie richtig?
</div>
<h2>PDO-Verbindung aufbauen</h2>
<p>PDO unterstützt MySQL, PostgreSQL, SQLite und mehr über dieselbe API. Der DSN-String unterscheidet sich, der Rest ist gleich:</p>
<pre><span class="v">$dsn</span> = <span class="s">'mysql:host=localhost;dbname=myapp;charset=utf8mb4'</span>;
<span class="v">$pdo</span> = <span class="k">new</span> \<span class="t">PDO</span>(<span class="v">$dsn</span>, <span class="s">'username'</span>, <span class="s">'password'</span>, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => <span class="k">false</span>,
]);</pre>
<p>Die drei Options sind <b>Pflicht in jedem Projekt</b>: Exceptions bei Fehlern, assoziative Arrays beim Fetch, echte Prepared Statements (statt emulierter Client-Side).</p>
<h2>Prepared Statements (immer!)</h2>
<p><b>Niemals</b> User-Input direkt in SQL-Strings konkatenieren. Stattdessen: Placeholder mit <code class="inline">?</code> oder Named Parameters:</p>
<pre><span class="c">// Falsch: SQL-Injection-Lücke</span>
<span class="v">$id</span> = <span class="v">$_GET</span>[<span class="s">'id'</span>];
<span class="v">$result</span> = <span class="v">$pdo</span>-><span class="f">query</span>(<span class="s">"SELECT * FROM users WHERE id = </span><span class="v">$id</span><span class="s">"</span>);
<span class="c">// $_GET['id'] = "1; DROP TABLE users" → katastrophal</span>
<span class="c">// Richtig: Prepared Statement mit Positional Parameters</span>
<span class="v">$stmt</span> = <span class="v">$pdo</span>-><span class="f">prepare</span>(<span class="s">'SELECT * FROM users WHERE id = ?'</span>);
<span class="v">$stmt</span>-><span class="f">execute</span>([<span class="v">$id</span>]);
<span class="v">$user</span> = <span class="v">$stmt</span>-><span class="f">fetch</span>();
<span class="c">// Oder mit Named Parameters</span>
<span class="v">$stmt</span> = <span class="v">$pdo</span>-><span class="f">prepare</span>(<span class="s">'SELECT * FROM users WHERE id = :id AND active = :active'</span>);
<span class="v">$stmt</span>-><span class="f">execute</span>([<span class="s">':id'</span> => <span class="v">$id</span>, <span class="s">':active'</span> => <span class="k">true</span>]);</pre>
<h2>Ergebnisse abrufen</h2>
<pre><span class="c">// Einzelne Zeile</span>
<span class="v">$user</span> = <span class="v">$stmt</span>-><span class="f">fetch</span>(); <span class="c">// assoc array oder false</span>
<span class="c">// Alle Zeilen</span>
<span class="v">$users</span> = <span class="v">$stmt</span>-><span class="f">fetchAll</span>(); <span class="c">// Array von Zeilen</span>
<span class="c">// Iterativ für große Result-Sets</span>
<span class="k">while</span> (<span class="v">$row</span> = <span class="v">$stmt</span>-><span class="f">fetch</span>()) {
<span class="f">processRow</span>(<span class="v">$row</span>);
}
<span class="c">// Direkt in Objekte mappen (PDO::FETCH_CLASS)</span>
<span class="v">$stmt</span>-><span class="f">setFetchMode</span>(PDO::FETCH_CLASS, <span class="t">User</span>::<span class="k">class</span>);
<span class="v">$users</span> = <span class="v">$stmt</span>-><span class="f">fetchAll</span>();</pre>
<h2>Transaktionen</h2>
<p>Wenn mehrere Operationen <b>atomar</b> sein müssen (alle oder keine), nutzt du Transaktionen:</p>
<pre><span class="v">$pdo</span>-><span class="f">beginTransaction</span>();
<span class="k">try</span> {
<span class="v">$pdo</span>-><span class="f">prepare</span>(<span class="s">'UPDATE accounts SET balance = balance - ? WHERE id = ?'</span>)
-><span class="f">execute</span>([<span class="s">100</span>, <span class="v">$fromId</span>]);
<span class="v">$pdo</span>-><span class="f">prepare</span>(<span class="s">'UPDATE accounts SET balance = balance + ? WHERE id = ?'</span>)
-><span class="f">execute</span>([<span class="s">100</span>, <span class="v">$toId</span>]);
<span class="v">$pdo</span>-><span class="f">commit</span>();
} <span class="k">catch</span> (\<span class="t">PDOException</span> <span class="v">$e</span>) {
<span class="v">$pdo</span>-><span class="f">rollBack</span>();
<span class="k">throw</span> <span class="v">$e</span>;
}</pre>
<div class="callout warn">
<div class="callout-icon">!</div>
<div class="callout-body">
<b>Niemals mysql_* oder mysqli ohne Prepared Statements</b>
Die alten <code class="inline">mysql_*</code>-Funktionen sind seit PHP 7 entfernt. <code class="inline">mysqli</code> existiert noch, aber nur mit Prepared Statements nutzen. Standard heute: <b>PDO</b> für direkten DB-Zugriff, <b>Doctrine DBAL</b> für Query-Builder, <b>Doctrine ORM</b> für komplexe Domänen.
</div>
</div>
<div class="recall">
<b>Recall</b>
<ol>
<li>Welche drei Options gehören in jeden PDO-Konstruktor?</li>
<li>Warum sind Prepared Statements sicherer als String-Konkatenation?</li>
<li>Wann nutzt du eine Transaktion?</li>
</ol>
</div>
</section>
<!-- ===== KAPITEL 10 ===== -->
<section class="chapter">
<div class="chapter-head">
<div class="chapter-num">10</div>
<div class="chapter-title">
<h1>HTTP-Requests mit Guzzle</h1>
<div class="subtitle">APIs konsumieren, async Requests</div>
</div>
</div>
<div class="gap">
<b>Frage zum Einstieg</b>
Du musst eine externe API ansprechen – REST mit JSON, Authentifizierung, vielleicht Retry bei 503. Mit nativen Funktionen wie <code>file_get_contents</code> oder cURL geht das, aber wird schnell hässlich. <b>Guzzle</b> ist Standard-Library für HTTP in PHP.
</div>
<h2>Setup</h2>
<pre>composer require guzzlehttp/guzzle</pre>
<pre><span class="k">use</span> GuzzleHttp\Client;
<span class="v">$client</span> = <span class="k">new</span> Client([
<span class="s">'base_uri'</span> => <span class="s">'https://api.example.com'</span>,
<span class="s">'timeout'</span> => <span class="s">5</span>,
<span class="s">'headers'</span> => [
<span class="s">'Accept'</span> => <span class="s">'application/json'</span>,
<span class="s">'Authorization'</span> => <span class="s">'Bearer '</span> . <span class="v">$token</span>,
],
]);</pre>
<h2>GET und POST</h2>
<pre><span class="c">// GET</span>
<span class="v">$response</span> = <span class="v">$client</span>-><span class="f">get</span>(<span class="s">'/users/42'</span>);
<span class="v">$data</span> = <span class="f">json_decode</span>(<span class="v">$response</span>-><span class="f">getBody</span>()-><span class="f">getContents</span>(), <span class="k">true</span>);
<span class="c">// GET mit Query-Parametern</span>
<span class="v">$response</span> = <span class="v">$client</span>-><span class="f">get</span>(<span class="s">'/users'</span>, [
<span class="s">'query'</span> => [<span class="s">'page'</span> => <span class="s">2</span>, <span class="s">'limit'</span> => <span class="s">50</span>],
]);
<span class="c">// POST mit JSON-Body</span>
<span class="v">$response</span> = <span class="v">$client</span>-><span class="f">post</span>(<span class="s">'/users'</span>, [
<span class="s">'json'</span> => [<span class="s">'name'</span> => <span class="s">'Marek'</span>, <span class="s">'email'</span> => <span class="s">'m@e.de'</span>],
]);
<span class="c">// Status und Header</span>
<span class="v">$response</span>-><span class="f">getStatusCode</span>(); <span class="c">// 201</span>
<span class="v">$response</span>-><span class="f">getHeader</span>(<span class="s">'Content-Type'</span>);</pre>
<h2>Fehlerbehandlung</h2>
<p>Guzzle wirft bei HTTP-Status 4xx/5xx Exceptions. Die hierarchische Struktur erlaubt gezieltes Catchen:</p>
<pre><span class="k">use</span> GuzzleHttp\Exception\ClientException;
<span class="k">use</span> GuzzleHttp\Exception\ServerException;
<span class="k">use</span> GuzzleHttp\Exception\ConnectException;
<span class="k">try</span> {
<span class="v">$response</span> = <span class="v">$client</span>-><span class="f">get</span>(<span class="s">'/users/42'</span>);
} <span class="k">catch</span> (<span class="t">ClientException</span> <span class="v">$e</span>) {
<span class="c">// 4xx Fehler – z.B. 404 Not Found, 401 Unauthorized</span>
<span class="v">$status</span> = <span class="v">$e</span>-><span class="f">getResponse</span>()-><span class="f">getStatusCode</span>();
} <span class="k">catch</span> (<span class="t">ServerException</span> <span class="v">$e</span>) {
<span class="c">// 5xx Fehler – z.B. 500, 503</span>
} <span class="k">catch</span> (<span class="t">ConnectException</span> <span class="v">$e</span>) {
<span class="c">// Netzwerk-Fehler, Timeout, DNS</span>
}</pre>
<h2>Async Requests</h2>
<p>Mehrere Requests parallel statt seriell? Guzzle unterstützt Promises:</p>
<pre><span class="k">use</span> GuzzleHttp\Promise;
<span class="v">$promises</span> = [
<span class="s">'users'</span> => <span class="v">$client</span>-><span class="f">getAsync</span>(<span class="s">'/users'</span>),
<span class="s">'orders'</span> => <span class="v">$client</span>-><span class="f">getAsync</span>(<span class="s">'/orders'</span>),
<span class="s">'stats'</span> => <span class="v">$client</span>-><span class="f">getAsync</span>(<span class="s">'/stats'</span>),
];
<span class="c">// Warte bis alle fertig sind</span>
<span class="v">$results</span> = Promise\Utils::<span class="f">settle</span>(<span class="v">$promises</span>)-><span class="f">wait</span>();
<span class="k">foreach</span> (<span class="v">$results</span> <span class="k">as</span> <span class="v">$key</span> => <span class="v">$result</span>) {
<span class="k">if</span> (<span class="v">$result</span>[<span class="s">'state'</span>] === <span class="s">'fulfilled'</span>) {
<span class="v">$body</span> = (<span class="t">string</span>) <span class="v">$result</span>[<span class="s">'value'</span>]-><span class="f">getBody</span>();
<span class="c">// ...</span>
}
}</pre>
<p>Drei API-Calls in der Zeit eines einzelnen – wenn die API es zulässt, ist das ein riesiger Performance-Gewinn.</p>
<div class="callout tip">
<div class="callout-icon">✓</div>
<div class="callout-body">
<b>PSR-18 als alternative</b>
Wenn du framework-agnostisch bleiben willst, programmiere gegen <code class="inline">Psr\Http\Client\ClientInterface</code>. Guzzle implementiert das, aber auch andere Clients (z.B. Symfony HttpClient). Du tauschst sie dann ohne Code-Änderung aus.
</div>
</div>
<div class="recall">
<b>Recall</b>
<ol>
<li>Wofür ist <code style="color:#777BB4">base_uri</code> in der Client-Konfiguration?</li>
<li>Welche Exception fängst du für HTTP 4xx?</li>
<li>Wann lohnen sich async Requests?</li>
</ol>
</div>
</section>
<!-- ===== KAPITEL 11 ===== -->
<section class="chapter">
<div class="chapter-head">
<div class="chapter-num">11</div>
<div class="chapter-title">
<h1>PHPStan und Static Analysis</h1>
<div class="subtitle">Bugs vor der Ausführung finden</div>
</div>
</div>
<div class="gap">
<b>Frage zum Einstieg</b>
Ein Tippfehler im Property-Namen, ein null-Wert, der zu einer Method-Aufruf-Exception führt, eine Funktion mit falscher Argument-Anzahl – all das passiert in PHP zur Laufzeit, oft erst in Production. <b>PHPStan</b> findet diese Bugs ohne den Code auszuführen.
</div>
<h2>Setup</h2>
<pre>composer require --dev phpstan/phpstan</pre>
<pre><span class="c"># phpstan.neon</span>
parameters:
level: <span class="s">8</span> <span class="c"># strikteste Stufe</span>
paths:
- src
excludePaths:
- vendor</pre>
<pre><span class="c"># Analyse ausführen</span>
vendor/bin/phpstan analyse</pre>
<h2>Die Level-Stufen</h2>
<p>PHPStan kennt Stufen von 0 (loose) bis 9 (sehr strikt). Beim Einstieg in ein Projekt fängst du niedrig an und arbeitest dich hoch:</p>
<table>
<tr><th>Level</th><th>Was geprüft wird</th></tr>
<tr><td>0</td><td>Existenz von Klassen und Funktionen</td></tr>
<tr><td>2</td><td>Falsche Typen in Operatoren</td></tr>
<tr><td>5</td><td>Typen der Funktionsargumente</td></tr>
<tr><td>7</td><td>Möglicherweise null-Werte</td></tr>
<tr><td>8</td><td>Calls auf nullable-Typen</td></tr>
<tr><td>9</td><td>mixed-Werte korrekt einschränken</td></tr>
</table>
<h2>Typische Bugs, die PHPStan findet</h2>
<pre><span class="c">// 1. Tippfehler in Property</span>
<span class="k">class</span> <span class="t">User</span> {
<span class="k">public function</span> <span class="f">__construct</span>(<span class="k">public readonly</span> <span class="t">string</span> <span class="v">$name</span>) {}
}
<span class="v">$user</span> = <span class="k">new</span> <span class="t">User</span>(<span class="s">'Marek'</span>);
<span class="k">echo</span> <span class="v">$user</span>->naem; <span class="c">// PHPStan: Access to undefined property</span>
<span class="c">// 2. null-Aufruf</span>
<span class="k">function</span> <span class="f">findUser</span>(<span class="t">int</span> <span class="v">$id</span>): ?<span class="t">User</span> { <span class="c">/* ... */</span> }
<span class="v">$user</span> = <span class="f">findUser</span>(<span class="s">42</span>);
<span class="k">echo</span> <span class="v">$user</span>->name; <span class="c">// PHPStan: $user might be null</span>
<span class="c">// 3. Falsche Argument-Typen</span>
<span class="k">function</span> <span class="f">double</span>(<span class="t">int</span> <span class="v">$x</span>): <span class="t">int</span> { <span class="k">return</span> <span class="v">$x</span> * <span class="s">2</span>; }
<span class="f">double</span>(<span class="s">'5'</span>); <span class="c">// PHPStan: expects int, gets string</span></pre>
<h2>PHPDoc für Inferenz nutzen</h2>
<p>Je präziser deine PHPDoc-Annotationen, desto mehr findet PHPStan. Besonders mächtig bei Arrays:</p>
<pre><span class="c">/**
* @param array<User> $users
* @return array<string>
*/</span>
<span class="k">function</span> <span class="f">getNames</span>(<span class="t">array</span> <span class="v">$users</span>): <span class="t">array</span> {
<span class="k">return</span> <span class="f">array_map</span>(<span class="k">fn</span>(<span class="v">$u</span>) => <span class="v">$u</span>->name, <span class="v">$users</span>);
}
<span class="c">// Ohne PHPDoc würde PHPStan nicht wissen, was $users enthält</span>
<span class="c">// Mit PHPDoc kann es fehlerhafte Aufrufe finden:</span>
<span class="f">getNames</span>([<span class="k">new</span> <span class="t">User</span>(<span class="s">'M'</span>), <span class="s">'string'</span>]); <span class="c">// PHPStan: erwartet User, nicht string</span></pre>
<h2>Baseline für Legacy-Code</h2>
<p>Wenn du PHPStan zu einem bestehenden Projekt hinzufügst, sind oft hunderte Fehler in altem Code. Eine <b>Baseline</b> ignoriert bestehende Fehler – neue müssen gefixt werden:</p>
<pre>vendor/bin/phpstan analyse --generate-baseline</pre>
<p>Das erzeugt <code class="inline">phpstan-baseline.neon</code>. Bestehende Fehler werden eingefroren, neuer Code wird streng geprüft. Über die Zeit räumst du die Baseline ab.</p>
<div class="callout note">
<div class="callout-icon">i</div>
<div class="callout-body">
<b>Psalm als Alternative</b>
<b>Psalm</b> ist ein vergleichbares Tool von Vimeo. Die Features überschneiden sich zu 95% – PHPStan ist verbreiteter, Psalm geht bei bestimmten Edge Cases tiefer. In Symfony und Laravel-Welt: meist PHPStan.
</div>
</div>
<div class="recall">
<b>Recall</b>
<ol>
<li>Was tut PHPStan, was PHP-Runtime nicht tut?</li>
<li>Welche Level-Stufe sollte langfristig das Ziel sein?</li>
<li>Wofür ist eine PHPStan-Baseline?</li>
</ol>
</div>
</section>
<!-- ===== KAPITEL 12 ===== -->
<section class="chapter">
<div class="chapter-head">
<div class="chapter-num">12</div>
<div class="chapter-title">
<h1>Testen mit PHPUnit</h1>
<div class="subtitle">Unit-Tests, Mocks, Data Providers</div>
</div>
</div>
<div class="gap">
<b>Frage zum Einstieg</b>
Tests fühlen sich als zusätzliche Arbeit an – bis du das erste Mal eine Änderung machst und in Sekunden weißt, dass nichts kaputt ist. <b>PHPUnit</b> ist Standard für PHP-Tests. Wie schreibst du erste Tests und welche Patterns lohnen sich?
</div>
<h2>Setup</h2>
<pre>composer require --dev phpunit/phpunit</pre>
<pre><span class="c">// phpunit.xml</span>
<span class="t"><?xml version="1.0"?></span>
<span class="t"><phpunit</span> xmlns:xsi=<span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span><span class="t">></span>
<span class="t"><testsuites></span>
<span class="t"><testsuite</span> name=<span class="s">"default"</span><span class="t">></span>
<span class="t"><directory></span>tests<span class="t"></directory></span>
<span class="t"></testsuite></span>
<span class="t"></testsuites></span>
<span class="t"></phpunit></span></pre>
<h2>Erste Test-Klasse</h2>
<pre><span class="c">// src/Calculator.php</span>
<span class="k">namespace</span> App;
<span class="k">class</span> <span class="t">Calculator</span> {
<span class="k">public function</span> <span class="f">add</span>(<span class="t">int</span> <span class="v">$a</span>, <span class="t">int</span> <span class="v">$b</span>): <span class="t">int</span> {
<span class="k">return</span> <span class="v">$a</span> + <span class="v">$b</span>;
}
<span class="k">public function</span> <span class="f">divide</span>(<span class="t">int</span> <span class="v">$a</span>, <span class="t">int</span> <span class="v">$b</span>): <span class="t">float</span> {
<span class="k">if</span> (<span class="v">$b</span> === <span class="s">0</span>) <span class="k">throw new</span> \<span class="t">DivisionByZeroError</span>();
<span class="k">return</span> <span class="v">$a</span> / <span class="v">$b</span>;
}
}
<span class="c">// tests/CalculatorTest.php</span>
<span class="k">namespace</span> App\Tests;
<span class="k">use</span> App\Calculator;
<span class="k">use</span> PHPUnit\Framework\TestCase;
<span class="k">class</span> <span class="t">CalculatorTest</span> <span class="k">extends</span> <span class="t">TestCase</span> {
<span class="k">public function</span> <span class="f">testAdd</span>(): <span class="t">void</span> {
<span class="v">$calc</span> = <span class="k">new</span> <span class="t">Calculator</span>();
<span class="v">$this</span>-><span class="f">assertSame</span>(<span class="s">5</span>, <span class="v">$calc</span>-><span class="f">add</span>(<span class="s">2</span>, <span class="s">3</span>));
}
<span class="k">public function</span> <span class="f">testDivideByZero</span>(): <span class="t">void</span> {
<span class="v">$this</span>-><span class="f">expectException</span>(\<span class="t">DivisionByZeroError</span>::<span class="k">class</span>);
(<span class="k">new</span> <span class="t">Calculator</span>())-><span class="f">divide</span>(<span class="s">10</span>, <span class="s">0</span>);
}
}</pre>
<p>Ausführen: <code class="inline">vendor/bin/phpunit</code>. Du siehst grüne Punkte für jeden bestandenen Test.</p>
<h2>Data Providers</h2>
<p>Wenn du eine Methode mit vielen Input-Output-Paaren testen willst, ist ein Data Provider effizienter als einzelne Testmethoden:</p>
<pre><span class="k">use</span> PHPUnit\Framework\Attributes\DataProvider;
<span class="k">class</span> <span class="t">CalculatorTest</span> <span class="k">extends</span> <span class="t">TestCase</span> {
<span class="k">#[DataProvider(<span class="s">'addCases'</span>)]</span>
<span class="k">public function</span> <span class="f">testAdd</span>(<span class="t">int</span> <span class="v">$a</span>, <span class="t">int</span> <span class="v">$b</span>, <span class="t">int</span> <span class="v">$expected</span>): <span class="t">void</span> {
<span class="v">$this</span>-><span class="f">assertSame</span>(<span class="v">$expected</span>, (<span class="k">new</span> <span class="t">Calculator</span>())-><span class="f">add</span>(<span class="v">$a</span>, <span class="v">$b</span>));
}
<span class="k">public static function</span> <span class="f">addCases</span>(): <span class="t">array</span> {
<span class="k">return</span> [
<span class="s">'positives'</span> => [<span class="s">2</span>, <span class="s">3</span>, <span class="s">5</span>],
<span class="s">'negatives'</span> => [<span class="s">-1</span>, <span class="s">-2</span>, <span class="s">-3</span>],
<span class="s">'mixed'</span> => [<span class="s">-5</span>, <span class="s">10</span>, <span class="s">5</span>],
<span class="s">'zero'</span> => [<span class="s">0</span>, <span class="s">0</span>, <span class="s">0</span>],
];
}
}</pre>
<p>PHPUnit ruft <code class="inline">testAdd</code> einmal pro Datensatz auf. Bei Fehler zeigt es den Key (z.B. <code class="inline">'negatives'</code>), du findest den problematischen Fall sofort.</p>
<h2>Mocks für Abhängigkeiten</h2>
<p>Wenn deine Klasse externe Services nutzt (DB, API), willst du im Test nicht wirklich zugreifen. Mit <b>Mocks</b> simulierst du das Verhalten:</p>
<pre><span class="k">public function</span> <span class="f">testOrderServiceLogsCreation</span>(): <span class="t">void</span> {
<span class="c">// Mock erstellen</span>
<span class="v">$logger</span> = <span class="v">$this</span>-><span class="f">createMock</span>(<span class="t">LoggerInterface</span>::<span class="k">class</span>);
<span class="c">// Erwarte: info wird genau 1x mit 'Order created' aufgerufen</span>
<span class="v">$logger</span>-><span class="f">expects</span>(<span class="v">$this</span>-><span class="f">once</span>())
-><span class="f">method</span>(<span class="s">'info'</span>)
-><span class="f">with</span>(<span class="v">$this</span>-><span class="f">stringContains</span>(<span class="s">'Order created'</span>));
<span class="v">$service</span> = <span class="k">new</span> <span class="t">OrderService</span>(<span class="v">$logger</span>);
<span class="v">$service</span>-><span class="f">create</span>(<span class="k">new</span> <span class="t">Order</span>(<span class="s">42</span>));
}</pre>
<h2>Was zuerst testen?</h2>
<p>Faustregel für Anfang:</p>
<ul>
<li><b>Business-Logik</b> – Berechnungen, Domänen-Regeln, Validierung</li>
<li><b>Edge Cases</b> – null, leere Strings, Grenzwerte</li>
<li><b>Bug Fixes</b> – jeder gefixte Bug bekommt einen Test, damit er nicht wiederkehrt</li>
</ul>
<p>Was du <i>nicht</i> testen musst: Getter/Setter, fremde Library-Funktionen, Framework-Code.</p>
<div class="callout tip">
<div class="callout-icon">✓</div>
<div class="callout-body">
<b>Test-Pyramid: viele Unit, wenige E2E</b>
Schreibe viele Unit-Tests (schnell, isoliert), weniger Integration-Tests (echte DB, API), und wenige End-to-End-Tests (gesamte App). Eine Pyramide, keine Eisbecher-Form. Schnelles Feedback ist wichtiger als 100% Test-Coverage.
</div>
</div>
<div class="recall">
<b>Recall</b>
<ol>
<li>Was prüft <code style="color:#777BB4">assertSame</code> im Gegensatz zu <code style="color:#777BB4">assertEquals</code>?</li>
<li>Wofür sind Data Providers?</li>
<li>Warum nutzt du Mocks statt echter Abhängigkeiten in Tests?</li>
</ol>
</div>
</section>
<!-- ===== ENDING ===== -->
<section class="ending">
<h1>Wie es weitergeht</h1>
<p>Du hast PHP jetzt von OOP-Patterns über funktionale Werkzeuge bis zu Production-Tools durchlaufen. Damit baust du wartbare, getestete Applikationen. Aber Praxis schlägt Theorie – setze diese Patterns in echtem Code ein.</p>
<h2>Spaced-Repetition-Plan</h2>
<div class="spaced-plan">
<div class="spaced-day">
<b>Heute</b>
<p>Guide gelesen, Recall-Fragen aus jedem Kapitel beantwortet.</p>
</div>
<div class="spaced-day">
<b>+3 Tage</b>
<p>Drei beliebige Kapitel überfliegen, Recall-Fragen aus dem Kopf.</p>
</div>
<div class="spaced-day">
<b>+14 Tage</b>
<p>Mini-Projekt: REST-API mit PDO, Tests und PHPStan Level 8.</p>
</div>
<div class="spaced-day">
<b>+60 Tage</b>
<p>Bestehendes Projekt um Tests und Static Analysis erweitern.</p>
</div>
</div>
<h2>Was als nächstes lernen</h2>
<p>Mit diesen Werkzeugen kannst du in jede Spezialisierung tiefer einsteigen:</p>
<ul>
<li><b>Frameworks</b> – Symfony oder Laravel mit voller Tiefe (DI, Events, Forms, Security)</li>
<li><b>Doctrine ORM</b> – Domain-Modelle mit komplexen Beziehungen abbilden</li>
<li><b>Event-Driven Architecture</b> – Messenger, Queues, Async-Processing</li>
<li><b>Performance</b> – Profiling mit Blackfire, OpCache, Caching-Strategien</li>
<li><b>Security</b> – Authentication, OWASP Top 10, Code-Review</li>
<li><b>Microservices</b> – kleine PHP-Services, API-Gateways, Service-Discovery</li>
</ul>
<h2>Begleitmaterial</h2>
<p>Dieser Guide ist Teil eines Sets:</p>
<ul>
<li><b>PHP OnePager</b> – die visuelle Übersicht</li>
<li><b>PHP Cheatsheet</b> – die dichte Referenz</li>
<li><b>PHP Mini-Guide</b> – der 15-Min-Schnelleinstieg</li>
<li><b>PHP Anfänger-Guide</b> – die Grundlagen</li>
<li><b>PHP Fortgeschritten-Guide</b> (dieses Dokument) – Patterns und Production</li>
</ul>
</section>
</body>
</html>