SOLID'i PHP'de pratiğe dökmek: tek sorumluluk ilkesi
SOLID'in SRP ilkesini soyut kalmadan PHP sınıflarına nasıl uyguladığımı somut örneklerle anlatıyorum.
SOLID prensiplerine ilk kez bir kitapta rastladığımda “evet, mantıklı” dedim ve geçtim. Sonra aynı prensipler bir konferans sunumunda karşıma çıktı, yine anlamlı geldi. Ama gerçekten içselleştirdiğim an, kendi yazdığım şişirilmiş bir controller’a bakarken oldu: bir sınıf hem veritabanı sorgusu çekiyor, hem e-posta gönderiyor, hem PDF oluşturuyordu.
Tek sorumluluk ilkesi (Single Responsibility Principle — SRP), SOLID’in ilk harfini oluşturuyor. Kısaca: bir sınıfın değişmesi için yalnızca bir nedeni olmalı.
”Tek sorumluluk” ne demek değildir?
İlk duyduğumda “bir sınıf yalnızca bir iş yapsın” anladım. Bu yanlış değil, ama eksik. Daha doğru tanım şu: bir sınıf, yalnızca tek bir aktörün (bir kullanıcı tipinin, bir ihtiyacın) değişiklik talebine muhatap olmalıdır.
Pratikte bu şu anlama geliyor: bir sınıfı değiştirmeniz gerektiğinde, değişikliğin sebebi hep aynı kaynaktan geliyor olmalı. Hem e-posta ekibinin hem de muhasebe ekibinin taleplerini karşılayan bir sınıf, iki farklı değişiklik nedenine sahip demektir.
Sorunlu bir örnek
Kullanıcı kaydı yapan bir sınıf düşünelim:
<?php
class UserRegistration
{
public function register(array $data): void
{
// Doğrulama
if (empty($data['email']) || empty($data['password'])) {
throw new \InvalidArgumentException('E-posta ve parola zorunludur.');
}
// Veritabanına kaydet
$pdo = new \PDO('mysql:host=localhost;dbname=app', 'root', '');
$stmt = $pdo->prepare('INSERT INTO users (email, password) VALUES (?, ?)');
$stmt->execute([$data['email'], password_hash($data['password'], PASSWORD_BCRYPT)]);
// Hoşgeldin e-postası gönder
mail($data['email'], 'Hoşgeldiniz', 'Kaydınız tamamlandı.');
}
}
Bu sınıf üç farklı kaygıyı (concern) bir arada taşıyor: doğrulama, kayıt ve bildirim. Parola hashleme algoritmasını değiştirmek, e-posta içeriğini güncellemek ya da doğrulama kurallarına alan eklemek — her biri bu sınıfa dokunmak anlamına geliyor.
Sorumlulukları ayırmak
Her kaygıyı kendi sınıfına taşırsak:
<?php
class UserValidator
{
public function validate(array $data): void
{
if (empty($data['email']) || !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException('Geçerli bir e-posta adresi giriniz.');
}
if (empty($data['password']) || strlen($data['password']) < 8) {
throw new \InvalidArgumentException('Parola en az 8 karakter olmalıdır.');
}
}
}
class UserRepository
{
private \PDO $pdo;
public function __construct(\PDO $pdo)
{
$this->pdo = $pdo;
}
public function save(string $email, string $password): int
{
$stmt = $this->pdo->prepare(
'INSERT INTO users (email, password) VALUES (?, ?)'
);
$stmt->execute([$email, password_hash($password, PASSWORD_BCRYPT)]);
return (int) $this->pdo->lastInsertId();
}
}
class WelcomeMailer
{
public function send(string $email): void
{
mail($email, 'Hoşgeldiniz', 'Kaydınız tamamlandı.');
}
}
class UserRegistration
{
public function __construct(
private UserValidator $validator,
private UserRepository $repository,
private WelcomeMailer $mailer
) {}
public function register(array $data): int
{
$this->validator->validate($data);
$userId = $this->repository->save($data['email'], $data['password']);
$this->mailer->send($data['email']);
return $userId;
}
}
Artık UserRegistration yalnızca kayıt akışını koordine ediyor. Doğrulama kuralları değişirse yalnızca UserValidator güncelleniyor. E-posta gönderme kütüphanesi değişirse yalnızca WelcomeMailer değişiyor.
”Çok sınıf oldu” itirazı
Bu yapıyı gösteren zaman en sık duyduğum şey şu: “ama çok fazla sınıf oluyor.” Bu itirazı anlıyorum; başlangıçta biraz fazla parçalı hissettiriyor. Ama deneyimle şunu fark ettim: altı ay sonra “şu doğrulama sınıfını bul ve şu kuralı ekle” yapmak, üç yüz satırlık bir God Class (tanrı sınıf) içinde ilgili satırı bulmaya çalışmaktan çok daha kolay.
Sınıf sayısı ile karmaşıklık arasında doğrudan bir ilişki yok. Karmaşıklık; sınıfların birbirine ne kadar sıkı bağlı olduğundan, değişikliğin ne kadar geniş bir yüzeyi etkilediğinden geliyor.
Test yazabilmek SRP’nin asıl göstergesidir
SRP’yi benimsemeden önce test yazmayı denediğimde hep bir engelle karşılaşıyordum: sınıf çok fazla şey yapıyordu, her testi kurmak için gerçek bir veritabanı bağlantısı veya mail sunucusu gerekiyordu. Sorumlulukları ayırdıktan sonra UserValidator’ı bağımsız test edebiliyorum; ne veritabanı var ne e-posta. Sadece geçerli/geçersiz veri gönderiyorum ve davranışı doğruluyorum.
Bu bağlantıyı fark etmek önemli: bir sınıfı izole biçimde test etmek zorlaşıyorsa, muhtemelen birden fazla sorumluluğu birlikte taşıyordur. Test zorluğu SRP ihlaline işaret eden erken bir uyarı.
Nerede durmalı?
SRP’yi aşırıya kaçırmak da mümkün. Her satırı ayrı bir sınıfa taşırsanız gereksiz bir dağıtım ortaya çıkıyor. Benim pratik kural olarak baktığım yer şu: bir sınıfı değiştirmem için birden fazla bağımsız neden varsa, bu sınıfın bölünmesi gerekiyor demektir. Tek bir neden varsa, şu halinde bırakabilirim.
Örneğin WelcomeMailer’ı ileride hem HTML şablon desteği hem de farklı dil seçeneği eklemek üzere genişletmek istediğimde, bu ihtiyaçlar hâlâ aynı aktörden geliyorsa (e-posta bildirimi sorumluluğu) ikisi aynı sınıfta kalabilir. Ama bir gün bu sınıf hem pazarlama e-postası hem de işlemsel bildirim göndermeye başlarsa, o zaman yeniden bölünme vakti gelmiş demektir.
SOLID ilkelerinin geri kalanına da bakmayı düşünüyorum. Soyut bir ilkeyi “oh evet çok iyi fikir” düzeyinden kendi kodumda “bu şekilde yazıyorum” düzeyine taşımak, farkında olmaksızın uzun zaman aldı.