Giriş & Felsefe

ArtiFrame, "Convention over Configuration" (Yapılandırmadan Önce Kurallar) ilkesine dayanır. Bir framework'ün gücü, sunduğu araçların zenginliğinden değil, kurduğu düzenin tutarlılığından gelir.

Zero Overhead

Üçüncü parti Composer paketlerine veya kütüphanelere sıfır bağımlılık. Composer sadece projenin kendi PSR-4 otomatik sınıf yükleme (autoloading) mekanizmasını çalıştırmak için kullanılır. Her satır kod sizindir; gereksiz soyutlama katmanlarından arınmış.

Güvenlik Önce Gelir

XSS koruması, CSRF doğrulama, SQL injection'a karşı sanitizasyon ve HTTP metod kontrolü varsayılan olarak yerleşik gelir. Güvenlik bir seçenek değil, bir standart.

Katı Kural Seti

Projeye yeni dahil olan bir junior geliştirici, data-js mimarisini ve dizin yapısını dakikalar içinde kavrar. Takım uyumu, framework seviyesinde garanti altındadır.

CLI Öncelikli

View, API, class dosyalarını elle oluşturmak yok. CLI, stub dosyalarından üretir, asset bağlantılarını kurar ve projeyi tutarlı tutar.

ℹ️ AGPLv3 Lisans: ArtiFrame açık kaynaklıdır. Ürettiğiniz türev çalışmalar, kaynak kodun açık kalması koşuluyla özgürce kullanılabilir. Telif bildirimi kaldırılamaz.

Kurulum

ArtiFrame CLI, global bir PHP aracı olarak kurulur. Bir kez kurulur, her projede kullanılır.

1. CLI Aracını Global Olarak Kur

# NPM üzerinden global kurulum
npm install -g @artilingo/artiframe-cli

# Kurulumu doğrula
artiframe

2. İnteraktif Kabuk

Terminale sadece artiframe yazıp Enter'a basın. CLI kapanmaz; sürekli komut dinleyen interaktif bir kabuk açılır:

==================================================
        ArtiFrame CLI Interactive Shell v1.0.0
==================================================
Type 'help' for commands, or 'exit' to quit.

artiframe> 

3. Yeni Proje Başlat

artiframe> new benim-projem

Bu komut benim-projem/ dizinini oluşturur ve tüm iskelet yapıyı içine kopyalar: app/, bin/, config/, public/, src/, .env.example ve ilk index.php sayfası.

4. Ortam Ayarları

cp .env.example .env

.env dosyanızı açıp veritabanı ve uygulama bilgilerini doldurun. Bu dosya asla versiyon kontrolüne girmez.

⚠️ Web Sunucusu Ayarı: Apache/Nginx'in document root'unu /public/ klasörüne yönlendirin. Diğer dizinler dışarıya asla açık olmamalıdır.

Dizin Yapısı

Proje başlatıldığında oluşan mimari; sorumlulukların (SoC) net biçimde ayrılmasını sağlar.

proje-adi/ ├── app/ # Altyapı katmanı │ ├── ViewControl.php # View (HTML sayfa) bootstrapper'ı │ ├── ApiControl.php # API endpoint bootstrapper'ı │ ├── Database.php # PDO tabanlı veritabanı bağlantısı │ ├── DotEnv.php # .env okuyucu │ └── R2Manager.php # Cloudflare R2 dosya yöneticisi │ ├── bin/ # ⚠️ Çekirdek sistem — doğrudan düzenlemeyin │ ├── SystemMethod.php # API/Backend global yardımcıları │ ├── ViewMethod.php # View/Frontend global yardımcıları │ └── stubs/ # CLI'ın kullandığı şablon dosyaları │ ├── view.stub │ ├── api-standart.stub │ ├── api-switch-case.stub │ └── class.stub │ ├── config/ # Yapılandırma dosyaları │ └── app-version.php # APP_VERSION ve APP_ENV sabitleri │ ├── public/ # ← Web sunucusunun tek açık dizini │ ├── assets/ │ │ ├── css/ # View'a özel CSS dosyaları │ │ └── js/ # View'a özel JS dosyaları │ ├── includes/ # Paylaşılan bileşenler │ │ ├── head.php │ │ ├── header.php │ │ └── footer.php │ ├── api/ # API endpoint dosyaları │ └── index.php # Ana sayfa │ ├── src/ # İş mantığı ve sınıflar ├── .env # Ortam değişkenleri (git'e girmiyor) ├── .env.example # Şablon — git'e giriyor └── kilavuz.html # Bu doküman
🚫 bin/ dizinine dokunmayın: bin/ içindeki dosyalar çerçevenin çekirdeğidir. Projeye özel iş mantığı buraya eklenmez. Ekleyeceğiniz sınıflar ve servisler src/ altında, altyapı bileşenleri app/ altında yer alır.

Bootstrapper Mimarisi Kritik

ArtiFrame, iki tamamen bağımsız bootstrapper kullanır. Bu mimari, HTML header sorunlarını ve güvenlik açıklarını kökten önler.

View ViewControl.php

HTML sayfaları (view dosyaları) için kullanılır. Session başlatır, ViewMethod'u yükler.

// public/profil.php — EN ÜSTE, HTML basmadan önce
<?php
require_once $_SERVER['DOCUMENT_ROOT']
    . '/../app/ViewControl.php';
use Bin\ViewMethod;
?>
<!DOCTYPE html>
...

API ApiControl.php

API endpoint dosyaları için kullanılır. JSON header'ı ayarlar, HTTP metod kontrolü yapar, SystemMethod'u yükler.

// public/api/kullanici/getir.php
<?php
// $allowedMethods MUTLAKA require'dan önce tanımlanmalı
$allowedMethods = ['GET'];

require_once $_SERVER['DOCUMENT_ROOT']
    . '/../app/ApiControl.php';

use Bin\SystemMethod;
🚫 ViewControl ve ApiControl asla karıştırılmaz: ViewControl bir API dosyasına require edilirse JSON header yerine HTML header dönebilir ve tüm API yanıtı bozulur. ApiControl bir HTML sayfasına require edilirse session başlamaz ve sayfa kırılır.

Kural Seti Standart

Kural 1: data-js Mimarisi Kritik

JavaScript olayları asla bir class veya id üzerinden dinlenemez. Bunlar görsel / stil kimliklerdir. Tüm JS etkileşimleri data-js özniteliği ile yönetilir. CSS bir class sildiğinde JavaScript asla çökmez.

<!-- ❌ Anti-Pattern — desteklenmez -->
<button id="submitBtn" class="btn">Gönder</button>
// JS: document.getElementById('submitBtn').addEventListener(...)

<!-- ✅ ArtiFrame Standardı -->
<button class="btn btn-primary" data-js="login-submit">Gönder</button>
// JS: document.querySelector('[data-js="login-submit"]').addEventListener(...)

Kural 2: Tema Mimarisi

Koyu/Açık mod ve temalar, <html> etiketinin data-theme ve data-mode öznitelikleri üzerinden yönetilir. Gövde sınıfları kullanılmaz.

<!-- view.stub şablonundan gelen HTML açılış etiketi -->
<html lang="tr" data-theme="default" data-mode="light">

/* app.css içindeki tema tanımı */
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;
}

Kural 3: Güvenli Veri Akışı

Veritabanından gelen her veri, DOM'a basılmadan önce display() ile sarılır. API'ye gelen her veri, işlenmeden önce sanitizeString() veya sanitizeInt() ile temizlenir.

Kural 4: APP_ENV ile Hata Yönetimi

.env dosyasındaki APP_ENV değeri hata görünürlüğünü belirler. Production'da kullanıcıya hata mesajı gösterilmez.

// 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 Ekosistemi

ArtiFrame CLI, terminale artiframe yazıldığında interaktif bir kabuk açar. Tüm komutlar bu kabuk içinde çalışır. Komutlar tek seferlik de çalıştırılabilir.

# İnteraktif mod (önerilen)
artiframe
artiframe> make:view admin/kullanicilar.php

# Tek seferlik mod
artiframe make:view admin/kullanicilar.php

new CLI Komutu

Yeni bir ArtiFrame projesi oluşturur. Tüm dizin iskeletini, bootstrapper dosyalarını ve ilk index sayfasını oluşturur.

artiframe> new proje-adi

Oluşturulan yapı:

proje-adi/ ├── app/ (ViewControl.php, ApiControl.php, Database.php, DotEnv.php) ├── bin/ (SystemMethod.php, ViewMethod.php, stubs/) ├── config/ (app-version.php) ├── public/ (index.php, assets/, includes/, api/) ├── src/ ├── .env.example └── kilavuz.html

make:view CLI Komutu

Yeni bir sayfa (view) dosyası ve ona özel CSS/JS varlıklarını oluşturur. Varlıklar otomatik olarak sayfaya bağlanır ve ?v=APP_VERSION ile önbellek temizleme (cache-busting) uygulanır.

artiframe> make:view admin/kullanicilar.php

Oluşturulan dosyalar:

✔  public/admin/kullanicilar.php
✔  public/assets/css/admin/kullanicilar.css
✔  public/assets/js/admin/kullanicilar.js

Oluşturulan view dosyasının başında ViewControl require edilmiş, head/header/footer include'ları eklenmiş ve CSS/JS linkleri cache-busting ile bağlanmış olarak gelir.

make:api CLI Komutu

İki farklı şablondan birini seçerek API endpoint dosyası oluşturur. Her yeni API dosyasında $allowedMethods değişkeni ve ApiControl.php require'ı hazır gelir.

standart — Tekil Eylemli API

Tek bir iş yapan (login, gönder, sil) endpoint'ler için. Doğrudan iş mantığı yazılır.

artiframe> make:api standart api/auth/giris.php
<?php
$allowedMethods = ['POST']; // Sadece POST kabul et
require_once $_SERVER['DOCUMENT_ROOT'] . '/../app/ApiControl.php';

use Bin\SystemMethod;

// İş mantığı buraya...
jsonResponse(['status' => 'success'], 200);

switch-case — Çok Eylemli API

Bir modül için CRUD işlemlerini tek endpoint'te yöneten yapı. action parametresiyle hangi işlemin yapılacağı belirlenir.

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' => 'Oluşturuldu.'], 200);
        break;
    case 'update':
        jsonResponse(['status' => 'success', 'message' => 'Güncellendi.'], 200);
        break;
    case 'delete':
        jsonResponse(['status' => 'success', 'message' => 'Silindi.'], 200);
        break;
    default:
        jsonResponse(['status' => 'error', 'message' => 'Geçersiz action.'], 400);
}

make:class CLI Komutu

Namespace ve sınıf boilerplate'i hazır gelecek şekilde yeni bir PHP sınıf dosyası oluşturur.

artiframe> make:class src/Service/EmailService.php

Eğer sınıfın dizini belirtilmezse komut hata döndürür:

artiframe> make:class
❌ Hata: Sınıfın oluşturulacağı dizin belirtilmeli! (örn: /app veya /src)
Example: /src/Service/PaymentService

version CLI Komutu

config/app-version.php içindeki sürüm numarasını semantik versiyonlama (SemVer) kurallarına göre günceller. Sürüm formatı: MAJOR.MINOR.PATCH

KomutAçıklamaÖrnek
version upgrade patchHata düzeltme, küçük iyileştirme1.2.3 → 1.2.4
version upgrade minorGeriye uyumlu yeni özellik1.2.3 → 1.3.0
version upgrade majorKırıcı değişiklik1.2.3 → 2.0.0
version downgrade patchSon patch'i geri al1.2.4 → 1.2.3
version downgrade minorSon minor'ı geri al1.3.0 → 1.2.0
version downgrade majorSon major'ı geri al2.0.0 → 1.0.0
artiframe> version upgrade minor
✔  Sürüm 1.2.0 → 1.3.0 olarak güncellendi.

View Helpers — display() ViewMethod

Veritabanından veya kullanıcı girdisinden gelen veriyi DOM'a basarken zorunlu XSS koruması uygular. Null veya boş değerlerde hata fırlatmak yerine varsayılan değer döner.

string display(mixed $data, string $default = '')
💡 İpucu: $default Kullanımı
İkinci parametre ($default), veri tabanından gelen veri boş (null, false, empty string) olduğunda ekrana basılacak kurtarıcı metindir. Örneğin isim verisi girilmemiş bir kullanıcı için display($isim, 'İsimsiz') kullanırsanız sayfanız çirkin boşluklar yerine anlamlı bir içerik sunar.
<!-- ❌ Güvensiz — XSS açığı oluşturur -->
<h1><?= $user['isim'] ?></h1>

<!-- ✅ ArtiFrame Güvenli Kullanımı -->
<h1><?= display($user['isim'], 'İsimsiz Kullanıcı') ?></h1>

View Helpers — escapeUrl() ViewMethod

Kullanıcılardan alınan linkleri (örneğin profil web siteleri) <a href="..."> veya <img src="..."> içlerine basarken kullanılır. javascript:alert(1) gibi (Stored XSS) tehlikeli payload'ları zararsız hale getirerek bağlantı üzerinden kod çalıştırılmasını önler.

string escapeUrl(string $url)
<!-- ❌ Güvensiz — URL üzerinden JS kodu sızdırılabilir -->
<a href="<?= $user['website'] ?>">Siteyi Ziyaret Et</a>

<!-- ✅ ArtiFrame Güvenli Kullanımı -->
<a href="<?= escapeUrl($user['website']) ?>">Siteyi Ziyaret Et</a>

View Helpers — csrfField() ViewMethod

HTML formlarına CSRF saldırılarına karşı gizli token alanı ekler. POST form içeren her sayfada kullanılması zorunludur.

string csrfField()
<form action="/api/kaydet.php" method="POST">
    <?= csrfField() ?>   <!-- Güvenlik için zorunludur -->
    <input type="text" name="ad">
    <button type="submit" data-js="kaydet-btn">Kaydet</button>
</form>

View Helpers — Tarih Fonksiyonları ViewMethod

Veritabanındaki Y-m-d H:i:s formatındaki tarih stringleri veya UNIX timestamp değerlerini işler. Tüm metinsel çıktılar dile göre (tr, en, de, fr, es) yerelleştirilir.

FonksiyonÇıktı ÖrneğiAçıklama
day($date)24Sadece gün
month($date)07Sadece ay (rakam)
year($date)2026Yıl
timeOnly($date)14:30Saat:Dakika
fulldate($date)24.07.2026Tam tarih
formatDate($date, $format)24.07.2026 14:30Özel format
monthName($date, $lang)Temmuz / July / JuliAy adı (dile göre)
fulldateName($date, $lang)24 Temmuz 2026 / July 24, 2026Tam tarih (ay adıyla, dile göre)
timeAgo($date, $lang)5 dakika önce / 5 minutes agoSosyal medya tarzı (dile göre)
💡 Parametreleri Anlamak: $format ve $lang
  • $format (Biçim): Sadece formatDate() fonksiyonu ile kullanılır. Standart PHP tarih harflerini kabul eder. (Örneğin: 'd/m/Y' ➔ 24/07/2026, veya 'H:i' ➔ 15:30). Diğer hazır fonksiyonlar (day, year vb.) yetersiz kaldığında kendi tarih kalıbınızı oluşturmanızı sağlar.
  • $lang (Dil): Çıktısında metin (ay adı, "önce" kelimesi) barındıran fonksiyonlarda kullanılır. Bu parametreyi boş bırakırsanız sistem varsayılan olarak 'tr' (Türkçe) çalışır. Çok dilli bir proje yapıyorsanız ikinci parametreye dil kodunu (tr, en, de, fr, es) girmeniz yeterlidir. (Örn: timeAgo($tarih, 'en') ➔ 5 minutes ago)
<!-- time() fonksiyonu ile alınmış bir UNIX timestamp olduğunu varsayalım -->
<?php $tarih = time(); ?>

<!-- Dile göre tam tarih -->
<span><?= fulldateName($tarih, 'tr') ?></span>
<!-- Çıktı: 24 Temmuz 2026 -->

<!-- Sosyal medya tarzı zaman gösterimi -->
<span><?= timeAgo($tarih, 'en') ?></span>
<!-- Çıktı: 5 minutes ago -->

<span><?= timeAgo($tarih, 'de') ?></span>
<!-- Çıktı: vor 5 Minuten -->

View Helpers — Metin Formatlama ViewMethod

truncate($text, $length, $append)

string truncate(string $text, int $length = 100, string $append = '...')

Uzun metinleri (örneğin blog özetlerini) son kelimeyi kesmeden istenilen karakter limitinde kırpar ve sonuna belirtilen ek ekler.

<p><?= truncate($post['icerik'], 160, '...') ?></p>

View Helpers — money() ViewMethod

Tutarı para birimi sembolüyle formatlar. Para birimi kodu (ISO), sembol yerleşimini otomatik belirler. Varsayılan para birimi usd'dir.

string money(float $amount, string $currency = 'usd')
KodÇıktıPara Birimi
usd$1.250,00ABD Doları
eur1.250,00 €Euro
try / tl1.250,00 ₺Türk Lirası
gbp£1.250,00İngiliz Sterlini
jpy1.250,00 ¥Japon Yeni
inr1.250,00 ₹Hindistan Rupisi
rub1.250,00 ₽Rus Rublesi
krw1.250,00 ₩Güney Kore Wonu
brlR$1.250,00Brezilya Reali
aed1.250,00 د.إBAE Dirhemi
<span><?= money($urun['fiyat'], 'try') ?></span>
<!-- Çıktı: 1.250,00 ₺ -->

<span><?= money($urun['fiyat'], 'usd') ?></span>
<!-- Çıktı: $1.250,00 -->

System Helpers — jsonResponse() SystemMethod

JSON header'ını ayarlar, HTTP durum kodunu verir ve json_encode ile çıktıyı güvenli biçimde basar, ardından betiği sonlandırır. Tüm API yanıtları bu fonksiyon üzerinden verilmek zorundadır.

void jsonResponse(array $data, int $statusCode = 200)
// Başarılı yanıt
jsonResponse(['status' => 'success', 'data' => $kullanici], 200);

// Hata yanıtı
jsonResponse(['status' => 'error', 'message' => 'Yetkisiz erişim.'], 401);

System Helpers — verifyCsrf() SystemMethod

Gelen POST isteğinin meşru bir formdan geldiğini doğrular. Formun içine csrfField() eklenmemişse veya token geçersizse false döner.

bool verifyCsrf(string $token)
if (!verifyCsrf($_POST['csrf_token'] ?? '')) {
    jsonResponse(['status' => 'error', 'message' => 'Geçersiz CSRF token.'], 403);
}

System Helpers — Sanitize Fonksiyonları SystemMethod

FonksiyonAçıklama
sanitizeInt($value)İçerisindeki tüm harfleri, sembolleri ve virgülleri uçurur, sadece tam sayı rakamlarını bırakır. ID veya limitler için kullanılır.
sanitizeFloat($value)Küsüratlı (ondalıklı) sayılar dışındaki her şeyi temizler. Para tutarları veya metrikler için kullanılır.
sanitizeString($value)XSS ve türevi saldırıları engellemek için tüm HTML ve PHP etiketlerini (<script>, <iframe> vb.) yok eder. Güvenli düz metin bırakır.
sanitizeEmail($email)E-posta formatına uymayan geçersiz ve tehlikeli tüm karakterleri filtreler.
$id    = sanitizeInt($_POST['id'] ?? 0);
$ad    = sanitizeString($_POST['ad'] ?? '');
$email = sanitizeEmail($_POST['email'] ?? '');

System Helpers — HTTP & İstek SystemMethod

FonksiyonAçıklama
isPost()İstek POST mu?
isGet()İstek GET mi?
isPut()İstek PUT mu?
isDelete()İstek DELETE mi?
isAjax()İstek XHR/Fetch API üzerinden mi?
getClientIp()Gerçek IP adresi (Cloudflare & Proxy destekli)
redirect($url)Verilen URL'ye yönlendirip betiği sonlandırır
// Doğrudan tarayıcı erişimini engelle
if (!isAjax()) {
    jsonResponse(['error' => 'Doğrudan erişim kısıtlı.'], 403);
}

// Oturum yoksa yönlendir
if (!isset($_SESSION['user'])) {
    redirect('/giris.php');
}

$ip = getClientIp(); // Cloudflare arkasında da gerçek IP

System Helpers — Güvenlik SystemMethod

FonksiyonAçıklama
generateCsrf()Yeni CSRF token üretir ve session'a kaydeder
generateToken($length)Kriptografik güvenli rastgele hex string (parola sıfırlama, API key vb.)
hashPassword($password)bcrypt ile şifre hash'ler
verifyPassword($password, $hash)Hash ile şifreyi doğrular
// Güvenli token üretimi (API key, e-posta doğrulama linki vb.)
$token = generateToken(32); 
// ⚠️ Not: 32 byte veri üretilir ancak hex formatına (16'lık taban)
// çevrildiği için çıktı tam 2 katı, yani 64 karakter uzunluğunda bir string olur.

// Şifre kaydı
$hash = hashPassword($_POST['sifre']);

// Şifre doğrulama
if (!verifyPassword($_POST['sifre'], $kullanici['sifre_hash'])) {
    jsonResponse(['error' => 'Hatalı şifre.'], 401);
}

API — HTTP Metod Kontrolü ApiControl

$allowedMethods dizisi, ApiControl require'ından önce tanımlanmalıdır. ApiControl bu diziyi okuyarak izin verilmeyen metodlardan gelen istekleri otomatik olarak 405 Method Not Allowed ile reddeder.

// Sadece GET ve POST kabul eden bir endpoint
$allowedMethods = ['GET', 'POST'];
require_once ... . '/../app/ApiControl.php';

// Sadece DELETE kabul eden bir endpoint
$allowedMethods = ['DELETE'];
require_once ... . '/../app/ApiControl.php';

Preflight OPTIONS istekleri CORS için otomatik 200 döner ve betik sonlanır — elle müdahale gerekmez.

API — CORS ApiControl (Yorum Satırı)

Başka domainlerden veya mobil uygulamalardan API erişimine izin vermek istiyorsanız ApiControl.php içindeki CORS bloğunu aktifleştirin.

// ApiControl.php içinde — açıklama kaldırılarak aktifleştirilir
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");
⚠️ Production ortamında * (tüm domainler) kullanmayın. Domain adresini açıkça belirtin.

API — Rate Limiting ApiControl (Yorum Satırı)

Kötü niyetli kullanıcıların veya botların API'yi flood etmesini önlemek için ApiControl.php içindeki Rate Limiting bloğunu aktifleştirebilirsiniz. Varsayılan kural: 60 saniyede 60 istek / IP.

⚠️ Redis Sunucusu Gereklidir: Rate Limiting (hız sınırlandırma) özelliği \Src\Service\RedisService::getInstance() sınıfı üzerinden Redis kullanarak çalışır. Bu kod bloğunu açmadan önce projenizde bir Redis servisi kurduğunuzdan ve src/Service/ altında bir bağlantı sınıfı oluşturduğunuzdan emin olun.
// Aynı IP'den 60 saniyede 60'tan fazla istek gelirse:
http_response_code(429); // Too Many Requests
echo json_encode(['error' => 'Çok fazla istek. Lütfen bekleyin.']);

Tam İş Akışı: İletişim Formu (End-to-End)

Bir İletişim Formu özelliğini ArtiFrame standartlarında baştan sona nasıl geliştireceğinizi adım adım inceleyelim.

  1. View Dosyasını Oluşturun
    artiframe> make:view iletisim.php
    ✔  public/iletisim.php
    ✔  public/assets/css/iletisim.css
    ✔  public/assets/js/iletisim.js
  2. API Endpoint Oluşturun
    artiframe> make:api standart api/iletisim/gonder.php
  3. HTML Formunu Kodlayın
    public/iletisim.php dosyasına aşağıdaki formu ekleyin:
    <form action="/api/iletisim/gonder.php" method="POST">
        <?= csrfField() ?>
        <input type="text"  name="ad"      placeholder="Adınız">
        <input type="email" name="eposta"  placeholder="E-posta">
        <textarea name="mesaj" placeholder="Mesajınız"></textarea>
        <button type="submit" data-js="iletisim-gonder">Gönder</button>
    </form>
  4. JavaScript ile Fetch API'yi Bağlayın
    public/assets/js/iletisim.js dosyasına:
    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);
    });
  5. Sunucu Mantığını (Backend) Tamamlayın
    public/api/iletisim/gonder.php dosyasında:
    <?php
    $allowedMethods = ['POST'];
    require_once $_SERVER['DOCUMENT_ROOT'] . '/../app/ApiControl.php';
    use Bin\SystemMethod;
    
    // 1. CSRF Doğrulama
    if (!verifyCsrf($_POST['csrf_token'] ?? '')) {
        jsonResponse(['error' => 'Geçersiz token.'], 403);
    }
    
    // 2. Veriyi Temizle
    $ad     = sanitizeString($_POST['ad'] ?? '');
    $eposta = sanitizeEmail($_POST['eposta'] ?? '');
    $mesaj  = sanitizeString($_POST['mesaj'] ?? '');
    
    // 3. İş Mantığı (Mail gönder, DB'ye kaydet vb.)
    // ...
    
    // 4. Yanıt Ver
    jsonResponse(['status' => 'success', 'message' => 'Mesajınız alındı.'], 200);
✅ Tamamlandı! XSS koruması, CSRF doğrulama, HTTP metod kısıtlaması ve data-js mimarisi ile tam güvenli ve standart uyumlu bir form akışı oluşturdunuz.