- PDO ile bir veritabanına bağlanmak ve sonuçları farklı getirme modlarında okumak
- Hazırlanmış ifadelerle CRUD işlemleri yapmak ve SQL enjeksiyonunu önlemek
- PDO, SQLite, oturumlar ve kaçışlama ile eksiksiz bir ziyaretçi defteri geliştirmek
- Laravel'in rotalarını, denetleyicilerini, Blade'i ve Eloquent'i genel hatlarıyla açıklamak
Bir betik işini bitirir bitirmez bütün değişkenleri yok olur. Oysa ziyaretçi defterindeki yazılar, kullanıcılar ve siparişler yıllarca saklanmalıdır; bunun için bir veritabanı gerekir. PHP'de veritabanlarıyla çalışmanın modern yolu PDO'dur (PHP Data Objects): aynı kod MySQL, PostgreSQL, SQLite ve diğer veritabanlarıyla çalışır. Bu son derste PDO'yu öğrenecek ve kurs boyunca öğrendiklerini birleştiren bir ziyaretçi defteri geliştireceksin.
PDO ile bağlanma
Bağlanmak için bir DSN dizesi gerekir: veritabanının türünü, adresini ve adını belirtir. MySQL için mutlaka charset=utf8mb4 yaz; böylece Azerbaycanca, Rusça ve Türkçe harfler, hatta emojiler bile doğru saklanır. SQLite için sunucu gerekmez: veritabanının tamamı tek bir dosyadır, sqlite::memory: ise veritabanını yalnızca bellekte oluşturur; denemeler için idealdir.
<?php
$pdo = new PDO(
'mysql:host=localhost;dbname=educora;charset=utf8mb4',
'educora_app',
getenv('DB_PASSWORD'),
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
// SQLite: a file or memory, no server needed
$sqlite = new PDO('sqlite:' . __DIR__ . '/app.sqlite');getenv) ya da bir yapılandırma dosyasından oku.ERRMODE_EXCEPTION— hatalı bir sorguPDOExceptionfırlatır (PHP 8'de bu varsayılandır ama açıkça yazmak iyidir).FETCH_ASSOC— satırlar['name' => ...]gibi ilişkisel diziler olarak döner.EMULATE_PREPARES => false— MySQL'de gerçek hazırlanmış ifadeler kullanılır.
SQL enjeksiyonu ve hazırlanmış ifadeler
Kullanıcının yazdığını doğrudan SQL metnine yapıştırırsan sorgunun kendisini değiştirebilir. Örneğin e-posta yerine ' OR '1'='1 yazılırsa koşul her zaman doğru olur ve sorgu bütün kullanıcıları döndürür. Buna SQL enjeksiyonu denir; web sitelerinin en tehlikeli açıklarından biridir. Hazırlanmış ifadede (prepared statement) SQL metni ve veriler sunucuya ayrı ayrı gönderilir; bu yüzden veriler asla SQL olarak çalıştırılmaz.
$email = $_POST['email'];
$sql = "SELECT * FROM users WHERE email = '$email'";
$user = $pdo->query($sql)->fetch();$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$_POST['email']]);
$user = $stmt->fetch();? bir yer tutucudur: veri execute'a ayrıca verilir. :email gibi adlandırılmış yer tutucular da vardır: execute(['email' => $email]).CRUD ve sonuçları okuma
CRUD dört temel işlemi ifade eder: oluşturma (Create — INSERT), okuma (Read — SELECT), güncelleme (Update — UPDATE) ve silme (Delete — DELETE). Aşağıdaki program bir yapılacaklar listesini bellekteki bir SQLite veritabanında tutar ve terminalde olduğu gibi çalışır. Bir kez hazırlanan bir ifade farklı verilerle defalarca çalıştırılabilir.
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$pdo->exec('CREATE TABLE tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
done INTEGER NOT NULL DEFAULT 0
)');
$insert = $pdo->prepare('INSERT INTO tasks (title) VALUES (:title)');
foreach (['Learn PDO', 'Build a guestbook', 'Read about Laravel'] as $title) {
$insert->execute(['title' => $title]);
}
echo "Last id: ", $pdo->lastInsertId(), "\n";
$pdo->prepare('UPDATE tasks SET done = 1 WHERE id = ?')->execute([1]);
$delete = $pdo->prepare('DELETE FROM tasks WHERE id = ?');
$delete->execute([3]);
echo "Deleted rows: ", $delete->rowCount(), "\n";
foreach ($pdo->query('SELECT id, title, done FROM tasks ORDER BY id') as $row) {
$mark = $row['done'] ? 'x' : ' ';
echo "[$mark] {$row['id']}. {$row['title']}\n";
}Last id: 3 Deleted rows: 1 [x] 1. Learn PDO [ ] 2. Build a guestbook
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->exec("CREATE TABLE scores (name TEXT, score INTEGER)");
$pdo->exec("INSERT INTO scores VALUES ('Aysel', 92), ('Murad', 78), ('Leyla', 95)");
$count = $pdo->query('SELECT COUNT(*) FROM scores')->fetchColumn();
echo "Students: $count\n";
$best = $pdo->query('SELECT * FROM scores ORDER BY score DESC')->fetch(PDO::FETCH_OBJ);
echo "Best: {$best->name} ({$best->score})\n";
$pairs = $pdo->query('SELECT name, score FROM scores ORDER BY name')
->fetchAll(PDO::FETCH_KEY_PAIR);
print_r($pairs);Students: 3
Best: Leyla (95)
Array
(
[Aysel] => 92
[Leyla] => 95
[Murad] => 78
)| Metot / mod | Ne döndürür |
|---|---|
fetch() | sonraki satırı; satır kalmadıysa false |
fetchAll() | tüm satırları dizi olarak |
fetchColumn() | sonraki satırın ilk sütununu — COUNT(*) için kullanışlıdır |
PDO::FETCH_OBJ | satırı nesne olarak: $row->name |
PDO::FETCH_KEY_PAIR | iki sütundan anahtar => değer dizisi |
rowCount() | bir UPDATE/DELETE'in değiştirdiği satır sayısı |
Proje: ziyaretçi defteri
Şimdi her şeyi birleştirelim: ziyaretçi adını ve mesajını yazar, PHP bunları doğrular, bir SQLite veritabanına kaydeder ve son 20 yazıyı gösterir. Projede oturum, CSRF belirteci, hazırlanmış ifade, PRG yönlendirmesi ve htmlspecialchars yer alır. Projenin tamamı tek bir index.php dosyasıdır; onu iki parça hâlinde gösteriyoruz.
<?php
declare(strict_types=1);
session_start();
$pdo = new PDO('sqlite:' . __DIR__ . '/guestbook.sqlite');
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$pdo->exec('CREATE TABLE IF NOT EXISTS entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
message TEXT NOT NULL,
created_at TEXT NOT NULL
)');
function e(string $value): string
{
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}
$_SESSION['csrf'] ??= bin2hex(random_bytes(32));
$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!hash_equals($_SESSION['csrf'], (string) ($_POST['csrf'] ?? ''))) {
http_response_code(403);
exit('Invalid CSRF token');
}
$name = trim((string) ($_POST['name'] ?? ''));
$message = trim((string) ($_POST['message'] ?? ''));
if ($name === '' || mb_strlen($name) > 50) {
$errors[] = 'Name must be 1-50 characters';
}
if ($message === '' || mb_strlen($message) > 500) {
$errors[] = 'Message must be 1-500 characters';
}
if ($errors === []) {
$stmt = $pdo->prepare('INSERT INTO entries (name, message, created_at) VALUES (?, ?, ?)');
$stmt->execute([$name, $message, date('Y-m-d H:i')]);
header('Location: /');
exit;
}
}
$entries = $pdo->query('SELECT * FROM entries ORDER BY id DESC LIMIT 20')->fetchAll();
?><!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Guestbook</title></head>
<body>
<h1>Guestbook</h1>
<?php foreach ($errors as $error): ?>
<p style="color: red"><?= e($error) ?></p>
<?php endforeach; ?>
<form method="post">
<input type="hidden" name="csrf" value="<?= e($_SESSION['csrf']) ?>">
<input name="name" placeholder="Your name" maxlength="50" required>
<textarea name="message" placeholder="Your message" maxlength="500" required></textarea>
<button>Sign the guestbook</button>
</form>
<?php foreach ($entries as $entry): ?>
<article>
<strong><?= e($entry['name']) ?></strong> · <?= e($entry['created_at']) ?>
<p><?= nl2br(e($entry['message'])) ?></p>
</article>
<?php endforeach; ?>
</body>
</html>foreach (...): ve endforeach; HTML içinde kullanışlı bir yazımdır- 1Bir klasör oluştur
Bir
guestbookklasörü oluştur ve iki bölümü art arda tek birindex.phpdosyasına yaz. - 2SQLite'ı denetle
php -mlistesindepdo_sqlitebulunmalıdır. Windows'ta gerekirsephp.iniiçindekiextension=pdo_sqlitesatırının başındaki;işaretini sil. - 3Sunucuyu başlat
Klasörde
php -S localhost:8000komutunu çalıştır vehttp://localhost:8000adresini aç.guestbook.sqlitedosyası ilk istekte kendiliğinden oluşur. - 4Dene ve bozmaya çalış
Ad yerine
<b>Hi</b>yaz: kalın değil, düz metin olarak görünmelidir. Formu gönderdikten sonra sayfayı yenile; yazı ikinci kez eklenmemelidir.
Sonraki adım: Laravel
Gerçek projelerde bunların hepsi her seferinde elle yazılmaz; bir çatı (framework) kullanılır. En popüler PHP çatısı Laravel'dir. Yeni bir proje composer create-project laravel/laravel guestbook komutuyla oluşturulur, php artisan serve ise geliştirme sunucusunu başlatır. Laravel'de rotalar (routes) bir adresi bir denetleyici metoduna bağlar, denetleyici (controller) isteği işler, Blade şablonları HTML'i oluşturur, Eloquent ise veritabanı tablolarını PHP sınıfları olarak sunar.
// routes/web.php
Route::get('/', [EntryController::class, 'index']);
Route::post('/entries', [EntryController::class, 'store']);
// app/Http/Controllers/EntryController.php
class EntryController extends Controller
{
public function index()
{
return view('entries', ['entries' => Entry::latest()->take(20)->get()]);
}
public function store(Request $request)
{
$data = $request->validate([
'name' => 'required|max:50',
'message' => 'required|max:500',
]);
Entry::create($data);
return redirect('/');
}
}Entry modelinin $fillable özelliğinde name ve message listelenir)<form method="post" action="/entries">
@csrf
<input name="name">
<textarea name="message"></textarea>
<button>Send</button>
</form>
@foreach ($entries as $entry)
<p><strong>{{ $entry->name }}</strong>: {{ $entry->message }}</p>
@endforeach{{ }} metni otomatik olarak kaçışlar, @csrf ise gizli belirteç alanını ekler| Görev | Saf PHP | Laravel |
|---|---|---|
| Yönlendirme | $_SERVER['REQUEST_METHOD'] | Route::get(...), Route::post(...) |
| Kaçışlama | htmlspecialchars() | {{ $value }} |
| CSRF | random_bytes + hash_equals | @csrf |
| Veritabanı | PDO::prepare() | Entry::create($data) |
| Tablo oluşturma | CREATE TABLE | migration'lar: php artisan migrate |
Önemli noktalar
- PDO birçok veritabanı için tek bir arayüz sunar; bağlantı bir DSN dizesiyle yapılır, MySQL için
charset=utf8mb4eklenir. - Kullanıcı verisi SQL'e yalnızca
prepareveexecuteile, yer tutucular (?,:name) aracılığıyla girer; bu, SQL enjeksiyonunu önler. fetch,fetchAll,fetchColumnveFETCH_ASSOC,FETCH_OBJ,FETCH_KEY_PAIRmodları sonucu gereken biçimde verir.- Güvenli bir web uygulaması; CSRF belirteci, sunucu tarafı doğrulama, hazırlanmış ifadeler, PRG yönlendirmesi ve kaçışlanmış çıktıyı birleştirir.
- Laravel aynı fikirleri rotalar, denetleyiciler, Blade ve Eloquent ile otomatikleştirir.
Kendini test et
10 soru. Her doğru cevap XP kazandırır.