# Laravel policy ve gate ile yetkilendirme

> Laravel'de Gate ve Policy ile yetkilendirme mantığını tek yerde toplamak; 'kim neyi yapabilir' kararlarını temiz kurmak.

- Yayın: 2017-10-01
- Kategori: Web Geliştirme
- Etiketler: Laravel
- Okuma süresi: 3 dk okuma
- Kaynak: https://www.muhammetsafak.com.tr/blog/laravel-policy-ve-gate-ile-yetkilendirme/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
Yetkilendirme — yani "bu kullanıcı bu işlemi yapabilir mi?" sorusu — her uygulamanın bir yerinde yanıtlanmak zorunda. Soru basit ama yanıtın nerede verildiği önemli. Controller içinde `if ($user->id !== $post->user_id)` gibi kontroller yazmak başlangıçta masum görünüyor; ama aynı kontrol birkaç farklı yere dağılınca bir kuralı değiştirmek onlarca yeri dolaşmak anlamına geliyor.

Laravel'in **Gate** ve **Policy** yapısı bu kararları merkezi ve test edilebilir hale getiriyor.

## Gate nedir?

**Gate**, basit, model tabanlı olmayan yetkilendirme kontrolleri için kullanılıyor. `AuthServiceProvider` içinde tanımlanıyor:

```php
use Illuminate\Support\Facades\Gate;

Gate::define('update-settings', function ($user) {
    return $user->is_admin;
});
```

Kullanımı:

```php
if (Gate::allows('update-settings')) {
    // Admin işlemi
}

// Veya kısayol:
Gate::authorize('update-settings'); // İzin yoksa 403 fırlatır
```

Blade şablonlarında da kullanılabiliyor:

```blade
@can('update-settings')
    <a href="/settings">Ayarlar</a>
@endcan
```

## Policy nedir?

**Policy**, bir Eloquent modeline özgü yetkilendirme mantığını toplayan sınıf. Birden fazla eylemi olan modeller için Gate'den daha düzenli bir yapı sunuyor.

Oluşturmak için:

```bash
php artisan make:policy PostPolicy --model=Post
```

Oluşturulan `PostPolicy` sınıfında metotlar yetkilendirme kurallarını tanımlıyor:

```php
namespace App\Policies;

use App\User;
use App\Post;

class PostPolicy
{
    public function update(User $user, Post $post): bool
    {
        return $user->id === $post->user_id;
    }

    public function delete(User $user, Post $post): bool
    {
        return $user->id === $post->user_id || $user->is_admin;
    }
}
```

`AuthServiceProvider` içinde kaydetmek gerekiyor:

```php
protected $policies = [
    Post::class => PostPolicy::class,
];
```

## Policy'yi kullanmak

Controller içinde:

```php
public function update(Request $request, Post $post)
{
    $this->authorize('update', $post);

    // Yetki varsa buraya geliyor
    $post->update($request->validated());
    return redirect()->route('posts.show', $post);
}
```

`authorize('update', $post)` çağrısı, mevcut kullanıcı ve `$post` nesnesiyle `PostPolicy@update` metodunu çalıştırıyor. `false` dönerse otomatik olarak 403 yanıtı üretiyor.

Blade tarafında:

```blade
@can('update', $post)
    <a href="{{ route('posts.edit', $post) }}">Düzenle</a>
@endcan

@can('delete', $post)
    <form action="{{ route('posts.destroy', $post) }}" method="POST">
        @csrf @method('DELETE')
        <button>Sil</button>
    </form>
@endcan
```

## Gate mı, Policy mi?

İkisi arasında seçim yaparken basit bir kural uyguladım:

- Belirli bir modele bağlı değilse ve tek bir kontrol söz konusuysa **Gate** yeterli.
- Bir modelin birden fazla eylemi (görüntüle, güncelle, sil, yayınla...) için kontrol gerekiyorsa **Policy** daha uygun.

Kullanıcı yönetimi, içerik yönetimi gibi alanlarda Policy kurmak, o alanın tüm yetkilendirme mantığını tek dosyada topluyor. Bir kural değiştiğinde tek yer açılıyor.

## Policy'yi test etmek

Bu yapının fark yarattığı bir diğer alan: test edilebilirlik. Policy bir sınıf olduğu için doğrudan örneklenip test edilebiliyor; controller'ı ayağa kaldırmak gerekmiyor:

```php
public function test_kullanici_kendi_yazilarini_guncelleyebilir()
{
    $user = User::factory()->create();
    $post = Post::factory()->for($user)->create();
    $policy = new PostPolicy();

    $this->assertTrue($policy->update($user, $post));
}

public function test_kullanici_baskasinin_yazisini_guncelleyemez()
{
    $yazar  = User::factory()->create();
    $misafir = User::factory()->create();
    $post    = Post::factory()->for($yazar)->create();
    $policy  = new PostPolicy();

    $this->assertFalse($policy->update($misafir, $post));
}
```

Bu testleri controller içine gömülü bir `if` bloğuyla yazmak mümkün değil; o bloğu HTTP isteği üzerinden dolaylı test etmek zorunda kalırdım. Policy sayesinde kural doğrudan, hızlıca ve bağımsız test ediliyor.

## `before` metodu: yönetici istisnası

Policy sınıflarında `before` adlı özel bir metot tanımlayabiliyorsunuz. Bu metot, diğer tüm policy metotlarından önce çalışıyor ve `null` dışında bir değer döndürürse diğer metotlar hiç çalışmıyor:

```php
class PostPolicy
{
    public function before(User $user, string $ability): ?bool
    {
        if ($user->is_superadmin) {
            return true; // Süper admin her şeyi yapabilir
        }

        return null; // Normal akışa devam et
    }

    public function update(User $user, Post $post): bool
    {
        return $user->id === $post->user_id;
    }
}
```

Burada `null` döndürmek önemli: `false` döndürseydiniz süper admin de dahil herkesin erişimini engellerdiniz. `null` dönünce "karar vermedim, diğer metoda bak" anlamına geliyor. Bu farkı ilk kez görenler `false` döndürme hatasına düşebiliyor.

## Politika olmadan sonrası

Bu yapıyı kullanmadan önce yetkilendirme kontrollerini bazı yerlerde controller'a, bazı yerlerde middleware'e yazmıştım. Modelin sahibinin kim olduğuna dair kural üç farklı dosyada biraz farklı şekillerde duruyordu. Policy ile bunların hepsini tek sınıfa topladım; kural değiştiğinde tek dosyaya bakıyorum. Uygulamaya yeni bir rol eklendiğinde veya mevcut bir kural yumuşatıldığında, değişikliği tek bir noktaya yapmak ve testleri çalıştırmak yeterli oluyor. Bu güven, dağıtık `if` bloklarıyla mümkün değildi.

Authentication ile yetkilendirmeyi karıştırmak da başlangıçta yaptığım bir hataydı. Authentication "bu kişi kim?" sorusunu yanıtlar; yetkilendirme ise "bu kişi bunu yapabilir mi?" sorusunu. [Laravel'de](/blog/laravel-5-2-coklu-kimlik-dogrulama-ve-route-gruplari) bu iki katman birbirinden ayrı: `Auth` ve middleware authentication'ı, `Gate` ve `Policy` ise yetkilendirmeyi hallediyor. Bu ayrımı netleştirince nereye ne yazacağım da netleşti. Bir kullanıcı giriş yapmış olabilir ama bu onun her kaynağa erişebileceği anlamına gelmiyor; kimliği doğrulanmış ama yetkisiz — iki farklı kontrol, iki farklı katman.
