Laravel dosya yükleme, doğru kurulduğunda birkaç satırda biten ama yanlış yapıldığında güvenlik açıkları, kırık görseller ve dağınık bir storage klasörü doğuran bir konudur. Bu rehberde bir görsel yükleme akışını baştan sona kuruyoruz: formdan gelen dosyayı doğrulamak, Storage facade ile diske yazmak, public symlink üzerinden erişilebilir kılmak ve son olarak gerçekten geçerli bir görsel olduğunu doğrulamak. Anlatılan her şey üretimde çalışan gerçek bir yaklaşımdır; uydurma yardımcı paket veya komut yok.
Disk mantığı: local, public ve storage
Laravel, dosya işlemlerini disk kavramı üzerinden soyutlar. Yapılandırma config/filesystems.php içindedir ve varsayılan olarak iki yerel disk gelir:
- local —
storage/app/privatealtında, web'den doğrudan erişilemeyen özel dosyalar için. - public —
storage/app/publicaltında, herkese açık olması gereken dosyalar (avatar, kapak görseli, ek) için.
Önemli ayrım şudur: storage/app/public klasörü web kök dizininin (public/) dışındadır, yani tarayıcı oraya doğrudan ulaşamaz. Köprüyü symlink kurar. Bunu bir kez çalıştırmanız yeterli:
php artisan storage:link
Bu komut public/storage konumunu storage/app/public hedefine bağlayan sembolik bir bağlantı oluşturur. Artık storage/app/public/avatars/foo.png dosyası /storage/avatars/foo.png URL'siyle erişilebilir hale gelir. Bazı paylaşımlı hosting ortamlarında PHP'nin symlink() fonksiyonu kapalıdır; o durumda bağlantıyı kabuk üzerinden ln -sfn storage/app/public public/storage ile kurabilirsiniz.
Yüklemeyi doğrulamak
Hiçbir dosya doğrulanmadan diske yazılmamalı. Form isteğini bir controller'da karşılarken kuralları doğrudan validate() ile geçirebilirsiniz:
public function store(Request $request)
{
$request->validate([
'avatar' => ['required', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048'],
]);
// ...
}
Buradaki her kural bir işe yarar:
image— dosyanın bir görsel olmasını şart koşar (jpeg, png, bmp, gif, svg veya webp).mimes— kabul edilen uzantıları açıkça sınırlar. SVG'yi dışarıda bırakmak çoğu zaman akıllıcadır çünkü içine script gömülebilir.max:2048— boyut sınırı kilobayt cinsindendir, yani burada 2 MB.
Daha karmaşık formlarda kuralları bir php artisan make:request StoreAvatarRequest ile üretilen Form Request sınıfına taşımak controller'ı temiz tutar.
Storage facade ile diske yazma
Doğrulama geçtiyse dosyayı kaydetmek tek satırdır. Yüklenen dosya nesnesi (Illuminate\Http\UploadedFile) doğrudan disk yöntemleriyle çalışır:
use Illuminate\Support\Facades\Storage;
$path = $request->file('avatar')->store('avatars', 'public');
// $path = "avatars/9aXk2...png"
store() dosyaya benzersiz, tahmin edilemez bir ad üretir ve göreli yolu döndürür. Bu yolu (örneğin avatars/9aXk2...png) veritabanına kaydedin; tam URL'yi değil. Böylece alan adınız veya disk yapılandırmanız değişirse veriler bozulmaz.
Belirli bir ad vermek isterseniz storeAs() kullanın:
$path = $request->file('avatar')->storeAs(
'avatars', $user->id.'.'.$request->file('avatar')->extension(), 'public'
);
Daha sonra dosyayı göstermek için göreli yolu genel bir URL'ye çevirin:
$url = Storage::disk('public')->url($path);
// /storage/avatars/9aXk2...png
Blade tarafında da aynı mantık geçerli:
<img src="{{ Storage::url($user->avatar) }}" alt="Avatar">
Gerçekten görsel mi? Doğrulamanın ötesi
image ve mimes kuralları MIME tipini ve uzantıyı kontrol eder, ama gerçekten okunabilir, bozulmamış bir görsel olduğunu garanti etmez. Görsel boyutlarına dair kısıtlama eklemek için dimensions kuralı vardır:
$request->validate([
'avatar' => [
'required', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048',
'dimensions:min_width=200,min_height=200,max_width=4000,max_height=4000',
],
]);
Görseli ayrıca yeniden boyutlandırmak, kırpmak veya optimize etmek istiyorsanız Intervention Image kütüphanesi yaygın ve güvenilir bir seçimdir. Composer ile eklenir:
composer require intervention/image
Tipik bir kullanım, yüklenen dosyayı belleğe alıp belirli bir genişliğe ölçeklemek ve diske geri yazmaktır:
use Intervention\Image\ImageManager;
use Intervention\Image\Drivers\Gd\Driver;
$manager = new ImageManager(new Driver());
$image = $manager->read($request->file('avatar'));
$image->scaleDown(width: 800);
Storage::disk('public')->put($path, (string) $image->encode());
Bu işlem aynı zamanda gizli bir güvenlik faydası sağlar: görseli yeniden kodlamak, dosyaya gömülmüş kötü amaçlı veriyi büyük ölçüde temizler.
Eski dosyaları temizlemek ve silmek
Bir kullanıcı avatarını değiştirdiğinde eskisi çöp olarak kalır. Yeni dosyayı kaydetmeden önce eskisini silmek iyi bir alışkanlıktır:
if ($user->avatar) {
Storage::disk('public')->delete($user->avatar);
}
$user->avatar = $request->file('avatar')->store('avatars', 'public');
$user->save();
Bir dosyanın varlığını kontrol etmek için Storage::disk('public')->exists($path) kullanılır. Toplu silme veya klasör listeleme gibi işlemler için de files(), allFiles() ve deleteDirectory() yöntemleri vardır.
Özel dosyalar ve güvenli indirme
Her dosyanın herkese açık olması gerekmez. Faturalar, sözleşmeler veya kişisel belgeler local diskte (storage/app/private) kalmalı ve yalnızca yetkilendirme kontrolünden geçen kullanıcıya sunulmalıdır. Bunun için symlink yerine bir controller üzerinden yönlendirme yaparsınız:
public function download(Document $document)
{
$this->authorize('view', $document);
return Storage::disk('local')->download($document->path);
}
Burada dosya hiçbir zaman doğrudan URL ile erişilebilir olmaz; her istek policy üzerinden denetlenir. Genel görseller için symlink, hassas belgeler için yetkili indirme — bu ayrımı net tutmak iyi bir mimarinin temelidir.
Sık Sorulan Sorular
storage:link çalıştırdım ama görseller hâlâ görünmüyor, neden?
En sık neden, sunucuda symlink'in oluşmamış olması (deploy sırasında public/storage kopyalanmamış veya PHP'nin symlink() fonksiyonu kapalı) veya veritabanında tam URL yerine yanlış göreli yol saklanmasıdır. Symlink'i kabuktan ln -sfn ile kurun ve URL'yi her zaman Storage::url() ile üretin.
Yüklenen dosyalar için en güvenli boyut ve tip sınırı nedir?
Tek bir doğru sayı yoktur; ihtiyaca göre belirleyin. Genel kural: yalnızca beklediğiniz tipleri mimes ile beyaz listeleyin, SVG'yi dışarıda bırakın, makul bir max sınırı koyun ve mümkünse görseli Intervention Image ile yeniden kodlayın.
Dosyaları Amazon S3'e taşımak kodumu değiştirir mi?
Neredeyse hiç. Disk soyutlaması sayesinde Storage::disk('s3') demek veya varsayılan diski değiştirmek yeterlidir; store(), url() ve delete() çağrılarınız aynı kalır. league/flysystem-aws-s3-v3 paketini eklemeniz gerekir.
Dosya yükleme akışınızı sağlamlaştırmak mı istiyorsunuz? İster sıfırdan bir Laravel uygulaması kuruyor olun ister mevcut bir storage yapısını güvenli hale getirmek isteyin, birlikte çalışabiliriz — benimle iletişime geçin.