# PHP'de arayüz (interface) ve bağımlılık enjeksiyonu pratiği

> PHP'de interface tanımlayarak ve bağımlılık enjeksiyonu uygulayarak sınıfları nasıl gevşek bağlı hale getirdiğimi örneklerle aktarıyorum.

- Yayın: 2016-07-03
- Kategori: Diller
- Etiketler: PHP, OOP
- Okuma süresi: 3 dk okuma
- Kaynak: https://www.muhammetsafak.com.tr/blog/php-de-arayuz-interface-ve-bagimlilik-enjeksiyonu-pratigi/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
E-posta göndermesi gereken bir sınıf yazıyorsunuz. İçine `new SwiftMailer()` yazarsanız ne olur? O sınıf artık SwiftMailer'a sıkı sıkıya bağlıdır. Gün gelir kütüphaneyi değiştirmeniz gerekir ya da test yazarken gerçek e-posta göndermek istemezsiniz — o zaman o sınıfa tekrar dokunmak zorunda kalırsınız.

Interface ve dependency injection, bu sorunu çözmek için birlikte kullanılan iki kavramdır. Birkaç aydır bunları kendi projelerimde daha bilinçli biçimde uygulamaya çalışıyorum; öğrendiklerimi bu yazıda derledim.

## Interface nedir?

**Interface**, bir sınıfın ne yapabileceğini tanımlayan bir sözleşmedir; nasıl yapacağını değil. Interface'i uygulayan (implement eden) sınıf, interface'de tanımlanan tüm metodları gerçekleştirmek zorundadır.

```php
<?php

interface MailerInterface
{
    public function gonder(string $alici, string $konu, string $icerik): bool;
}
```

Bu interface'i uygulayan iki farklı sınıf yazalım:

```php
<?php

class SmtpMailer implements MailerInterface
{
    public function gonder(string $alici, string $konu, string $icerik): bool
    {
        // Gerçek SMTP bağlantısı kurarak e-posta gönder
        return mail($alici, $konu, $icerik);
    }
}

class LogMailer implements MailerInterface
{
    public function gonder(string $alici, string $konu, string $icerik): bool
    {
        // Gerçek e-posta göndermek yerine log dosyasına yaz
        file_put_contents('mail.log', "[{$alici}] {$konu}\n", FILE_APPEND);
        return true;
    }
}
```

Her iki sınıf da `MailerInterface`'i karşılıyor. Dışarıdan bakıldığında ikisi de aynı şeyi yapabiliyor: `gonder()` metodunu kabul ediyor.

## Dependency injection nedir?

**Dependency injection**, bir sınıfın ihtiyaç duyduğu nesneleri kendi içinde oluşturmak yerine dışarıdan almasıdır. Genellikle constructor üzerinden yapılır.

```php
<?php

class UserNotifier
{
    private MailerInterface $mailer;

    public function __construct(MailerInterface $mailer)
    {
        $this->mailer = $mailer;
    }

    public function kayitOlduBildir(string $email): bool
    {
        return $this->mailer->gonder(
            $email,
            'Kaydınız tamamlandı',
            'Sisteme başarıyla kaydoldunuz. Hoşgeldiniz!'
        );
    }
}
```

`UserNotifier` artık `SmtpMailer`'ı bilmiyor. Yalnızca `MailerInterface`'i biliyor. Hangi gerçek uygulamanın kullanılacağı dışarıdan belirleniyor:

```php
<?php

// Üretim ortamı: gerçek e-posta
$notifier = new UserNotifier(new SmtpMailer());
$notifier->kayitOlduBildir('kullanici@example.com');

// Geliştirme ortamı: yalnızca loglama
$notifier = new UserNotifier(new LogMailer());
$notifier->kayitOlduBildir('test@example.com');
```

`UserNotifier` değişmiyor. Davranışı, dışarıdan enjekte ettiğiniz nesneyle şekilleniyor.

Bu kalıp ilk bakışta fazla dolaylı görünüyor. Ama bir düşünün: SwiftMailer'dan başka bir kütüphaneye geçmeniz gerektiğinde `UserNotifier`'a hiç dokunmuyorsunuz. Yeni bir `SmtpMailer` sınıfı yazıyorsunuz, interface'i karşılıyor mu diye PHP kontrol ediyor, bitti. `UserNotifier`'ı test ederken gerçek e-posta sunucusuna bağlanmanız gerekmiyor. Bu ikisi çözdüğü sorun.

## Test yazarken bu farkı açıkça görüyorsunuz

`UserNotifier`'ı test etmek istediğinizde gerçek e-posta göndermek istemezsiniz. Interface sayesinde test için özel bir uygulama yazabiliyorsunuz:

```php
<?php

class FakeMailer implements MailerInterface
{
    public array $gonderilen = [];

    public function gonder(string $alici, string $konu, string $icerik): bool
    {
        $this->gonderilen[] = compact('alici', 'konu', 'icerik');
        return true;
    }
}

// Test kodu
$fakeMailer = new FakeMailer();
$notifier   = new UserNotifier($fakeMailer);

$notifier->kayitOlduBildir('test@example.com');

assert(count($fakeMailer->gonderilen) === 1);
assert($fakeMailer->gonderilen[0]['alici'] === 'test@example.com');
```

Gerçek SMTP bağlantısı yok, test hızlı çalışıyor ve neyin gönderildiğini doğrulayabiliyorsunuz.

Arayüz olmadan bu testi yazmak için `SmtpMailer` sınıfını test sırasında değiştirmek ya da `UserNotifier`'ın içine koşullu mantık eklemek gerekir. İkisi de kötü seçenek.

## Arayüzü her yerde kullanmak zorunda değilsiniz

Bir sınıfın yalnızca tek bir uygulaması olacaksa ve değişmesi planlanmıyorsa, arayüz eklemek gereksiz karmaşıklık yaratabilir. Arayüzün değerini hissettiren durumlar şunlar:

- Birden fazla uygulaması olacak şeyler (ödeme, e-posta, depolama gibi).
- Test sırasında gerçek uygulama yerine sahte (fake/mock) kullanmak istediğiniz bağımlılıklar.
- Dışarıdan konfigüre edilmesi beklenen davranışlar.

Her sınıfa arayüz eklemek zorunlu değil; doğru yerde kullanmak önemli.

Başlarken yaptığım hata her şeye arayüz eklemek oldu. `UserRepository` var, `UserRepositoryInterface` var. `OrderService` var, `OrderServiceInterface` var. Proje bir süre sonra gerçek sınıf sayısının iki katı dosyaya sahip oldu; arayüzlerin çoğu tek bir uygulamaya sahipti ve hiçbir zaman değişmedi. Bu gereksiz soyutlamadan vazgeçmek biraz zaman aldı.

## Laravel container bunu kolaylaştırıyor

Laravel'de arayüzü somut bir sınıfa bağlamak için servis container'ı kullanıyorum:

```php
// AppServiceProvider veya ayrı bir provider içinde
$this->app->bind(MailerInterface::class, SmtpMailer::class);
```

Artık `UserNotifier` container üzerinden oluşturulduğunda, Laravel `MailerInterface` bağımlılığını otomatik olarak `SmtpMailer` ile karşılıyor. Ortam değişkenine göre farklı bir uygulama bağlamak da mümkün:

```php
if (app()->environment('production')) {
    $this->app->bind(MailerInterface::class, SmtpMailer::class);
} else {
    $this->app->bind(MailerInterface::class, LogMailer::class);
}
```

Bu kayıt bir kez yapılıyor; bundan sonra `UserNotifier`'ı nerede oluşturursanız oluşturun, doğru mailer otomatik enjekte ediliyor.

Bu iki kavramı bir arada kullanmak, başlangıçta biraz soyut hissettiriyor. Ama pratiğe döküldüğünde kod tabanının ne kadar esnekleştiğini görünce mantığı oturuyor.
