ArtiFrame Dokumentation
Ein Framework für skalierbare native PHP-Projekte, verwaltet ohne externe Abhängigkeiten, mit strikten Regeln und einer leistungsstarken CLI. Keine Abhängigkeiten, befreit vom Composer-Paket-Chaos – ein leichtes und schnelles Ökosystem, das es Ihnen ermöglicht, sich auf Ihre Geschäftslogik zu konzentrieren.
Einführung & Philosophie
ArtiFrame basiert auf dem Prinzip "Convention over Configuration" (Konvention vor Konfiguration). Die Stärke eines Frameworks ergibt sich nicht aus dem Reichtum der angebotenen Tools, sondern aus der Konsistenz der aufgebauten Ordnung.
Zero Overhead
Null Abhängigkeit von Composer-Paketen, Framework-Kernen oder Drittanbieter-Bibliotheken. Jede Zeile Code gehört Ihnen – ohne Bloat, frei von unnötigen Abstraktionsschichten.
Sicherheit an erster Stelle
XSS-Schutz, CSRF-Validierung, Bereinigung gegen SQL-Injection und HTTP-Methoden-Kontrolle sind standardmäßig integriert. Sicherheit ist keine Option, sondern ein Standard.
Strikter Regelsatz
Ein Junior-Entwickler, der neu im Projekt ist, versteht die data-js-Architektur und die Verzeichnisstruktur in wenigen Minuten. Team-Konsistenz ist auf Framework-Ebene garantiert.
CLI-Fokussiert
Kein manuelles Erstellen von View-, API- oder Class-Dateien. Die CLI generiert sie aus Stub-Dateien, richtet Asset-Verbindungen ein und hält das Projekt konsistent.
Installation
ArtiFrame CLI wird als globales PHP-Tool installiert. Es wird einmal installiert und in jedem Projekt verwendet.
1. CLI-Tool global installieren
# Globale Installation über NPM
npm install -g @artilingo/artiframe-cli
# Installation überprüfen
artiframe
2. Interaktive Shell
Geben Sie einfach artiframe im Terminal ein und drücken Sie Eingabe. Die CLI schließt sich nicht; es öffnet sich eine interaktive Shell, die kontinuierlich auf Befehle hört:
==================================================
ArtiFrame CLI Interactive Shell v1.0.0
==================================================
Type 'help' for commands, or 'exit' to quit.
artiframe>
3. Neues Projekt starten
artiframe> new benim-projem
Dieser Befehl erstellt das Verzeichnis benim-projem/ und kopiert die gesamte Skelettstruktur hinein: app/, bin/, config/, public/, src/, .env.example und die erste index.php-Seite.
4. Umgebungseinstellungen
cp .env.example .env
Öffnen Sie Ihre .env-Datei und füllen Sie Datenbank- und Anwendungsinformationen aus. Diese Datei wird niemals in die Versionskontrolle aufgenommen.
/public/ um. Andere Verzeichnisse dürfen niemals nach außen hin zugänglich sein.
Verzeichnisstruktur
Die beim Projektstart erstellte Architektur sorgt für eine klare Trennung der Zuständigkeiten (SoC).
bin/ sind der Kern des Frameworks. Projektspezifische Geschäftslogik wird hier nicht hinzugefügt. Klassen und Dienste gehören unter src/, Infrastrukturkomponenten unter app/.
Bootstrapper-Architektur Kritisch
ArtiFrame verwendet zwei völlig unabhängige Bootstrapper. Diese Architektur verhindert von Grund auf HTML-Header-Probleme und Sicherheitslücken.
View ViewControl.php
Wird für HTML-Seiten (View-Dateien) verwendet. Startet eine Session und lädt ViewMethod.
// public/profil.php — GANZ OBEN, bevor HTML ausgegeben wird
<?php
require_once $_SERVER['DOCUMENT_ROOT']
. '/../app/ViewControl.php';
use Bin\ViewMethod;
?>
<!DOCTYPE html>
...
API ApiControl.php
Wird für API-Endpunkt-Dateien verwendet. Setzt den JSON-Header, führt HTTP-Methodenkontrolle durch und lädt SystemMethod.
// public/api/kullanici/getir.php
<?php
// $allowedMethods MUSS VOR require definiert werden
$allowedMethods = ['GET'];
require_once $_SERVER['DOCUMENT_ROOT']
. '/../app/ApiControl.php';
use Bin\SystemMethod;
Regelsatz Standard
Regel 1: data-js-Architektur Kritisch
JavaScript-Events dürfen niemals über eine class oder id abgehört werden. Diese sind visuelle / Styling-Identifikatoren. Alle JS-Interaktionen werden über das Attribut data-js verwaltet. Wenn CSS eine Klasse löscht, stürzt JavaScript niemals ab.
<!-- ❌ Anti-Pattern — nicht unterstützt -->
<button id="submitBtn" class="btn">Senden</button>
// JS: document.getElementById('submitBtn').addEventListener(...)
<!-- ✅ ArtiFrame-Standard -->
<button class="btn btn-primary" data-js="login-submit">Senden</button>
// JS: document.querySelector('[data-js="login-submit"]').addEventListener(...)
Regel 2: Theme-Architektur
Dunkel-/Hell-Modus und Themes werden über die Attribute data-theme und data-mode des <html>-Tags verwaltet. Body-Klassen werden nicht verwendet.
<!-- HTML-Eröffnungstag aus der view.stub-Vorlage -->
<html lang="de" data-theme="default" data-mode="light">
/* Theme-Definition in app.css */
html[data-theme="default"][data-mode="dark"] {
--bg-color: #0b0c0e;
--text-main: #ffffff;
}
html[data-theme="default"][data-mode="light"] {
--bg-color: #ffffff;
--text-main: #111111;
}
Regel 3: Sicherer Datenfluss
Alle aus der Datenbank stammenden Daten werden mit display() umschlossen, bevor sie im DOM ausgegeben werden. Alle an die API gesendeten Daten werden mit sanitizeString() oder sanitizeInt() bereinigt, bevor sie verarbeitet werden.
Regel 4: Fehlerbehandlung mit APP_ENV
Der Wert von APP_ENV in der .env-Datei bestimmt die Sichtbarkeit von Fehlern. In der Production-Umgebung werden dem Benutzer keine Fehlermeldungen angezeigt.
// config/app-version.php
define('APP_ENV', (int)$_ENV['APP_ENV']); // 1=Debug, 0=Production
if (APP_ENV === 1) {
ini_set('display_errors', 1);
error_reporting(E_ALL);
} else {
ini_set('display_errors', 0);
}
CLI-Ökosystem
Die ArtiFrame CLI öffnet eine interaktive Shell, wenn artiframe im Terminal eingegeben wird. Alle Befehle werden in dieser Shell ausgeführt. Befehle können auch einmalig ausgeführt werden.
# Interaktiver Modus (empfohlen)
artiframe
artiframe> make:view admin/kullanicilar.php
# Einmaliger Modus
artiframe make:view admin/kullanicilar.php
new CLI-Befehl
Erstellt ein neues ArtiFrame-Projekt. Generiert die gesamte Verzeichnisskelettstruktur, Bootstrapper-Dateien und die erste Index-Seite.
artiframe> new proje-adi
Erstellte Struktur:
make:view CLI-Befehl
Erstellt eine neue Seiten- (View-) Datei und deren spezifische CSS/JS-Assets. Assets werden automatisch mit der Seite verknüpft und Cache-Busting wird mit ?v=APP_VERSION angewendet.
artiframe> make:view admin/kullanicilar.php
Erstellte Dateien:
✔ public/admin/kullanicilar.php
✔ public/assets/css/admin/kullanicilar.css
✔ public/assets/js/admin/kullanicilar.js
Die erstellte View-Datei enthält am Anfang ein eingebundenes ViewControl, hinzugefügte Head/Header/Footer-Includes und per Cache-Busting verknüpfte CSS/JS-Links.
make:api CLI-Befehl
Erstellt eine API-Endpunkt-Dateien durch Auswahl aus zwei verschiedenen Vorlagen. Jede neue API-Datei enthält bereits die Variable $allowedMethods und die Einbindung von ApiControl.php.
standart — Einzelaktions-API
Für Endpunkte, die eine einzelne Aufgabe ausführen (Login, Senden, Löschen). Die Geschäftslogik wird direkt geschrieben.
artiframe> make:api standart api/auth/giris.php
<?php
$allowedMethods = ['POST']; // Nur POST akzeptieren
require_once $_SERVER['DOCUMENT_ROOT'] . '/../app/ApiControl.php';
use Bin\SystemMethod;
// Geschäftslogik hier...
jsonResponse(['status' => 'success'], 200);
switch-case — Multiaktions-API
Eine Struktur, die CRUD-Operationen für ein Modul in einem einzigen Endpunkt verwaltet. Die auszuführende Aktion wird durch den Parameter action bestimmt.
artiframe> make:api switch-case api/kullanici/yonet.php
<?php
$allowedMethods = ['POST'];
require_once $_SERVER['DOCUMENT_ROOT'] . '/../app/ApiControl.php';
use Bin\SystemMethod;
$action = sanitizeString($_POST['action'] ?? '');
switch ($action) {
case 'create':
jsonResponse(['status' => 'success', 'message' => 'Erstellt.'], 200);
break;
case 'update':
jsonResponse(['status' => 'success', 'message' => 'Aktualisiert.'], 200);
break;
case 'delete':
jsonResponse(['status' => 'success', 'message' => 'Gelöscht.'], 200);
break;
default:
jsonResponse(['status' => 'error', 'message' => 'Ungültige Aktion.'], 400);
}
make:class CLI-Befehl
Erstellt eine neue PHP-Klassendatei mit vorbereitetem Namespace- und Klassen-Boilerplate.
artiframe> make:class src/Service/EmailService.php
Wenn das Klassenverzeichnis nicht angegeben wird, gibt der Befehl einen Fehler zurück:
artiframe> make:class
❌ Fehler: Das Verzeichnis für die Klasse muss angegeben werden! (z.B. /app oder /src)
Example: /src/Service/PaymentService
version CLI-Befehl
Aktualisiert die Versionsnummer in config/app-version.php gemäß den Regeln der semantischen Versionierung (SemVer). Versionsformat: MAJOR.MINOR.PATCH
| Befehl | Beschreibung | Beispiel |
|---|---|---|
| version upgrade patch | Fehlerbehebung, kleine Verbesserung | 1.2.3 → 1.2.4 |
| version upgrade minor | Abwärtskompatible neue Funktion | 1.2.3 → 1.3.0 |
| version upgrade major | Breaking Change (brechende Änderung) | 1.2.3 → 2.0.0 |
| version downgrade patch | Letzten Patch rückgängig machen | 1.2.4 → 1.2.3 |
| version downgrade minor | Letztes Minor-Release rückgängig machen | 1.3.0 → 1.2.0 |
| version downgrade major | Letztes Major-Release rückgängig machen | 2.0.0 → 1.0.0 |
artiframe> version upgrade minor
✔ Version von 1.2.0 auf 1.3.0 aktualisiert.
View Helpers — display() ViewMethod
Wendet obligatorischen XSS-Schutz an, wenn Daten aus der Datenbank oder Benutzereingaben im DOM ausgegeben werden. Gibt bei Null- oder leeren Werten einen Standardwert zurück, anstatt einen Fehler zu werfen.
string display(mixed $data, string $default = '')$defaultDer zweite Parameter (
$default) ist der Fallback-Text, der auf dem Bildschirm ausgegeben wird, wenn die aus der Datenbank stammenden Daten leer sind (null, false, leere Zeichenkette). Wenn Sie beispielsweise display($isim, 'Unbenannter Benutzer') für einen Benutzer verwenden, dessen Name nicht eingegeben wurde, bietet Ihre Seite sinnvolle Inhalte statt unschöner Lücken.
<!-- ❌ Unsicher — erzeugt XSS-Lücke -->
<h1><?= $user['isim'] ?></h1>
<!-- ✅ Sichere ArtiFrame-Verwendung -->
<h1><?= display($user['isim'], 'Unbenannter Benutzer') ?></h1>
View Helpers — escapeUrl() ViewMethod
Wird verwendet, wenn von Benutzern erhaltene Links (z. B. Profil-Websites) in <a href="..."> oder <img src="..."> ausgegeben werden. Macht gefährliche Payloads wie javascript:alert(1) (Stored XSS) unschädlich und verhindert die Ausführung von Code über den Link.
<!-- ❌ Unsicher — JS-Code kann über URL eingeschleust werden -->
<a href="<?= $user['website'] ?>">Website besuchen</a>
<!-- ✅ Sichere ArtiFrame-Verwendung -->
<a href="<?= escapeUrl($user['website']) ?>">Website besuchen</a>
View Helpers — csrfField() ViewMethod
Fügt HTML-Formularen ein verstecktes Token-Feld zum Schutz vor CSRF-Angriffen hinzu. Muss auf jeder Seite mit einem POST-Formular verwendet werden.
string csrfField()<form action="/api/kaydet.php" method="POST">
<?= csrfField() ?> <!-- Aus Sicherheitsgründen erforderlich -->
<input type="text" name="ad">
<button type="submit" data-js="kaydet-btn">Speichern</button>
</form>
View Helpers — Datumsfunktionen ViewMethod
Verarbeitet Datums-Strings im Format Y-m-d H:i:s oder UNIX-Timestamp-Werte aus der Datenbank. Alle Textausgaben werden je nach Sprache (tr, en, de, fr, es) lokalisiert.
| Funktion | Ausgabebeispiel | Beschreibung |
|---|---|---|
| day($date) | 24 | Nur Tag |
| month($date) | 07 | Nur Monat (Zahl) |
| year($date) | 2026 | Jahr |
| timeOnly($date) | 14:30 | Stunde:Minute |
| fulldate($date) | 24.07.2026 | Vollständiges Datum |
| formatDate($date, $format) | 24.07.2026 14:30 | Benutzerdefiniertes Format |
| monthName($date, $lang) | Temmuz / July / Juli | Monatsname (je nach Sprache) |
| fulldateName($date, $lang) | 24 Temmuz 2026 / July 24, 2026 | Vollständiges Datum (mit Monatsname, je nach Sprache) |
| timeAgo($date, $lang) | 5 dakika önce / 5 minutes ago | Social-Media-Stil (je nach Sprache) |
$format und $lang
$format(Format): Wird nur mit der FunktionformatDate()verwendet. Akzeptiert Standard-PHP-Datumsbuchstaben. (Z. B.:'d/m/Y'➔ 24/07/2026 oder'H:i'➔ 15:30). Ermöglicht das Erstellen eigener Datums-Muster, wenn andere vordefinierte Funktionen (day, year usw.) unzureichend sind.$lang(Sprache): Wird in Funktionen verwendet, die Text in ihrer Ausgabe enthalten (Monatsname, Wort "vor"). Wenn Sie diesen Parameter leer lassen, arbeitet das System standardmäßig mit'tr'(Türkisch). Wenn Sie ein mehrsprachiges Projekt erstellen, reicht es aus, den Sprachcode (tr, en, de, fr, es) als zweiten Parameter einzugeben. (Z. B.:timeAgo($tarih, 'en')➔ 5 minutes ago)
<!-- Angenommen, es ist ein UNIX-Timestamp von der Funktion time() -->
<?php $tarih = time(); ?>
<!-- Vollständiges Datum je nach Sprache -->
<span><?= fulldateName($tarih, 'tr') ?></span>
<!-- Ausgabe: 24 Temmuz 2026 -->
<!-- Zeitdarstellung im Social-Media-Stil -->
<span><?= timeAgo($tarih, 'en') ?></span>
<!-- Ausgabe: 5 minutes ago -->
<span><?= timeAgo($tarih, 'de') ?></span>
<!-- Ausgabe: vor 5 Minuten -->
View Helpers — Textformatierung ViewMethod
truncate($text, $length, $append)
string truncate(string $text, int $length = 100, string $append = '...')
Kürzt lange Texte (z. B. Blog-Zusammenfassungen) auf das gewünschte Zeichenlimit, ohne das letzte Wort zu zerschneiden, und fügt das angegebene Suffix am Ende hinzu.
<p><?= truncate($post['icerik'], 160, '...') ?></p>
View Helpers — money() ViewMethod
Formatiert den Betrag mit dem Währungssymbol. Der Währungscode (ISO) bestimmt automatisch die Platzierung des Symbols. Die Standardwährung ist usd.
| Code | Ausgabe | Währung |
|---|---|---|
| usd | $1.250,00 | US-Dollar |
| eur | 1.250,00 € | Euro |
| try / tl | 1.250,00 ₺ | Türkische Lira |
| gbp | £1.250,00 | Britisches Pfund |
| jpy | 1.250,00 ¥ | Japanischer Yen |
| inr | 1.250,00 ₹ | Indische Rupie |
| rub | 1.250,00 ₽ | Russischer Rubel |
| krw | 1.250,00 ₩ | Südkoreanischer Won |
| brl | R$1.250,00 | Brasilianischer Real |
| aed | 1.250,00 د.إ | VAE-Dirham |
<span><?= money($urun['fiyat'], 'try') ?></span>
<!-- Ausgabe: 1.250,00 ₺ -->
<span><?= money($urun['fiyat'], 'usd') ?></span>
<!-- Ausgabe: $1.250,00 -->
System Helpers — jsonResponse() SystemMethod
Setzt den JSON-Header, übergibt den HTTP-Statuscode, gibt die Ausgabe sicher mit json_encode aus und beendet anschließend das Skript. Alle API-Antworten müssen über diese Funktion gegeben werden.
void jsonResponse(array $data, int $statusCode = 200)// Erfolgreiche Antwort
jsonResponse(['status' => 'success', 'data' => $kullanici], 200);
// Fehlerantwort
jsonResponse(['status' => 'error', 'message' => 'Unbefugter Zugriff.'], 401);
System Helpers — verifyCsrf() SystemMethod
Überprüft, ob die eingehende POST-Anfrage von einem legitimen Formular stammt. Gibt false zurück, wenn csrfField() nicht im Formular enthalten ist oder das Token ungültig ist.
if (!verifyCsrf($_POST['csrf_token'] ?? '')) {
jsonResponse(['status' => 'error', 'message' => 'Ungültiges CSRF-Token.'], 403);
}
System Helpers — Bereinigungsfunktionen SystemMethod
| Funktion | Beschreibung |
|---|---|
| sanitizeInt($value) | Entfernt alle Buchstaben, Symbole und Kommas und lässt nur Ganzzahlen übrig. Wird für IDs oder Limits verwendet. |
| sanitizeFloat($value) | Bereinigt alles außer Dezimalzahlen. Wird für Geldbeträge oder Metriken verwendet. |
| sanitizeString($value) | Eliminiert alle HTML- und PHP-Tags (<script>, <iframe> usw.), um XSS- und ähnliche Angriffe zu verhindern. Hinterlässt sicheren Klartext. |
| sanitizeEmail($email) | Filtert alle ungültigen und gefährlichen Zeichen heraus, die nicht dem E-Mail-Format entsprechen. |
$id = sanitizeInt($_POST['id'] ?? 0);
$ad = sanitizeString($_POST['ad'] ?? '');
$email = sanitizeEmail($_POST['email'] ?? '');
System Helpers — HTTP & Anfragen SystemMethod
| Funktion | Beschreibung |
|---|---|
| isPost() | Anfrage POST? |
| isGet() | Anfrage GET? |
| isPut() | Anfrage PUT? |
| isDelete() | Anfrage DELETE? |
| isAjax() | Anfrage über XHR/Fetch-API? |
| getClientIp() | Echte IP-Adresse (mit Cloudflare- & Proxy-Unterstützung) |
| redirect($url) | Leitet zur angegebenen URL weiter und beendet das Skript |
// Direktzugriff über den Browser verhindern
if (!isAjax()) {
jsonResponse(['error' => 'Direkter Zugriff eingeschränkt.'], 403);
}
// Weiterleiten, wenn keine Sitzung vorhanden ist
if (!isset($_SESSION['user'])) {
redirect('/giris.php');
}
$ip = getClientIp(); // Echte IP auch hinter Cloudflare
System Helpers — Sicherheit SystemMethod
| Funktion | Beschreibung |
|---|---|
| generateCsrf() | Generiert ein neues CSRF-Token und speichert es in der Session |
| generateToken($length) | Kryptografisch sichere zufällige Hex-Zeichenkette (Passwort-Reset, API-Key usw.) |
| hashPassword($password) | Hasht das Passwort mit bcrypt |
| verifyPassword($password, $hash) | Überprüft das Passwort anhand des Hashes |
// Sichere Token-Generierung (API-Key, E-Mail-Bestätigungslink usw.)
$token = generateToken(32);
// ⚠️ Hinweis: 32 Byte Daten werden generiert, aber da sie ins Hex-Format (Basis 16)
// umgewandelt werden, ist die Ausgabe genau doppelt so lang, d. h. eine 64 Zeichen lange Zeichenkette.
// Passwortspeicherung
$hash = hashPassword($_POST['sifre']);
// Passwortüberprüfung
if (!verifyPassword($_POST['sifre'], $kullanici['sifre_hash'])) {
jsonResponse(['error' => 'Falsches Passwort.'], 401);
}
API — HTTP-Methoden-Kontrolle ApiControl
Das Array $allowedMethods muss vor dem Einbinden von ApiControl definiert werden. ApiControl liest dieses Array und lehnt Anfragen von nicht zugelassenen Methoden automatisch mit 405 Method Not Allowed ab.
// Ein Endpunkt, der nur GET und POST akzeptiert
$allowedMethods = ['GET', 'POST'];
require_once ... . '/../app/ApiControl.php';
// Ein Endpunkt, der nur DELETE akzeptiert
$allowedMethods = ['DELETE'];
require_once ... . '/../app/ApiControl.php';
Preflight-OPTIONS-Anfragen geben für CORS automatisch 200 zurück und das Skript beendet sich — kein manueller Eingriff erforderlich.
API — CORS ApiControl (Auskommentiert)
Wenn Sie den API-Zugriff von anderen Domains oder mobilen Anwendungen erlauben möchten, aktivieren Sie den CORS-Block in ApiControl.php.
// In ApiControl.php — wird durch Einkommentieren aktiviert
header("Access-Control-Allow-Origin: https://sizin-domaininiz.com");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
* (alle Domains). Geben Sie die Domainadresse explizit an.
API — Rate Limiting ApiControl (Auskommentiert)
Sie können den Rate-Limiting-Block in ApiControl.php aktivieren, um zu verhindern, dass bösartige Benutzer oder Bots die API überfluten. Standardregel: 60 Anfragen in 60 Sekunden / IP.
\Src\Service\RedisService::getInstance() unter Verwendung von Redis. Stellen Sie vor dem Aktivieren dieses Codeblocks sicher, dass Sie einen Redis-Dienst in Ihrem Projekt eingerichtet und eine Verbindungsklasse unter src/Service/ erstellt haben.
// Wenn von derselben IP mehr als 60 Anfragen in 60 Sekunden eingehen:
http_response_code(429); // Too Many Requests
echo json_encode(['error' => 'Zu viele Anfragen. Bitte warten Sie.']);
Vollständiger Arbeitsablauf: Kontaktformular (End-to-End)
Schauen wir uns Schritt für Schritt an, wie Sie eine Kontaktformular-Funktion nach ArtiFrame-Standards von Anfang bis Ende entwickeln.
-
Erstellen Sie die View-Datei
artiframe> make:view iletisim.php ✔ public/iletisim.php ✔ public/assets/css/iletisim.css ✔ public/assets/js/iletisim.js -
Erstellen Sie den API-Endpunkt
artiframe> make:api standart api/iletisim/gonder.php -
Codieren Sie das HTML-Formular
Fügen Sie das folgende Formular zur Dateipublic/iletisim.phphinzu:<form action="/api/iletisim/gonder.php" method="POST"> <?= csrfField() ?> <input type="text" name="ad" placeholder="Ihr Name"> <input type="email" name="eposta" placeholder="E-Mail"> <textarea name="mesaj" placeholder="Ihre Nachricht"></textarea> <button type="submit" data-js="iletisim-gonder">Senden</button> </form> -
Verbinden Sie die Fetch-API mit JavaScript
In der Dateipublic/assets/js/iletisim.js:document.querySelector('[data-js="iletisim-gonder"]').addEventListener('click', async (e) => { e.preventDefault(); const formData = new FormData(e.target.closest('form')); const res = await fetch('/api/iletisim/gonder.php', { method: 'POST', body: formData }); const data = await res.json(); console.log(data); }); -
Vervollständigen Sie die Server-Logik (Backend)
In der Dateipublic/api/iletisim/gonder.php:<?php $allowedMethods = ['POST']; require_once $_SERVER['DOCUMENT_ROOT'] . '/../app/ApiControl.php'; use Bin\SystemMethod; // 1. CSRF-Validierung if (!verifyCsrf($_POST['csrf_token'] ?? '')) { jsonResponse(['error' => 'Ungültiges Token.'], 403); } // 2. Daten bereinigen $ad = sanitizeString($_POST['ad'] ?? ''); $eposta = sanitizeEmail($_POST['eposta'] ?? ''); $mesaj = sanitizeString($_POST['mesaj'] ?? ''); // 3. Geschäftslogik (E-Mail senden, in DB speichern usw.) // ... // 4. Antwort geben jsonResponse(['status' => 'success', 'message' => 'Ihre Nachricht wurde empfangen.'], 200);