set_error_handler() ist praktisch, wenn du PHP-Fehler nicht einfach irgendwo verstreut behandeln willst. Stattdessen reagierst du an einer zentralen Stelle auf Warnungen, Notices oder eigene Fehler.
set_error_handler(function (int $severity, string $message, string $file, int $line): bool {
error_log("[$severity] $message in $file:$line");
return true;
});
Warum das nützlich ist:
Du bekommst einheitliches Logging und kannst viele alte Warnungen in saubere Exceptions umwandeln.
set_error_handler(function (int $severity, string $message, string $file, int $line): bool {
throw new ErrorException($message, 0, $severity, $file, $line);
});
Besonders hilfreich bei:
- Legacy-Code
- Bibliotheken mit schwacher Fehlerbehandlung
- Projekten, in denen du Fehler lieber gesammelt als stillschweigend behandeln willst
Wichtig:
Nicht jeder Fehler sollte blind zu einer Exception werden. In produktiven Systemen lohnt sich ein klarer Plan, welche Fehler du hart behandelst und welche nur geloggt werden.
register_shutdown_function() läuft ganz am Ende des Skripts — auch dann, wenn vorher etwas richtig schiefgelaufen ist. Genau deshalb ist die Funktion so wertvoll für Logging und Notfall-Handling.
register_shutdown_function(function (): void {
$error = error_get_last();
if ($error === null) {
return;
}
error_log($error['message'] . ' in ' . $error['file'] . ':' . $error['line']);
});
Warum das nützlich ist:
Du kannst fatale Fehler noch erkennen, bevor der Request komplett verschwindet, und bekommst wenigstens ein sauberes Log.
Typische Einsatzfälle:
- Fehlerprotokollierung
- Cleanup von temporären Dateien
- letzte Benachrichtigung bei kritischen Abbrüchen
Wichtig:
Die Shutdown-Funktion ist keine Wunderwaffe. Wenn schon ein fataler Fehler passiert ist, ist dein Spielraum begrenzt — aber für Diagnose und Aufräumen oft genau richtig.
Wenn irgendwo eine Exception durchrutscht, ist set_exception_handler() dein letzter sauberer Anlaufpunkt. Genau dafür ist die Funktion stark: Du definierst einmal zentral, was bei einem unbehandelten Fehler passieren soll.
set_exception_handler(function (Throwable $e): void {
error_log($e->__toString());
http_response_code(500);
echo 'Es ist ein Fehler aufgetreten.';
});
Warum das nützlich ist:
Du brauchst nicht überall denselben Fallback-Code und bekommst trotzdem einheitliches Logging sowie eine kontrollierte Antwort an den Browser.
Besonders praktisch bei:
- APIs
- kleinen Projekten ohne großes Framework
- Bootstrap-Dateien, die den ganzen Request initialisieren
Wichtig:
Der Handler selbst sollte sehr robust sein. Wenn dort wieder ein Fehler passiert, wird die eigentliche Ursache schnell schwerer nachvollziehbar.
IPv6 wird oft nur auf mehr Adressen reduziert. Der eigentliche Gewinn kommt aber erst dann, wenn immer mehr Netze nativ umsteigen: weniger NAT, weniger Port-Forwarding, weniger CGNAT und insgesamt weniger historische Workarounds.
Dadurch werden Netze und Software einfacher. Endpunkte sind wieder direkter erreichbar, die Planung wird sauberer und selbst im Protokoll fällt Ballast weg, weil IPv6 keine Header-Prüfsumme mehr hat, die Router bei jedem Hop neu berechnen müssen.
IPv6 ist nicht automatisch schneller oder sicherer, aber deutlich aufgeräumter. Je mehr umsteigen, desto mehr verschwinden Sonderfälle und desto einfacher wird Infrastruktur langfristig. Viele Mobilfunknetze zeigen schon heute, dass IPv6-first in der Praxis längst funktioniert.
Statt berechnete Werte im Code vorzubereiten oder redundant in der DB zu speichern, übernimmt MariaDB/MySQL das selbst:
ALTER TABLE orders
ADD COLUMN gross DECIMAL(10,2) AS (net * (1 + tax_rate)) VIRTUAL;
VIRTUAL bedeutet: kein eigener Speicherplatz – der Wert wird beim Lesen berechnet. Die Alternative STORED schreibt ihn physisch in die Tabelle.
Vorteil: Kein Trigger, kein App-Code, keine Synchronisations-Fehler – die Spalte ist immer korrekt.
Index: Nutzt du die VIRTUAL Column in einer WHERE-Bedingung, lohnt sich ein Index darauf – sonst muss MariaDB/MySQL den Ausdruck für jede Zeile im Scan berechnen:
ALTER TABLE orders ADD INDEX idx_gross (gross);
Der Unterschied ist dramatisch: Abfragen auf großen Tabellen fallen damit von mehreren Sekunden auf wenige Millisekunden – bei mir von 4 Sekunden auf 80 ms. Verwendest du die Spalte hingegen nur im SELECT, ist kein Index nötig: Der Ausdruck wird erst nach dem Filtern berechnet, also nur für die Zeilen, die bereits alle anderen Bedingungen passiert haben.
Nested CSS nutzt du direkt im Eltern-Block mit & für States, Klassen und Elemente.
.card {
padding: 1rem;
border: 1px solid var(--border);
& h2 {
margin: 0 0 0.5rem;
}
&:hover {
border-color: var(--accent);
}
&.is-featured {
background: var(--surface-highlight);
}
@media (min-width: 48rem) {
padding: 1.5rem;
}
}
Damit bleiben Elemente, Hover-State und Responsive-Anpassung direkt an der Komponente.
In manchen Fällen wird damit SCSS für genau solche Verschachtelungen obsolet.
Die Idee: Prüfe Ausnahmefälle zuerst und verlasse die Funktion sofort. So bleibt alles sauber.
function createInvoice(?User $user, array $items) {
if ($user) {
if ($user->isActive()) {
if ($items) {
return Invoice::fromItems($user, $items);
}
}
}
return null;
}
function createInvoice(?User $user, array $items) {
if (!$user) {
return null;
}
if (!$user->isActive()) {
return null;
}
if (!$items) {
return null;
}
return Invoice::fromItems($user, $items);
}
Vorteil: Weniger Einrückung, schneller erfassbar, leichter zu testen.
Spam-Formulare kommen häufig automatisch zustande. Ein zusätzliches Feld, das Menschen nicht sehen (und Bots trotzdem ausfüllen), filtert gefühlt 98 % der Einsendungen, weil du die Verarbeitung sofort abbrichst, sobald das Feld Inhalt hat.
<input type="text" name="unused" aria-hidden="true" autocomplete="off" tabindex="-1" />
[name="unused"] { position: absolute; left: -9999px; }
Serverseitig reicht ein einfacher Check: läuft da was rein? Dann verwirfst du den Eintrag und sparst dir die komplexen „I’m not a robot“-Checkboxen, das JavaScript und externe APIs.
Eleventy ist das perfekte SSG, wenn du mit Markdown, Nunjucks oder HTML schreiben willst und dir der Build nicht im Weg stehen soll. Du legst ein paar Markdown-Dateien, packst deine Assets daneben und lässt Eleventy rendern. Layouts oder Includes kannst du dazuschalten, aber sie sind optional der ganze Ablauf bleibt minimal.
Probiere es in 2 Schritten aus:
echo "Hello World" > index.md
npx @11ty/eleventy
Wenn du im Frontend schnell debuggen willst, nutze einen geheimen Hash im User-Agent.
Nur wenn der Hash stimmt, gibt das Backend Debug-Infos aus (z. B. Timing, Cache-Hit/Miss, interne Steps).
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
$token = $_ENV['UA_DEBUG_TOKEN'] ?? '';
$debug = $token !== '' && str_contains($ua, 'DevCoreDebug/' . $token);
if ($debug) {
header('X-Debug-Info: ' . json_encode(['dbMs' => 42, 'cache' => 'MISS']));
}
Warum gut:
Du erzeugst keinen Debug-Noise für normale User, siehst direkt im echten Frontend, was passiert, und kannst alles ohne extra UI-Flag aktivieren.
Wichtig:
Das ist kein Security-Feature, sondern nur ein Debug-Schalter; gib keine sensiblen Daten aus und wechsle den Token regelmäßig.
Komplizierte Formularverarbeitung? Nutze HTML-Arrays!
<input name="user[name]">
<input name="user[email]">
<input name="product[sku][]">
<input name="product[sku][]">
$_POST['user']['name']
$_POST['user']['email']
$_POST['product']['sku'][0]
$_POST['product']['sku'][1]
Vorteil: Automatische Strukturierung, kein manuelles Parsing, weniger Code.
Gold-Regel: Immer $_POST validieren bevor du es nutzt!
MySQL hat Superkräfte mit JSON! Statt mühsame Normalisierung brauchst du nur:
INSERT INTO users (profile) VALUES ('{"name":"John","age":30}');
SELECT profile->>'$.name' AS name FROM users;
SELECT JSON_SEARCH(profile, 'one', 'John') FROM users;
UPDATE users SET profile = JSON_SET(profile, '$.age', 31) WHERE id = 1;
Vorteile: Kein Schema-Overhead, direkter Zugriff auf verschachtelte Daten, perfekt für halbstrukturierte Daten.
Tipp: Indexiere JSON-Pfade mit GENERATED COLUMNS für bessere Performance!
Hinweis zu MariaDB
⚠️ Achtung: Die moderne JSON-Syntax (-> und ->>) funktioniert in MariaDB noch nicht. Verwende stattdessen JSON_VALUE() für einen ähnlichen Effekt:
SELECT JSON_VALUE(profile, '$.name') AS name FROM users;
JSON_VALUE() ist zukunftssicher und wird auch in neueren MySQL-Versionen unterstützt.