SOLID Neden Önemlidir (Pragmatik Uygulandığında)
SOLID prensipleri taşa kazınmış kurallar değildir. Düşünceli bir şekilde uygulandığında kodunuzu anlamayı, test etmeyi ve değiştirmeyi kolaylaştıran yönergelerdir. Anahtar kelime düşüncelidir -- SOLID'e körü körüne bağlılık, çözdüğü sorundan daha karmaşık kod üretebilir.
Her prensibin gerçek bir Laravel uygulamasında nasıl göründüğünü inceleyelim.
S -- Single Responsibility Principle (Tek Sorumluluk Prensibi)
"Bir sınıfın değişmek için yalnızca bir nedeni olmalıdır."
Bu, "bir sınıf yalnızca tek bir şey yapmalıdır" anlamına gelmez. Bir sınıfın tek bir aktör veya paydaşa karşı sorumlu olması gerektiği anlamına gelir.
Öncesi: Çok fazla iş yapan controller
class OrderController extends Controller
{
public function store(Request $request)
{
// Validation
$validated = $request->validate([
'items' => 'required|array',
'payment_method' => 'required|string',
]);
// Business logic: calculate totals
$subtotal = 0;
foreach ($validated['items'] as $item) {
$product = Product::findOrFail($item['id']);
$subtotal += $product->price * $item['quantity'];
}
$tax = $subtotal * 0.1;
$total = $subtotal + $tax;
// Persistence
$order = Order::create([
'user_id' => auth()->id(),
'subtotal' => $subtotal,
'tax' => $tax,
'total' => $total,
]);
// Payment processing
Stripe::charges()->create([
'amount' => $total * 100,
'currency' => 'usd',
'source' => $validated['payment_method'],
]);
// Notification
Mail::to(auth()->user())->send(new OrderConfirmation($order));
return redirect()->route('orders.show', $order);
}
}
Sonrası: Her sorumluluk kendi yerinde
class OrderController extends Controller
{
public function store(StoreOrderRequest $request, PlaceOrder $placeOrder)
{
$order = $placeOrder->execute(
PlaceOrderData::fromRequest($request)
);
return redirect()->route('orders.show', $order);
}
}
class PlaceOrder
{
public function __construct(
private readonly PaymentGateway $payments,
) {}
public function execute(PlaceOrderData $data): Order
{
$order = Order::create($data->toArray());
$this->payments->charge($order->total, $data->paymentMethod);
event(new OrderPlaced($order));
return $order;
}
}
// Side effects handled by event listeners
// OrderPlaced → SendConfirmationEmail
// OrderPlaced → UpdateInventory
// OrderPlaced → NotifySalesTeam
O -- Open/Closed Principle (Açık/Kapalı Prensibi)
"Genişlemeye açık, değişikliğe kapalı." Pratikte bu, mevcut kodu değiştirmeden yeni davranış ekleyebileceğiniz anlamına gelir.
// Instead of a growing switch statement...
class NotificationSender
{
public function send(User $user, string $message, string $channel)
{
match($channel) {
'email' => $this->sendEmail($user, $message),
'sms' => $this->sendSms($user, $message),
'slack' => $this->sendSlack($user, $message),
// Adding a new channel = modifying this class
};
}
}
// ...use an interface and let Laravel's container resolve it
interface NotificationChannel
{
public function send(User $user, string $message): void;
}
class EmailChannel implements NotificationChannel { /* ... */ }
class SmsChannel implements NotificationChannel { /* ... */ }
class SlackChannel implements NotificationChannel { /* ... */ }
// Register in a service provider
$this->app->tag([EmailChannel::class, SmsChannel::class, SlackChannel::class], 'notification.channels');
// Adding Telegram? Just create a new class and tag it. No existing code changes.
class TelegramChannel implements NotificationChannel { /* ... */ }
L -- Liskov Substitution Principle (Liskov Yerine Geçme Prensibi)
"Alt türler, temel türlerinin yerine geçebilir olmalıdır." Kodunuz bir Logger bekliyorsa, herhangi bir Logger uygulaması bir şeyleri bozmadan çalışmalıdır.
// Violation: CachedRepository changes behavior in unexpected ways
class CachedProductRepository extends ProductRepository
{
public function find(int $id): ?Product
{
return Cache::remember("product.{$id}", 3600, function () use ($id) {
return parent::find($id);
});
}
public function save(Product $product): void
{
parent::save($product);
// But what if callers expect find() to return the latest data
// immediately after save()? The cache might serve stale data.
// This violates LSP.
}
}
// Better: make caching explicit and invalidate properly
class CachedProductRepository extends ProductRepository
{
public function save(Product $product): void
{
parent::save($product);
Cache::forget("product.{$product->id}");
}
}
I -- Interface Segregation Principle (Arayüz Ayrımı Prensibi)
"Hiçbir istemci, kullanmadığı metotlara bağımlı olmaya zorlanmamalıdır."
// Too broad: not every export needs all these methods
interface Exportable
{
public function toCsv(): string;
public function toPdf(): string;
public function toExcel(): string;
public function toJson(): string;
}
// Better: small, focused interfaces
interface CsvExportable
{
public function toCsv(): string;
}
interface PdfExportable
{
public function toPdf(): string;
}
// Classes implement only what they need
class InvoiceReport implements CsvExportable, PdfExportable
{
public function toCsv(): string { /* ... */ }
public function toPdf(): string { /* ... */ }
}
class AuditLog implements CsvExportable
{
public function toCsv(): string { /* ... */ }
// No need to implement PDF — audit logs are CSV-only
}
D -- Dependency Inversion Principle (Bağımlılık Tersine Çevirme Prensibi)
"Soyutlamalara bağımlı olun, somut sınıflara değil." Laravel'in service container'ının parladığı yer burasıdır.
// Tightly coupled to a specific implementation
class OrderService
{
public function sendConfirmation(Order $order): void
{
$mailer = new SendGridMailer(); // Hard dependency
$mailer->send($order->user->email, 'Your order is confirmed');
}
}
// Inverted: depend on the interface
class OrderService
{
public function __construct(
private readonly Mailer $mailer // Interface, not concrete class
) {}
public function sendConfirmation(Order $order): void
{
$this->mailer->send($order->user->email, 'Your order is confirmed');
}
}
// Bind in AppServiceProvider
$this->app->bind(Mailer::class, SendGridMailer::class);
// In tests, swap for a fake
$this->app->bind(Mailer::class, FakeMailer::class);
Pragmatik Yaklaşım
SOLID prensiplerini önceden uygulamayın. Bunların yokluğunun acısını hissettiğinizde uygulayın -- bir sınıfı test etmek zor olduğunda, bir özellik eklemek beş dosyayı değiştirmeyi gerektirdiğinde, bir yerdeki hata düzeltmesi başka bir yeri bozduğunda.
- SRP: Bir sınıfı isimlendirmek veya test etmek zorlaştığında yeniden düzenleyin
- OCP: Aynı kavramın 3 veya daha fazla uygulaması olduğunda interface'ler tanıtın
- LSP: Beklenmedik istisnalar fırlatan veya dönüş türlerini değiştiren alt sınıflara dikkat edin
- ISP: Uygulamalar
nulldöndürmeye veya "desteklenmiyor" hatası fırlatmaya başladığında interface'leri bölün - DIP: Uygulamaları değiştirmeniz gerektiğinde (test, çoklu sağlayıcı, vb.) bağımlılıkları enjekte edin