Laravel

Laravel Middleware Kullanımı: Başlangıç Rehberi

Laravel middleware, bir HTTP isteği controller’a ulaşmadan önce veya yanıt kullanıcıya dönerken araya giren ve ortak kontrolleri çalıştıran katmandır. Oturum denetimi, e-posta doğrulama kontrolü, hız sınırı, dil seçimi, bakım modu ve güvenlik başlıkları eklemek middleware’in tipik görevleridir. Doğru kullanıldığında kod tekrarını ortadan kaldırır ve her rotada aynı kontrolü yazmaktan kurtarır; ancak middleware içinde bütün iş kurallarını toplamak test ve bakımı zorlaştırır. Bu rehberde middleware’in nasıl çalıştığını, Laravel 11 ve sonrasındaki kayıt yöntemini, parametreli ve sonlandırılabilir (terminable) middleware örneklerini, yetkilendirme ile farkını ve test yöntemlerini adım adım ele alıyoruz.

Middleware nasıl çalışır?

Laravel’de her HTTP isteği bir dizi katmandan geçer. Bu katmanları iç içe geçmiş soğan halkaları gibi düşünebilirsiniz: istek dıştan içe doğru her middleware’den geçerek controller’a ulaşır, controller’ın ürettiği yanıt ise içten dışa doğru aynı katmanlardan geçerek kullanıcıya döner. Her middleware isteği bir sonraki katmana iletmeye, kendi yanıtını döndürerek zinciri kesmeye veya dönen yanıtı değiştirmeye karar verebilir.

Laravel'de bir istek middleware katmanlarından nasıl geçer?
İstek dıştan içe, yanıt içten dışa aynı katmanlardan geçer.

Bu yapı sayesinde iki tür middleware yazılabilir:

  • Önce çalışan (before) middleware: İsteği controller’a ulaşmadan inceler. Kullanıcı giriş yapmamışsa giriş sayfasına yönlendirmek veya istekte geçersiz bir dil kodu varsa varsayılan dili ayarlamak buna örnektir.
  • Sonra çalışan (after) middleware: Controller’ın yanıtını aldıktan sonra devreye girer. Yanıta güvenlik başlıkları eklemek veya yanıt süresini ölçüp kaydetmek buna örnektir.

Middleware oluşturma

Yeni bir middleware sınıfını Artisan komutuyla oluşturabilirsiniz:

php artisan make:middleware EnsureUserIsAdmin

Komut app/Http/Middleware klasöründe bir sınıf oluşturur. Bu sınıfın handle metodu iki parametre alır: gelen istek ve bir sonraki katmanı temsil eden $next closure’ı. Kontrol başarılıysa $next($request) çağrılarak istek ilerletilir; başarısızsa uygun HTTP yanıtı döndürülür.

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class EnsureUserIsAdmin
{
    public function handle(Request $request, Closure $next): Response
    {
        if (! $request->user()?->is_admin) {
            abort(403, 'Bu alana erişim yetkiniz yok.');
        }

        return $next($request);
    }
}

Sonra çalışan bir middleware’de ise önce $next($request) çağrılır, dönen yanıt bir değişkene alınır, üzerinde değişiklik yapılır ve sonra döndürülür:

public function handle(Request $request, Closure $next): Response
{
    $response = $next($request);
    $response->headers->set('X-Content-Type-Options', 'nosniff');
    $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');

    return $response;
}

Middleware’i kaydetme ve rotalara bağlama

Laravel 11 ile birlikte uygulama iskeleti sadeleştirildi ve önceki sürümlerdeki app/Http/Kernel.php dosyası varsayılan kurulumdan kaldırıldı. Yeni projelerde middleware kayıtları bootstrap/app.php dosyasındaki withMiddleware bölümünde yapılır. Eski bir projeyi güncellediyseniz Kernel dosyası hâlâ yerinde olabilir; bu nedenle kullandığınız sürümün resmi belgesini mutlaka kontrol edin.

// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
    // Kısa ad (alias) tanımlama
    $middleware->alias([
        'admin' => \App\Http\Middleware\EnsureUserIsAdmin::class,
    ]);

    // Tüm web rotalarına ekleme
    $middleware->web(append: [
        \App\Http\Middleware\SetSecurityHeaders::class,
    ]);
})

Kısa ad tanımladıktan sonra middleware’i tek bir rotaya, bir rota grubuna veya controller’a bağlayabilirsiniz:

Route::middleware(['auth', 'admin'])->prefix('yonetim')->group(function () {
    Route::get('/panel', [PanelController::class, 'index']);
    Route::get('/kullanicilar', [KullaniciController::class, 'index']);
});

Global, grup ve rota middleware farkı

  • Global middleware: Uygulamaya gelen her istekte çalışır. Bakım modu kontrolü veya gelen verideki boşlukları temizleme gibi gerçekten her isteği ilgilendiren işler için uygundur.
  • Grup middleware: web ve api gibi gruplara bağlıdır. Örneğin oturum, çerez şifreleme ve CSRF koruması web grubunda çalışır; api rotalarında bu katmanlar yoktur.
  • Rota middleware: Yalnızca bağlandığı rota veya grupta çalışır. Yönetici kontrolü, abonelik kontrolü gibi alana özgü işler burada olmalıdır.

Global middleware her istekte çalıştığı için yalnızca yönetici alanına ait bir kontrolü global olarak eklemek gereksiz veritabanı sorgusu ve beklenmedik engeller yaratabilir. Örneğin global bir abonelik kontrolü, giriş ve kayıt sayfalarına erişimi de engelleyip kullanıcıyı döngüye sokabilir.

Controller içinde middleware tanımlama

Middleware’i rota dosyası yerine doğrudan controller sınıfında tanımlamak, ilgili kuralların controller ile birlikte görünmesini sağlar. Laravel 11 ve sonrasında bunun için controller sınıfı HasMiddleware arayüzünü uygular ve statik bir middleware metodu döndürür:

use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;

class FaturaController extends Controller implements HasMiddleware
{
    public static function middleware(): array
    {
        return [
            'auth',
            new Middleware('verified', except: ['index']),
            new Middleware('throttle:10,1', only: ['indir']),
        ];
    }
}

Bu örnekte tüm metotlar giriş gerektirir, liste sayfası dışındaki metotlar e-posta doğrulaması ister ve fatura indirme işlemi dakikada 10 istekle sınırlanır. only ve except parametreleri, aynı controller içindeki farklı işlemler için farklı kurallar tanımlamayı kolaylaştırır.

Laravel’in yerleşik middleware’leri

Yeni bir middleware yazmadan önce Laravel’in hazır sunduğu katmanları kontrol edin; çoğu yaygın ihtiyaç zaten karşılanmıştır:

  • auth: Kullanıcının giriş yapmış olmasını zorunlu kılar; giriş yapmamışsa web isteklerinde giriş sayfasına yönlendirir, JSON bekleyen isteklerde 401 döndürür.
  • guest: Tersine çalışır; giriş yapmış kullanıcıların giriş ve kayıt sayfalarına tekrar erişmesini engeller.
  • verified: E-posta adresini doğrulamamış kullanıcıları doğrulama bildirimi sayfasına yönlendirir.
  • throttle: Belirli bir süre içindeki istek sayısını sınırlar ve sınır aşıldığında 429 yanıtı döndürür. Adlandırılmış oran sınırlayıcılarla kullanıcı veya IP bazında farklı limitler tanımlanabilir.
  • signed: İmzalı URL’lerin değiştirilmediğini ve süresinin dolmadığını doğrular. Abonelikten çıkma veya geçici indirme bağlantıları için kullanışlıdır.
  • password.confirm: Hesap silme veya ödeme bilgisi değiştirme gibi hassas işlemlerden önce kullanıcıdan şifresini yeniden girmesini ister.
  • can: Bir policy veya gate kuralını rota düzeyinde çalıştırır.

Hazır middleware’leri kullanmak hem kod miktarını azaltır hem de Laravel güncellemeleriyle gelen güvenlik iyileştirmelerinden otomatik olarak yararlanmanızı sağlar.

Pratik örnek: dil seçimi middleware’i

Çok dilli bir sitede kullanıcının tercih ettiği dili her istekte ayarlamak, middleware için ideal bir görevdir. Aşağıdaki örnek dili önce URL’deki parametreden, yoksa oturumdan, o da yoksa tarayıcının Accept-Language başlığından alır ve yalnızca desteklenen dillere izin verir:

public function handle(Request $request, Closure $next): Response
{
    $desteklenen = ['tr', 'en'];
    $dil = $request->query('lang')
        ?? $request->session()->get('dil')
        ?? $request->getPreferredLanguage($desteklenen);

    if (! in_array($dil, $desteklenen, true)) {
        $dil = config('app.locale');
    }

    app()->setLocale($dil);
    $request->session()->put('dil', $dil);

    return $next($request);
}

Bu middleware oturuma eriştiği için web grubuna, oturum başlatan katmandan sonra eklenmelidir. Kullanıcıdan gelen dil değerini doğrudan kullanmak yerine izin verilen listeyle karşılaştırmak, beklenmedik değerlerin uygulamaya sızmasını önler.

Parametreli middleware

Bazen aynı kontrolü farklı değerlerle kullanmak istersiniz. Örneğin bazı rotalara yalnızca editörler, bazılarına yalnızca yöneticiler erişsin. Bunun için middleware’e parametre geçebilirsiniz:

public function handle(Request $request, Closure $next, string ...$roller): Response
{
    if (! in_array($request->user()?->rol, $roller, true)) {
        abort(403);
    }
    return $next($request);
}

// Kullanım
Route::get('/yazilar/onay', ...)->middleware('rol:editor,yonetici');

Parametreler iki nokta üst üsteden sonra virgülle ayrılarak yazılır. Rol kontrolü gibi basit senaryolarda bu yöntem işe yarar; ancak kaynak düzeyinde yetki gerekiyorsa bir sonraki bölümde anlatılan policy yapısını tercih edin.

Sonlandırılabilir (terminable) middleware

Yanıt kullanıcıya gönderildikten sonra yapılması gereken işler için middleware sınıfına terminate metodu eklenebilir. Ayrıntılı istek kaydı tutmak veya istatistik göndermek gibi kullanıcının beklemesini gerektirmeyen işler burada yapılabilir. PHP-FPM kullanan sunucularda yanıt istemciye iletildikten sonra bu metot çalışır; ancak bu süre boyunca PHP işçisi meşgul kalır. Uzun süren işler için terminate yerine kuyruk sistemini kullanmak daha doğrudur; ayrıntılar için Laravel queue rehberimize bakabilirsiniz.

Middleware ve yetkilendirme: hangisi ne zaman?

Kimlik doğrulama “bu kişi kim?”, yetkilendirme ise “bu kişi bu kaynağa erişebilir mi?” sorusunu cevaplar. Rotaya auth middleware’i eklemek yalnızca kullanıcının giriş yapmış olduğunu garanti eder; giriş yapmış bir kullanıcının başkasına ait bir siparişi veya faturayı görüntülemesini engellemez.

Kayıt düzeyinde yetki kontrolü için Laravel’in policy ve gate yapılarını kullanın. Örneğin bir SiparisPolicy içinde view metodu siparişin kullanıcıya ait olup olmadığını kontrol eder; controller’da $this->authorize('view', $siparis) veya Gate::authorize() ile bu kural çalıştırılır. Policy’leri rotaya bağlamak isterseniz Laravel’in yerleşik can middleware’i de kullanılabilir:

Route::get('/siparisler/{siparis}', [SiparisController::class, 'show'])
    ->middleware('can:view,siparis');

Bu ayrım önemlidir çünkü web uygulamalarındaki en yaygın güvenlik açıklarından biri olan IDOR (doğrudan nesne referansı) sorunu, tam olarak yalnızca oturum kontrolü yapılıp kayıt sahipliği kontrol edilmediğinde ortaya çıkar.

Middleware mi, policy mi?
auth yalnızca 'giriş yaptı mı?' sorusunu cevaplar; kayıt sahipliğini kontrol etmez.

Middleware sırası ve öncelik

Birden fazla middleware aynı rotaya bağlandığında çalışma sırası sonucu doğrudan etkiler. Laravel, rotaya bağladığınız sırayı genellikle korur; ancak oturum başlatma, kimlik doğrulama ve rota model bağlama (route model binding) gibi bazı çekirdek katmanlar için bir öncelik listesi tutar ve bunları her zaman doğru sırada çalıştırır. Kendi middleware’inizin bu çekirdek katmanlardan önce veya sonra çalışması gerekiyorsa bootstrap/app.php içinde priority ayarıyla öncelik listesini düzenleyebilirsiniz.

Sıralamayı planlarken şu basit kuralı izleyin: ucuz ve isteği erken reddedebilen kontroller önce, pahalı ve yanıtı değiştiren işlemler sonra çalışmalıdır. Örneğin oran sınırı kontrolü, veritabanından kullanıcı abonelik bilgisini okuyan bir middleware’den önce gelmelidir; böylece kötü niyetli yoğun istekler daha veritabanına ulaşmadan durdurulur.

Hata ayıklama ve performans

Bir rotanın hangi middleware’lerden geçtiğini görmek için php artisan route:list -v komutunu kullanabilirsiniz. Ayrıntılı çıktı, her rotaya bağlı middleware listesini gösterir ve “bu rota neden 403 dönüyor?” sorusunu cevaplamanın en hızlı yoludur. Belirli bir rotayı aramak için --path=yonetim parametresiyle listeyi daraltın.

Performans açısından middleware’ler her istekte çalıştığı için küçük gecikmeler toplandığında belirgin hale gelir. Canlı ortamda php artisan route:cache ve php artisan config:cache komutlarıyla rota ve yapılandırma önbelleğini oluşturmak, middleware çözümleme maliyetini de azaltır. Middleware içinde dış servise HTTP isteği yapmaktan kaçının; zorunluysa kısa bir zaman aşımı belirleyin ve sonucu önbelleğe alın. Aksi halde dış servisteki bir yavaşlama, uygulamanızın tüm sayfalarını yavaşlatır.

Hata durumlarını anlamak için middleware içinde neden reddettiğinizi kısa bir log kaydıyla belirtmek, özellikle canlı ortamda kullanıcı şikâyetlerini incelerken zaman kazandırır. Bu kayıtların nerede tutulduğunu ve nasıl okunacağını Laravel log dosyaları rehberinde bulabilirsiniz.

Sık karşılaşılan hatalar

  • Middleware içinde iş mantığı: Sipariş toplamı hesaplamak veya e-posta göndermek middleware’in işi değildir. Middleware ince ve tek amaçlı kalmalıdır.
  • Sıralama hatası: Kullanıcı bilgisine ihtiyaç duyan bir middleware, oturumu başlatan katmandan önce çalışırsa $request->user() boş döner. Middleware’leri listelediğiniz sıra çalışma sırasını belirler.
  • API rotalarında CSRF beklemek: api grubunda oturum ve CSRF katmanları yoktur. Token tabanlı kimlik doğrulama kullanılmalıdır.
  • Yönlendirme döngüsü: “Profilini tamamlamamış kullanıcıyı profil sayfasına yönlendir” gibi bir middleware’i profil sayfasının kendisine de uygularsanız sonsuz döngü oluşur. Hedef rotayı istisna olarak tanımlayın.
  • Ağır sorgular: Her istekte çalışan bir middleware içinde önbelleksiz veritabanı sorgusu yapmak, trafik arttığında ciddi performans sorununa dönüşür.

Middleware’i test etmek

Middleware davranışını en güvenilir biçimde özellik (feature) testleriyle doğrulayabilirsiniz. Her kritik rota için en az üç senaryo yazın: yetkili kullanıcı, anonim kullanıcı ve başka bir kullanıcının kaydına erişmeye çalışan kullanıcı.

public function test_yonetici_olmayan_kullanici_panele_giremez(): void
{
    $kullanici = User::factory()->create(['is_admin' => false]);

    $this->actingAs($kullanici)
         ->get('/yonetim/panel')
         ->assertForbidden();
}

public function test_anonim_kullanici_girise_yonlendirilir(): void
{
    $this->get('/yonetim/panel')->assertRedirect('/login');
}

Testlerde belirli bir middleware’i geçici olarak devre dışı bırakmak için withoutMiddleware() metodu kullanılabilir; ancak güvenlik middleware’lerini devre dışı bırakarak yazılan testler, gerçek davranışı doğrulamadığı için dikkatli kullanılmalıdır.

Gerçek bir senaryo: bakımdaki modül

Bir e-ticaret uygulamasında ödeme modülünü güncellerken yalnızca ödeme sayfalarını geçici olarak kapatmak istediğinizi düşünün. Tüm siteyi bakım moduna almak yerine, bir yapılandırma değerini kontrol eden küçük bir middleware yazıp yalnızca ödeme rota grubuna bağlayabilirsiniz. Yapılandırma açıkken middleware kullanıcıya bilgilendirici bir sayfa döndürür, yöneticilerin ise test için geçmesine izin verir. Bu yaklaşım tek satırlık bir değişiklikle açılıp kapatılabilir ve diğer sayfaların çalışmasını etkilemez.

Sıkça Sorulan Sorular

Laravel 11’de Kernel.php nerede?

Laravel 11 ile oluşturulan yeni projelerde app/Http/Kernel.php dosyası bulunmaz; middleware kayıtları bootstrap/app.php içindeki withMiddleware bölümünde yapılır. Eski sürümden yükseltilen projelerde dosya hâlâ kullanılıyor olabilir.

Middleware mi kullanmalıyım, policy mi?

Rotanın tamamını ilgilendiren genel kontroller (giriş yapmış olmak, e-postayı doğrulamış olmak, belirli bir role sahip olmak) için middleware; belirli bir kaydın sahipliğine veya durumuna bağlı kontroller için policy kullanın.

Bir rotayı gruptaki middleware’den hariç tutabilir miyim?

Evet. Rota tanımında withoutMiddleware() metodu ile gruptan gelen belirli bir middleware’i o rota için devre dışı bırakabilirsiniz. Global middleware’ler bu yöntemle kaldırılamaz.

Middleware’de veritabanı sorgusu yapmak sorun mu?

Sorun değil, ancak her istekte çalışacağını unutmayın. Sık değişmeyen veriler için önbellek kullanın ve sorguyu yalnızca gerçekten ihtiyaç duyulan rotalarda çalıştırın.

Middleware içinde yönlendirme mi yapmalıyım, hata mı döndürmeliyim?

Kullanıcının düzeltebileceği durumlarda (giriş yapmamış olmak, e-postayı doğrulamamış olmak, profili eksik olmak) ilgili sayfaya yönlendirmek iyi bir deneyimdir. Kullanıcının yetkisi olmayan bir alana erişmeye çalıştığı durumlarda ise 403 döndürmek daha doğrudur. API isteklerinde yönlendirme yerine her zaman uygun durum kodu ve JSON hata mesajı döndürün; aksi halde istemci bir HTML giriş sayfası alır ve hatayı anlamlandıramaz.

Aynı middleware’i hem web hem API rotalarında kullanabilir miyim?

Kullanabilirsiniz; ancak oturuma veya çerezlere bağımlı olmamasına dikkat edin. İsteğin JSON beklediğini $request->expectsJson() ile kontrol edip yanıtı buna göre biçimlendirmek, aynı middleware’in iki ortamda da doğru çalışmasını sağlar.

İlgili Rehberler

Kaynaklar ve İleri Okuma

özgür BAYRAM

Özgür Bayram, WordPress, Laravel, yapay zekâ ve web performansı alanlarında çalışan bir yazılım geliştiricisidir. Bilim Meraklısı’nda teknoloji, yazılım, hosting, SEO ve dijital araçlar hakkında anlaşılır, uygulanabilir rehberler hazırlar. Amacı, teknik konuları sade bir dille anlatarak okuyucuların doğru kararlar vermesine ve sorunlarını güvenle çözmesine yardımcı olmaktır.

İlgili Makaleler

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Başa dön tuşu