
PHP Dosya İşlemleri: Okuma, Yazma ve Güvenli Yükleme
Dosya işlemleri basit görünür, ancak iki noktada sık hata yapılır: büyük dosyaların tamamının belleğe alınması ve kullanıcıdan gelen dosyaların yeterince denetlenmeden saklanması. Bu yazıda okuma ve yazma yöntemleri ile yükleme güvenliği birlikte ele alınıyor.
Dosya işlemleri aynı zamanda yavaş işlemlerdir. Disk erişimi bellek erişiminden kat kat uzun sürdüğü için, döngü içinde tekrarlanan okuma ve yazma çağrıları istek süresini hızla büyütür. Tekrarlanan erişim yerine veriyi tek seferde alıp bellekte işlemek, çoğu durumda en basit iyileştirmedir.
Dosya okuma
Küçük dosyalar tek çağrıyla okunabilir. Boyut büyüdüğünde ise dosyanın tamamı belleğe alınacağı için satır satır okuma tercih edilmelidir:
// küçük dosya
$icerik = file_get_contents('ayar.json');
$ayar = json_decode($icerik, true);
// büyük dosya: bellek sabit kalır
$fp = fopen('kayit.log', 'r');
if ($fp === false) {
throw new RuntimeException('Dosya açılamadı');
}
while (($satir = fgets($fp)) !== false) {
isle(trim($satir));
}
fclose($fp);Dönüş değerleri kontrol edilmeden kullanılmamalıdır. Dosya yoksa ya da okuma izni verilmemişse fonksiyonlar false döner ve bu değer sessizce boş metin gibi davranarak hatanın kaynağını gizler.
Dosya yazma ve kilitleme
Aynı dosyaya birden fazla istek aynı anda yazabilir. Kilit kullanılmadığında satırlar iç içe geçer ve dosya bozulur. LOCK_EX bayrağı yazma sırasında dosyayı kilitler:
file_put_contents('sayac.txt', $deger, LOCK_EX);
// sona ekleme
file_put_contents('islem.log', $satir . PHP_EOL, FILE_APPEND | LOCK_EX);Yazma işleminin bir kısmının tamamlanıp bir kısmının başarısız olması riskliyse, önce geçici dosyaya yazıp sonra yeniden adlandırmak daha güvenlidir. Yeniden adlandırma aynı dosya sisteminde atomik kabul edilir.
Dizin işlemleri
if (!is_dir($dizin)) {
mkdir($dizin, 0755, true);
}
// belirli uzantıdaki dosyalar
$resimler = glob($dizin . '/*.{jpg,png}', GLOB_BRACE);
// dizin içeriği
foreach (scandir($dizin) as $ad) {
if ($ad === '.' || $ad === '..') {
continue;
}
}Dizin oluştururken üçüncü parametrenin true verilmesi, ara dizinlerin de oluşturulmasını sağlar. İzinler gereğinden geniş verilmemelidir; yazma izni yalnızca gerçekten gereken dizinlerde bulunmalıdır.
Yüklenen dosyaların denetimi
Yükleme işleminde en sık yapılan hata, dosya türünü uzantıya ya da tarayıcının bildirdiği tür bilgisine bakarak belirlemek. İkisi de kullanıcı kontrolündedir. Tür, dosyanın içeriğinden okunmalıdır:
$dosya = $_FILES['gorsel'] ?? null;
if ($dosya === null || $dosya['error'] !== UPLOAD_ERR_OK) {
throw new RuntimeException('Yükleme başarısız');
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$tur = $finfo->file($dosya['tmp_name']);
$izinli = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'];
if (!isset($izinli[$tur])) {
throw new RuntimeException('Desteklenmeyen tür');
}Dosya adı kullanıcıdan alınmaz, sunucuda üretilir. Böylece hem çakışma hem de ad üzerinden yapılan dizin geçişi denemeleri engellenir:
$ad = bin2hex(random_bytes(16)) . '.' . $izinli[$tur];
$hedef = $yuklemeDizini . '/' . $ad;
if (!move_uploaded_file($dosya['tmp_name'], $hedef)) {
throw new RuntimeException('Dosya taşınamadı');
}Yol denetimi ve dizin geçişi
Kullanıcıdan gelen bir değer dosya yoluna eklendiğinde, iki nokta üst üste kullanılarak üst dizinlere çıkılabilir. Çözüm, yolu çözümleyip beklenen dizinin içinde kalıp kalmadığını doğrulamaktır:
$istenen = realpath($yuklemeDizini . '/' . basename($_GET['dosya'] ?? ''));
$kok = realpath($yuklemeDizini);
if ($istenen === false || !str_starts_with($istenen, $kok)) {
http_response_code(404);
exit;
}Yüklenen dosyaların web kökünün dışında saklanması ek bir koruma sağlar. Dosyalar doğrudan adresle değil, yetki kontrolü yapan bir betik üzerinden sunulur.
İzinler ve sahiplik
Web sunucusu süreci hangi kullanıcı adına çalışıyorsa, dosya izinleri o kullanıcıya göre ayarlanmalıdır. Yaygın hata, sorun çıkmasın diye tüm dizine tam yetki vermektir. Bu ayar, sunucuya sızan bir saldırganın dosyaları değiştirmesini de serbest bırakır.
Genel kural şudur: dizinler için 755, dosyalar için 644 yeterlidir. Yazma izni yalnızca yükleme ve önbellek dizinlerinde bulunmalı, uygulama kodunun bulunduğu dizinler yazmaya kapalı olmalıdır.
// yalnızca gereken dizinde yazma izni
chmod($yuklemeDizini, 0755);
chmod($hedefDosya, 0644);Geçici dosyalar ve temizlik
Ara işlemler için üretilen dosyalar işletim sisteminin geçici dizininde tutulmalıdır. Bu dizin hem doğru izinlere sahiptir hem de düzenli olarak temizlenir. Dosya adının elle üretilmesi yerine hazır fonksiyon kullanmak, aynı anda çalışan isteklerin birbirinin dosyasını ezmesini engeller:
$gecici = tempnam(sys_get_temp_dir(), 'rapor_');
try {
file_put_contents($gecici, $veri);
isle($gecici);
} finally {
unlink($gecici);
}Silme işleminin finally bloğunda yapılması, hata durumunda bile dosyanın kalmamasını sağlar. Uzun süre çalışan sistemlerde biriken geçici dosyalar disk dolmasının sessiz nedenlerinden biridir.
Kontrol listesi
- Büyük dosyalar satır satır okunur.
- Dosya işlemlerinin dönüş değerleri kontrol edilir.
- Eşzamanlı yazmada kilit kullanılır.
- Yüklenen dosyanın türü içerikten okunur, uzantıya güvenilmez.
- Dosya adı sunucuda üretilir.
- Kullanıcıdan gelen yol realpath ile doğrulanır.
PHP eğitim serisi
Bu yazı, temel sözdiziminden üretim ortamına kadar ilerleyen 25 bölümlük serinin bir parçası. Bölümler sırayla okunacak biçimde hazırlandı, ancak her biri tek başına da kullanılabilir.
Temeller
- Kurulum ve temel sözdizimi
- Döngüler ve akış kontrolü
- Diziler
- Fonksiyonlar
- String fonksiyonları
- Süper globaller
- Form işleme
- Dosya işlemleri (bu yazı)
Dilin ayrıntıları
Nesne yönelimli PHP
- Sınıf ve nesne
- Kalıtım, arayüz ve trait
- Statik üyeler, sabitler ve enum
- Namespace, Composer ve autoload
Veri ve güvenlik
- PDO ile veritabanı işlemleri
- Oturum yönetimi ve kimlik doğrulama
- Güvenli çıktı ve XSS
- JSON ile veri değişimi
Dış dünya ve üretim
Yorumlar
İlk yorumu sen yap.
İlgili Yazılar

PHP Uygulamasını Üretime Almak: Yapılandırma ve Güvenlik
Geliştirme makinesinde çalışan kod ile üretimde güvenle çalışan kod arasındaki fark yapılandırmadır. Ayarlar, izinler, dağıtım akışı ve son kontroller.

PHP ile REST API Yazmak: Yönlendirme ve HTTP Durum Kodları
Bir API'nin kalitesi çoğunlukla ayrıntılarda belli olur: doğru durum kodu, tutarlı hata biçimi ve öngörülebilir adresler. Saf PHP ile uçtan uca kurulum.

PHPUnit ile Birim Test Yazmak: Kurulum, Sahte Nesneler ve Kapsam
Testin amacı hata bulmak değil, değişikliği güvenle yapabilmektir. Kurulum, ilk test, sahte nesneler ve test edilebilir kodun özellikleri.