---
title: 'PHP 8.0 from PHP 7.4: JIT, Union Types, Match & Nullsafe — Upgrade Guide | DevSense'
description: 'Erkunden Sie PHP 8.0 nach 7.4: Named Arguments, Match-Expression, Nullsafe-Operator, Attribute, JIT, Union-Typen – was bricht, was umgeschrieben werden muss und ausführbare Beispiele.'
faq:
    - { question: 'Was ist der Hauptvorteil des JIT-Compilers in PHP 8.0?', answer: 'Der JIT-Compiler (Just-In-Time) übersetzt Bytecode zur Laufzeit direkt in Maschinencode. Dies bietet bis zu 500% Leistungssteigerung für rechenintensive CPU-bound Aufgaben, während typische Webanwendungen etwa 1-5% an Geschwindigkeit gewinnen.' }
    - { question: 'Wie unterscheidet sich der Match-Ausdruck von einem klassischen Switch-Statement?', answer: "Match verwendet einen strengen Identitätsvergleich (===), gibt direkt einen Wert zurück, hat kein automatisches Durchlaufen (kein 'fall-through', somit wird kein 'break' benötigt) und wirft einen UnhandledMatchError, wenn kein Wert übereinstimmt und kein 'default' angegeben ist." }
    - { question: 'Was ist Constructor Property Promotion in PHP 8.0?', answer: 'Constructor Property Promotion ermöglicht es Entwicklern, Klasseneigenschaften direkt in der Parameterliste des Konstruktors zu deklarieren und zu initialisieren, wodurch separate Eigenschaftsdeklarationen und -zuweisungen entfallen.' }
    - { question: 'Wie haben sich lose Vergleiche in PHP 8.0 verändert?', answer: 'In PHP 8.0 vergleichen lose Vergleiche zwischen Zahlen und nicht-numerischen Strings diese als Strings, anstatt den String in 0 zu konvertieren. Dies verhindert Fehler wie ''0 == "foobar"'', was zuvor true zurückgab.' }
published: '2026-05-31'
---
# PHP 8.0: Hauptmerkmale

Von PHP 7.4 kommend: Praxisnahe Hinweise zu JIT, Typen und den BC-Breaks, die in realen Anwendungen auftreten.

## Inhalt
* [Named Arguments (Benannte Argumente)](#named-arguments)
* [Match-Ausdruck](#match-expression)
* [Nullsafe-Operator (?->)](#nullsafe-operator)
* [Constructor Property Promotion](#constructor-promotion)
* [Evolution des Typsystems (Union-Typen, mixed, static)](#type-system)
* [Attribute / Annotationen](#attributes)
* [Neue String-Funktionen](#string-functions)
* [Weak Maps](#weak-maps)
* [Throw-Ausdruck und Fehlerbehandlung](#throw-and-errors)
* [JIT-Compiler und Performance](#jit-performance)
* [Änderungen an Modulen und Core](#modules-changes)
* [Syntax-Optimierungen (::class, abschließende Kommata)](#syntax-tweaks)
* [Sinnvollere Vergleiche von Strings mit Zahlen](#saner-comparisons)
* [Rückwärtskompatibilität und BC-Breaks (Migrationshinweise)](#backward-incompatible)
* [Häufige Fehler](#common-mistakes)
* [Selbsttest-Quiz](#self-test-quiz)

---

Das Upgrade auf eine neue Hauptversion einer Sprache ist immer ein Wagnis: Man wünscht sich die glänzenden neuen Performance-Gewinne, fürchtet aber die nächtlichen Abstürze in der Produktion, die durch veraltete Code-Annahmen verursacht werden. PHP 8.0 ist nicht nur ein typisches kleines Update – es ist ein grundlegender Reset der Plattform. Durch die Einführung des JIT-Compilers, die Umwandlung loser Typkonvertierungen in strenge Vergleiche und die Transformation stiller Warnungen in skriptbeendende Errors verlangt PHP 8.0 ein völliges Umdenken bei der Entwicklung und Migration. Hier ist eine pragmatische, praxiserprobte Übersicht darüber, was sich unter der Haube ändert und was in Ihrer Anwendung tatsächlich brechen wird.

<a id="named-arguments"></a>
## Named Arguments (Benannte Argumente)

Die Übergabe von Argumenten an eine Funktion anhand des Namens macht die Reihenfolge der Parameter überflüssig und ermöglicht das Überspringen optionaler Parameter. Dies macht den Code selbstdokumentierend.

> [!NOTE]
> **Wussten Sie schon?**
> Ab PHP 8.0 ist das Umbenennen eines Parameters in einer Elternklasse oder einem Interface ein BC-Break, wenn Sie benannte Argumente verwenden! Wenn eine Kindklasse oder ein Aufrufer den alten Parameternamen verwendet, wird ein `ArgumentCountError` oder `Error` geworfen.

```php
// Zuvor: Sie mussten alle Parameter in der richtigen Reihenfolge angeben
// app/Services/CookieService.php
setcookie('test', '', time() + 60 * 60 * 2, '/', '', false, true);

// PHP 8.0: Nur angeben, was wirklich benötigt wird
// app/Services/CookieService.php
setcookie(
    name: 'test',
    expires: time() + 60 * 60 * 2,
    httponly: true
);
```

---

<a id="match-expression"></a>
## Match-Ausdruck

Eine strengere und prägnantere Alternative zum Switch-Statement. Der Ausdruck gibt einen Wert zurück und nutzt den strengen Vergleich (===), was unerwartete Fehler durch implizite Typkonvertierung verhindert.

```php
// app/Services/ResponseService.php
$statusCode = 200;

$statusMessage = match ($statusCode) {
    200, 300 => 'Erfolg oder Weiterleitung',
    400, 404 => 'Client-Fehler',
    500 => 'Server-Fehler',
    default => 'Unbekannter Status',
};
```

---

<a id="nullsafe-operator"></a>
## Nullsafe-Operator (?->)

Ermöglicht das Auslesen von Eigenschaften und das Aufrufen von Methoden in einer Kette. Wenn eines der Elemente in der Kette null ist, gibt die gesamte Kette null zurück, anstatt einen Fatal Error zu werfen.

```php
// PHP 7.4
// app/Services/SessionService.php
$country = null;
if ($session !== null) {
    $user = $session->user;
    if ($user !== null) {
        $country = $user->getAddress()->country;
    }
}

// PHP 8.0
// app/Services/SessionService.php
$country = $session?->user?->getAddress()?->country;
```

---

<a id="constructor-promotion"></a>
## Constructor Property Promotion

Zuvor erforderte das Erstellen einfacher DTOs (Data Transfer Objects) oder Value Objects viel Boilerplate-Code: Eigenschaften deklarieren, sie an den Konstruktor übergeben und dort zuweisen. PHP 8.0 kombiniert diese drei Schritte in einem einzigen Schritt.

```php
// PHP 7.4: Klassischer (und langatmiger) Ansatz
// app/DTO/UserDTO.php
class UserDTO 
{
    public string $name;
    public string $email;
    protected int $age;

    public function __construct(string $name, string $email, int $age) 
    {
        $this->name = $name;
        $this->email = $email;
        $this->age = $age;
    }
}

// PHP 8.0: Elegant und prägnant
// app/DTO/UserDTO.php
class UserDTO 
{
    public function __construct(
        public string $name,
        public string $email,
        protected int $age,
    ) {}
}
```

---

<a id="type-system"></a>
## Evolution des Typsystems

In PHP 8.0 ist das Typsystem deutlich strenger und ausdrucksstärker geworden.

### Union-Typen
Vor PHP 8.0 waren wir bei Variablen, die mehrere Datentypen akzeptieren konnten (z. B. `int` oder `float`), auf PHPDoc-Annotationen angewiesen. Jetzt unterstützt PHP dies nativ.

```php
// app/Services/CalculatorService.php
class Calculator 
{
    private int|float $number;

    public function setNumber(int|float $number): void 
    {
        $this->number = $number;
    }
}
```

### Der mixed Pseudo-Typ

Der neue Typ `mixed` löst Probleme in Legacy-Code. Er entspricht `array|bool|callable|int|float|null|object|resource|string`. Hinweis: `mixed` beinhaltet bereits `null`. Daher ist das Schreiben von `?mixed` oder `mixed|null` nicht erlaubt und führt zu einem Compile-Error.

### Der static Rückgabetyp

Zur Implementierung von Patterns wie "Late Static Binding" und "Fluent Interfaces" wurde der Rückgabetyp `static` hinzugefügt (zuvor war nur `self` oder `parent` möglich).

```php
// app/Factories/BaseFactory.php
class BaseFactory {
    public function create(): static {
        return new static();
    }
}
```

### Das Stringable-Interface

Wenn eine Klasse die magische Methode `__toString()` implementiert, weist PHP 8.0 ihr automatisch (implizit) das `Stringable-Interface` zu. Dies erlaubt die Verwendung von `string|Stringable` in Typ-Hints.

---

<a id="attributes"></a>
## Attribute / Annotationen (Deep Dive)

**Attribute** (in PHP 8.0 eingeführt) sind **strukturierte Metadaten**, die Sie an Klassen, Methoden, Eigenschaften, Parameter und Konstanten anhängen können. Die Engine speichert diese im Bytecode; Sie lesen sie mittels **Reflection** aus. Sie sind **keine Magie**: Nichts passiert von allein, es sei denn, *Ihr* Framework, Router oder Werkzeug liest sie aus – genau wie Annotationen in Java oder C#, aber nativ in PHP.

### Attribute vs. PHPDoc

| | PHPDoc (`@route`, `@deprecated`) | Attribute (`#[Route]`) |
|---|----------------------------------|-------------------------|
| Parsing | String in Kommentaren; erfordert eigene Parser | First-Class-Syntax; kein Regex über Kommentare |
| Typisierung | Informell; neigt dazu, vom echten Code abzuweichen | Konstruktor-Argumente sind echte PHP-Werte |
| Tooling | IDE-Unterstützung variiert | Reflection API ist stabil und schnell |

Verwenden Sie PHPDoc für **menschliche Dokumentation**; nutzen Sie Attribute für **Verhalten, das Ihr Code tatsächlich auswertet** (Routing, Validierungshinweise, Serialisierung, Code-Generierung).

### Deklaration einer Attribut-Klasse

Jede Klasse kann als Attribut verwendet werden, wenn sie mit dem eingebauten Attribut `#[Attribute]` markiert ist. Eine optionale Bitmaske schränkt ein, **wo** es deklariert werden darf (`Attribute::TARGET_*`). Ohne Angabe gilt es standardmäßig für **alle** Ziele (in der Regel ist es besser, explizit zu sein).

```php
// app/Attributes/Route.php
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
final class Route
{
    public function __construct(
        public string $path,
        public array $methods = ['GET'],
    ) {}
}
```

* **`final`** wird häufig verwendet, damit Attribut-Klassen nicht versehentlich vererbt werden.
* Parameter des Konstruktors werden zu den **Argumenten**, die bei der Nutzung übergeben werden (`#[Route('/x', methods: [...])]`).

Sie können ein Attribut über die Bitmaske mit `Attribute::IS_REPEATABLE` auch als **mehrfach verwendbar** auf demselben Element deklarieren (z. B. für mehrere Validierungsregeln).

### Anwendung von Attributen (Syntax)

Attribute stehen **vor** der Deklaration, einzeln, mehrfach pro Zeile oder gruppiert:

```php
// app/Http/Controllers/UserController.php
#[Route('/api/users', methods: ['GET', 'POST'])]
final class UserController {}

// app/Http/Controllers/ItemController.php
#[Route('/items')]
final class ItemController
{
    #[Route('/items/{id}', methods: ['GET'])]
    public function show(int $id): array
    {
        return ['id' => $id];
    }
}
```

Benannte Argumente (PHP 8.0) lesen sich hierbei sehr natürlich: `methods: ['GET']` wird direkt dem Konstruktor-Parameter zugeordnet.

### Wie es zur Laufzeit funktioniert

1. PHP **parst** die Attribute zur Kompilierzeit und verknüpft sie mit der Reflection-Struktur.
2. Zur Laufzeit holen Sie sich ein `ReflectionClass`-, `ReflectionMethod`- oder `ReflectionProperty`-Objekt.
3. Rufen Sie **`getAttributes()`** auf, um `ReflectionAttribute`-Instanzen zu erhalten, und anschließend **`newInstance()`**, um Ihre Attribut-Klasse mit den Argumenten aus dem Quellcode zu instanziieren.

Nichts läuft „von selbst“: Ihr Bootstrap, DI-Container oder Router muss Reflection aufrufen.

### Auslesen von Attributen mit Reflection

```php
// app/Services/RouterService.php
$reflection = new ReflectionClass(UserController::class);

// Nur Attribute einer bestimmten Klasse laden (empfohlen)
foreach ($reflection->getAttributes(Route::class) as $attr) {
    /** @var Route $route */
    $route = $attr->newInstance();
    // $route->path, $route->methods
}

// Alle Attribute auf der Klasse laden (selbst filtern)
foreach ($reflection->getAttributes() as $attr) {
    $name = $attr->getName();       // z. B. Route::class
    $args = $attr->getArguments(); // Rohe Konstruktor-Argumente
}
```

Verwenden Sie **`ReflectionMethod::getAttributes()`** für Methoden-Routen oder Middleware, **`ReflectionParameter::getAttributes()`** für Parameter-Validierungen usw.

---

<a id="saner-comparisons"></a>
## Sinnvollere Vergleiche von Strings mit Zahlen

Dies ist einer der tückischsten BC-Breaks. Zuvor konvertierte PHP beim losen Vergleich zwischen einer Zahl und einem String den String in eine Zahl. In PHP 8.0 werden Zahlen bei nicht-numerischen Strings stattdessen als Strings verglichen.

```php
// app/Services/ComparisonService.php
// PHP 7.4
0 == 'foobar'; // true

// PHP 8.0
0 == 'foobar'; // false
```

---

<a id="syntax-tweaks"></a>
## Syntax-Optimierungen

### Verwendung von `::class` auf Objekten

Sie können den Klassennamen nun über `::class` direkt von einer Objektinstanz abfragen. Zuvor musste dafür die Funktion `get_class()` genutzt werden.

```php
// app/Services/ReflectionService.php
$object = new \App\Models\User();
// PHP 7: get_class($object);
echo $object::class; // Gibt "App\Models\User" aus
```

### Abschließende Kommata

Es ist nun erlaubt, ein abschließendes Komma in Parameterlisten von Funktionen, Methoden und Closures zu hinterlassen. Dies sorgt für sauberere Git-Commits.

```php
// app/Services/RequestService.php
public function makeRequest(
    string $url,
    array $data,
    array $headers, // <-- Abschließendes Komma ist nun erlaubt
) { ... }
```

---

<a id="string-functions"></a>
## Neue String-Funktionen

Anstelle von `strpos()` und der anschließenden Prüfung auf `!== false` wurden drei neue Funktionen hinzugefügt, die ein striktes `bool` zurückgeben:

```php
// app/Services/StringService.php
$str = "DevSense ist großartig";

str_starts_with($str, "DevSense"); // true
str_contains($str, "großartig");   // true
str_ends_with($str, "großartig");  // true
```

### Die Funktion `get_debug_type()`

Die neue Funktion `get_debug_type()` gibt den tatsächlichen Typ einer Variable zurück (z. B. `App\Models\User` statt nur `object`, oder `int` statt `integer`). Perfekt für aussagekräftige Fehlermeldungen.

---

<a id="weak-maps"></a>
## Weak Maps

Ein architektonischer Durchbruch für `ORMs` und `Caching`. `WeakMap` erlaubt es, Verknüpfungen zu Objekten zu erstellen, die den Garbage Collector nicht daran hindern, diese Objekte aus dem Speicher zu entfernen, wenn sie ansonsten nicht mehr referenziert werden.

```php
// app/Services/CacheService.php
class Cache {
    private WeakMap $cache;
    
    public function __construct() {
        $this->cache = new WeakMap();
    }
    
    public function getMetadata(object $obj): array {
        if (!isset($this->cache[$obj])) {
            $this->cache[$obj] = $this->computeExpensiveData($obj);
        }
        return $this->cache[$obj];
    }
}
```

Sobald das Objekt `$obj` in der Anwendung zerstört wird, verschwindet es automatisch auch aus der `WeakMap` und gibt den Speicher frei.

---

<a id="throw-and-errors"></a>
## Throw-Ausdruck und Fehlerbehandlung

Der `throw`-Operator wurde von einer Anweisung (Statement) zu einem Ausdruck (Expression) geändert.

```php
// app/Services/UserService.php
// Sie können Exceptions nun direkt in ternären Operatoren oder beim Null-Coalescing werfen
$user = $request->get('user') ?? throw new InvalidArgumentException('User ist erforderlich');

$callable = fn() => throw new Exception('Dies sollte nicht ausgeführt werden');
```

Globale Änderung der Strenge: In PHP 8.0 unterdrückt der `@`-Operator keine fatalen Fehler mehr. Zudem wurden die meisten Engine-Warnungen in `Error`-Exceptions umgewandelt (z. B. führt der Zugriff auf eine Eigenschaft eines Nicht-Objekts nun zum Skriptabbruch statt nur zu einer Warnung im Log).

### Fehlerfreie Catches ohne Variable

Wenn Sie eine Exception abfangen müssen, das Exception-Objekt selbst aber nicht benötigen, kann die Zuweisung an eine Variable nun weggelassen werden.

```php
// app/Services/ErrorService.php
// Zuvor: Deklaration der Variable $e war zwingend erforderlich
try {
    // Code
} catch (Exception $e) {
    Log::error('Etwas ging schief');
}

// PHP 8.0:
try {
    // Code
} catch (Exception) {
    Log::error('Etwas ging schief');
}
```

### Type Errors bei internen Funktionen

Die meisten internen PHP-Funktionen werfen nun bei ungültigen Parametern eine strikte `TypeError`- oder `ValueError`-Exception, anstatt eine Warnung auszugeben und `null` zurückzugeben.

```php
// app/Services/DebugService.php
// PHP 7.4
strlen([]); // Warning: strlen() expects parameter 1 to be string, array given

// PHP 8.0
strlen([]); // TypeError: strlen(): Argument #1 ($str) must be of type string, array given
array_chunk([], -1); // ValueError: array_chunk(): Argument #2 ($length) must be greater than 0
```

---

<a id="jit-performance"></a>
## JIT-Compiler und Performance

Die grundlegendste architektonische Änderung unter der Haube von PHP 8.0 ist die Einführung des **JIT-Compilers (Just-In-Time)**.

**OPcache** (Vor PHP 8): Der Quellcode wurde in OpCodes parst und die Zend Virtual Machine führte diese Zeile für Zeile aus.

**JIT (PHP 8.0+)**: Analysiert die OpCodes und kompiliert häufig genutzte Pfade ("hot paths") direkt in Maschinencode für die CPU (x86/ARM).

Für typische I/O-lastige Webanwendungen liegt der Performance-Gewinn bei rund 1-5%. Bei reinen Berechnungen (CPU Bound) liegt er jedoch bei **300% bis 500%**.

> [!NOTE]
> **Wussten Sie schon?**
> Der JIT-Compiler in PHP 8.0 läuft als Teil von OPcache. Wenn Sie `opcache.jit_buffer_size` aktivieren, aber vergessen, `opcache.enable` oder `opcache.enable_cli` zu setzen, bleibt der JIT-Compiler ohne jegliche Warnung vollständig deaktiviert!

---

<a id="modules-changes"></a>
## Änderungen an Modulen und Core

Die Bereinigung des Core von intransparenten Ressourcen-Typen geht weiter. Diese werden durch Objekte ersetzt:

* **cURL**: `curl_init()` gibt ein `CurlHandle`-Objekt zurück.
* **GD**: `imagecreate()` gibt eine `GdImage`-Instanz zurück.
* **Sockets**: `socket_create()` gibt eine `Socket`-Instanz zurück.

Speicherbereinigung: Die neuen Objekte werden vom Garbage Collector automatisch zerstört. Funktionen wie `curl_close()` sind funktionslos geworden.

Weitere Änderungen an Erweiterungen:
* **JSON** ist nun fest in den Core integriert (kann nicht mehr über `--disable-json` deaktiviert werden).
* **XML-RPC** wurde aus dem Core nach PECL ausgelagert.
* Die mathematische Funktion **`fdiv()`** wurde hinzugefügt, die Divisionen durch Null erlaubt und `INF`, `-INF` oder `NAN` zurückgibt, anstatt einen Fehler zu werfen.

---

<a id="backward-incompatible"></a>
## Rückwärtskompatibilität und BC-Breaks (Migrationshinweise)

Selbst wenn Sie die neue Syntax nicht verwenden, kann das Upgrade auf PHP 8.0 Anwendungen aufgrund strengerer Verhaltensweisen zur Laufzeit und entfernter Funktionen beeinträchtigen. Hier ist eine Checkliste der häufigsten Fallstricke.

### Sprache / Schlüsselwörter / Entfernungen

* `match` ist jetzt ein **reserviertes Schlüsselwort**.
* `mixed` ist jetzt ein **reserviertes Wort** (darf nicht als Name für Klassen, Interfaces oder Traits verwendet werden).
* `__autoload()` wurde entfernt. Verwenden Sie `spl_autoload_register()`.
* `create_function()` wurde entfernt. Verwenden Sie anonyme Funktionen.
* `each()` wurde entfernt. Verwenden Sie `foreach`.
* Case-insensitive Konstanten-Definitionen wurden entfernt (`define('FOO', 'bar', true)` wird nicht mehr unterstützt).
* Methoden mit dem Namen der Klasse werden nicht mehr als Konstruktoren behandelt (nutzen Sie `__construct()`).
* Der Aufruf nicht-statischer Methoden als statische Methode ist nicht mehr erlaubt.
* Die Casts `(real)` und `(unset)` wurden entfernt.

### Fehlerbehandlung & Laufzeit-Strenge

* Assertions werfen standardmäßig Exceptions bei Fehlern.
* Die INI-Direktive `track_errors` wurde entfernt (somit ist `$php_errormsg` nicht mehr verfügbar; nutzen Sie `error_get_last()`).
* Der `@`-Operator unterdrückt keine fatalen Fehler mehr.
* Das Standard-Fehler-Reporting-Level ist nun `E_ALL`.
* Viele Warnungen wurden zu `Error`-Exceptions erhoben (z. B. Schreiben auf eine Eigenschaft eines Nicht-Objekts, ungültige Array-Schlüssel-Typen, Entpacken eines Nicht-Iterables).
* Viele Notices wurden zu Warnungen (undefinierte Variablen/Eigenschaften/Array-Schlüssel).

### Numerische Strings & Typkonvertierung

* Lose Vergleiche zwischen Zahlen und nicht-numerischen Strings verhalten sich nun anders (Zahlen werden als Strings verglichen).
* Arithmetische Operationen auf nicht-numerischen Strings werfen nun `TypeError`.
* Float-zu-String-Konvertierungen sind nun unabhängig von der System-Locale.

---

<a id="common-mistakes"></a>
## ⚠️ Häufige Fehler

Hier sind typische Stolpersteine bei der Verwendung der neuen Features:

### 1. Match-Ausdruck ohne Default
Ein `match`-Ausdruck muss alle möglichen Fälle abdecken. Wenn keine Bedingung zutrifft und kein `default`-Zweig definiert ist, wirft PHP einen `UnhandledMatchError`.

```php
// app/Services/PaymentService.php
// SCHLECHT: Stürzt ab, wenn $status den Wert 'failed' hat
$message = match ($status) {
    'pending' => 'Warten auf Zahlung',
    'completed' => 'Erfolgreich bezahlt',
};

// GUT: Immer einen Fallback definieren
$message = match ($status) {
    'pending' => 'Warten auf Zahlung',
    'completed' => 'Erfolgreich bezahlt',
    default => 'Zahlung fehlgeschlagen oder abgebrochen',
};
```

### 2. Nebeneffekte in Nullsafe-Ketten
Der Nullsafe-Operator bricht die gesamte Kette ab, sobald ein Glied `null` liefert. Wenn Sie jedoch versuchen, Werte innerhalb einer Nullsafe-Kette zuzuweisen oder Methoden mit Nebeneffekten aufzurufen, werden diese eventuell nicht ausgeführt. Zudem darf der Nullsafe-Operator nicht auf der linken Seite einer Zuweisung stehen.

```php
// app/Services/UserService.php
// SCHLECHT: Syntax-Fehler - Zuweisung in einer Nullsafe-Kette nicht erlaubt
$session?->user?->name = 'John'; 

// SCHLECHT: Log-Methode wird eventuell nicht aufgerufen, wenn $logger null ist
$logger?->log('Action occurred')?->sendNotification();
```

### 3. Reihenfolge benannter Argumente
Sie können positionelle und benannte Argumente mischen, aber benannte Argumente müssen immer **nach** den positionellen Argumenten stehen. Andernfalls tritt ein Compile-Error auf.

```php
// app/Services/NotificationService.php
// SCHLECHT: Benanntes Argument vor positionellem Argument (Compile-Error)
sendEmail(subject: 'Hello', $userEmail);

// GUT: Benannte Argumente stehen am Ende
sendEmail($userEmail, subject: 'Hello');
```

---

<a id="self-test-quiz"></a>
## Selbsttest-Quiz

Testen Sie Ihr Wissen über PHP 8.0.

### Frage 1: Was ist die Ausgabe des folgenden Codes?
```php
// app/Http/Controllers/TestController.php
$result = 0 == 'hello';
var_dump($result);
```
* A) `bool(true)`
* B) `bool(false)`
* C) `TypeError`

<details>
<summary>Antworten anzeigen</summary>

**Richtige Antwort: B**  
In PHP 8.0 vergleicht der lose Vergleich (`==`) eine Zahl mit einem nicht-numerischen String als Strings. Somit wird `0 == 'hello'` als `'0' == 'hello'` ausgewertet, was `false` ergibt. In PHP 7.4 hätte dies noch `true` ergeben.
</details>

### Frage 2: Welche Exception wird geworfen, wenn ein `match`-Ausdruck keinen passenden Zweig und kein `default` enthält?
* A) `RuntimeException`
* B) `ValueError`
* C) `UnhandledMatchError`

<details>
<summary>Antworten anzeigen</summary>

**Richtige Antwort: C**  
PHP 8.0 wirft einen `UnhandledMatchError`, wenn kein Zweig des `match`-Blocks auf den Wert zutrifft und kein `default`-Zweig vorhanden ist.
</details>

---

## Zusammenfassung

Betrachten Sie PHP 8.0 als einen **Reset der Plattform**: Weniger Überraschungen bei losen Vergleichen, klarere Fehlermeldungen aus internen APIs und ein Typsystem, das endlich der Realität moderner PHP-Anwendungen entspricht. Der Lohn sind weniger schwer zu findende Laufzeitfehler in der Produktion und ein viel einfacherer Weg hin zu PHP 8.1+.