Üretim Kalitesinde Laravel Kodu
Gerçek projelerde kullandığımız mimari kalıplar, tasarım desenleri ve en iyi pratikler. Hello World değil, production-ready kod.
<?php
declare(strict_types=1);
namespace App\Models;
use App\Enums\OrderStatus;
use App\Observers\OrderObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\Activitylog\LogOptions;
use Spatie\Activitylog\Traits\LogsActivity;
#[ObservedBy([OrderObserver::class])]
class Order extends Model
{
use HasFactory, SoftDeletes, LogsActivity;
protected $fillable = [
'user_id',
'status',
'subtotal',
'discount_amount',
'tax_amount',
'total_amount',
'currency',
'shipping_address',
'billing_address',
'notes',
'metadata',
'paid_at',
'shipped_at',
];
protected function casts(): array
{
return [
'status' => OrderStatus::class,
'subtotal' => 'decimal:2',
'discount_amount' => 'decimal:2',
'tax_amount' => 'decimal:2',
'total_amount' => 'decimal:2',
'shipping_address' => 'array',
'billing_address' => 'array',
'metadata' => 'array',
'paid_at' => 'datetime',
'shipped_at' => 'datetime',
];
}
// ─── Activity Log Yapılandırması ───────────────────────
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnly(['status', 'total_amount', 'paid_at', 'shipped_at'])
->logOnlyDirty()
->dontSubmitEmptyLogs()
->setDescriptionForEvent(fn (string $eventName) => "Sipariş {$eventName}");
}
// ─── İlişkiler ─────────────────────────────────────────
public function user(): BelongsTo
{
return $this->belongsTo(User::class)->withTrashed();
}
public function products(): BelongsToMany
{
return $this->belongsToMany(Product::class, 'order_items')
->withPivot(['quantity', 'unit_price', 'discount_rate', 'line_total'])
->withTimestamps()
->using(OrderItem::class);
}
public function payments(): HasMany
{
return $this->hasMany(Payment::class)->latestOfMany();
}
// ─── Local Scope'lar ───────────────────────────────────
public function scopePending(Builder $query): Builder
{
return $query->where('status', OrderStatus::Pending)
->whereNull('paid_at');
}
public function scopeForUser(Builder $query, int $userId): Builder
{
return $query->where('user_id', $userId);
}
public function scopeWithinDateRange(
Builder $query,
string $from,
string $to
): Builder {
return $query->whereBetween('created_at', [$from, $to]);
}
// ─── Accessor'lar ──────────────────────────────────────
protected function formattedTotal(): Attribute
{
return Attribute::make(
get: fn () => number_format((float) $this->total_amount, 2, ',', '.') . ' ' . $this->currency,
);
}
public function isEditable(): bool
{
return in_array($this->status, [
OrderStatus::Pending,
OrderStatus::Processing,
]);
}
}
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\StoreOrderRequest;
use App\Http\Requests\Api\UpdateOrderRequest;
use App\Http\Resources\OrderResource;
use App\Jobs\ProcessOrderJob;
use App\Services\OrderService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Spatie\QueryBuilder\AllowedFilter;
use Spatie\QueryBuilder\QueryBuilder;
class OrderController extends Controller
{
public function __construct(
private readonly OrderService $orderService,
) {
$this->middleware(['auth:sanctum', 'verified']);
$this->middleware('throttle:orders')->only(['store', 'update']);
}
/**
* Kimliği doğrulanmış kullanıcının siparişlerini filtreli + sayfalı döndürür.
*/
public function index(Request $request): AnonymousResourceCollection
{
$orders = QueryBuilder::for(
$this->orderService->getUserOrdersQuery($request->user()->id)
)
->allowedFilters([
AllowedFilter::exact('status'),
AllowedFilter::scope('within_date_range', 'withinDateRange'),
AllowedFilter::partial('notes'),
])
->allowedSorts(['created_at', 'total_amount', 'status'])
->allowedIncludes(['products', 'payments', 'user'])
->defaultSort('-created_at')
->paginate($request->integer('per_page', 15))
->withQueryString();
return OrderResource::collection($orders);
}
/**
* Yeni sipariş oluşturur ve arka plan kuyruğuna gönderir.
*/
public function store(StoreOrderRequest $request): JsonResponse
{
$order = $this->orderService->createOrder(
userId: $request->user()->id,
data: $request->validated(),
);
// Asenkron işlem: ödeme, stok güncelleme ve bildirimler
ProcessOrderJob::dispatch($order)
->onQueue('orders')
->delay(now()->addSeconds(5));
return OrderResource::make($order)
->response()
->setStatusCode(201);
}
/**
* Belirtilen siparişi günceller (sadece düzenlenebilir durumda ise).
*/
public function update(UpdateOrderRequest $request, int $orderId): OrderResource
{
$order = $this->orderService->findForUser(
orderId: $orderId,
userId: $request->user()->id,
);
abort_unless($order->isEditable(), 422, 'Bu sipariş artık düzenlenemiyor.');
$updated = $this->orderService->updateOrder($order, $request->validated());
return OrderResource::make($updated);
}
/**
* Siparişi iptal eder ve stok iadesi başlatır.
*/
public function cancel(Request $request, int $orderId): JsonResponse
{
$order = $this->orderService->findForUser(
orderId: $orderId,
userId: $request->user()->id,
);
$this->orderService->cancelOrder($order, $request->input('reason'));
return response()->json([
'message' => 'Sipariş başarıyla iptal edildi.',
'data' => OrderResource::make($order->fresh()),
]);
}
}
<?php
declare(strict_types=1);
namespace App\Jobs;
use App\Models\Order;
use App\Services\InventoryService;
use App\Services\NotificationService;
use App\Services\PaymentService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Throwable;
class ProcessOrderJob implements ShouldQueue, ShouldBeUnique
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/** Maksimum deneme sayısı */
public int $tries = 3;
/** Başarısız denemeler arası bekleme (saniye) */
public array $backoff = [60, 180, 600];
/** Job zaman aşımı süresi (saniye) */
public int $timeout = 120;
/** Unique lock süresi (saniye) */
public int $uniqueFor = 3600;
public function __construct(
private readonly Order $order,
) {}
/** Unique lock key'i — aynı sipariş için paralel işlem önlenir */
public function uniqueId(): string
{
return "order:{$this->order->id}";
}
/**
* İş mantığı: Ödeme → Stok → Bildirim (tek transaction'da)
*/
public function handle(
PaymentService $paymentService,
InventoryService $inventoryService,
NotificationService $notificationService,
): void {
Log::info('Sipariş işleme başladı', [
'order_id' => $this->order->id,
'attempt' => $this->attempts(),
]);
DB::transaction(function () use ($paymentService, $inventoryService, $notificationService) {
// 1. Ödeme tahsilatı
$payment = $paymentService->charge(
order: $this->order,
method: $this->order->metadata['payment_method'] ?? 'card',
);
// 2. Stok düşümü (atomik — race condition'a karşı korumalı)
$inventoryService->decrementBulk(
items: $this->order->products()
->withPivot(['quantity'])
->get()
->map(fn ($p) => [
'product_id' => $p->id,
'quantity' => $p->pivot->quantity,
])->all(),
);
// 3. Sipariş durumu ve ödeme zamanı güncelleme
$this->order->update([
'status' => \App\Enums\OrderStatus::Processing,
'paid_at' => now(),
]);
}); // transaction commit
// Transaction dışı: bildirimler (idem-potent olduğu için)
$notificationService->sendOrderConfirmation($this->order);
Log::info('Sipariş işleme tamamlandı', ['order_id' => $this->order->id]);
}
/**
* Tüm denemeler başarısız olduğunda çağrılır.
*/
public function failed(Throwable $exception): void
{
Log::error('Sipariş işleme başarısız', [
'order_id' => $this->order->id,
'exception' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Sipariş durumunu hata olarak işaretle
$this->order->update(['status' => \App\Enums\OrderStatus::Failed]);
// Müşteri ve ekibe acil bildirim gönder
\App\Notifications\OrderProcessingFailed::dispatch($this->order, $exception->getMessage());
}
}
<?php
declare(strict_types=1);
namespace App\Providers;
use App\Contracts\PaymentGatewayInterface;
use App\Contracts\Repositories\OrderRepositoryInterface;
use App\Contracts\Repositories\ProductRepositoryInterface;
use App\Contracts\Repositories\UserRepositoryInterface;
use App\Repositories\Eloquent\EloquentOrderRepository;
use App\Repositories\Eloquent\EloquentProductRepository;
use App\Repositories\Eloquent\EloquentUserRepository;
use App\Services\Payment\FakePaymentGateway;
use App\Services\Payment\IyzicoPaymentGateway;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\ServiceProvider;
class RepositoryServiceProvider extends ServiceProvider
{
/**
* Otomatik bağlantılar — interface → implementation
* Laravel bu eşlemeleri IoC container'a otomatik kaydeder.
*/
public array $bindings = [
OrderRepositoryInterface::class => EloquentOrderRepository::class,
ProductRepositoryInterface::class => EloquentProductRepository::class,
UserRepositoryInterface::class => EloquentUserRepository::class,
];
/**
* Manuel/koşullu servis kayıtları
*/
public function register(): void
{
// Test ortamında sahte (fake) ödeme gateway'i kullan
// Production/staging'de gerçek Iyzico gateway'i kullan
$this->app->singleton(
abstract: PaymentGatewayInterface::class,
concrete: function (Application $app): PaymentGatewayInterface {
if ($app->environment('testing')) {
return new FakePaymentGateway();
}
return new IyzicoPaymentGateway(
apiKey: config('services.iyzico.api_key'),
secretKey: config('services.iyzico.secret_key'),
baseUri: config('services.iyzico.base_uri'),
sandbox: $app->environment('local', 'staging'),
);
},
);
// Cache decorator — repository üzerine önbellekleme katmanı
$this->app->extend(
abstract: ProductRepositoryInterface::class,
closure: function (ProductRepositoryInterface $repository, Application $app) {
return new \App\Repositories\Cache\CachedProductRepository(
repository: $repository,
cache: $app->make('cache.store'),
ttl: now()->addMinutes(30),
);
},
);
}
public function boot(): void
{
// Rate limiter tanımlamaları
\Illuminate\Support\Facades\RateLimiter::for('orders', function ($request) {
return \Illuminate\Cache\RateLimiting\Limit::perMinute(10)
->by($request->user()?->id ?? $request->ip())
->response(fn () => response()->json([
'message' => 'Çok fazla istek gönderdiniz. Lütfen bekleyin.',
], 429));
});
}
}
Kullandığımız Laravel Paketleri
Her projede güvenilirliğini kanıtlamış, topluluk tarafından aktif olarak desteklenen paketler.
SPA ve mobil uygulamalar için hafif API token kimlik doğrulama sistemi. Cookie tabanlı session desteğiyle full-stack projeler için ideal çözüm.
Role ve permission yönetimi için endüstri standardı paket. Blade direktifleri, middleware ve Eloquent entegrasyonuyla tam donanımlı yetkilendirme.
Model üzerindeki tüm değişiklikleri otomatik olarak loglar. Kurumsal uygulamalarda denetim izi (audit trail) için vazgeçilmez bir araç.
Redis tabanlı queue işlemlerini gerçek zamanlı izleyen güzel bir dashboard. İş başarısızlıklarını, gecikmeleri ve iş yükü dağılımını tek ekranda görün.
JSON:API standardına uygun filter, sort ve include parametrelerini Eloquent sorgularına otomatik çeviren güçlü paket. RESTful API'lerde üretkenliği ikiye katlar.
Geliştirme ortamında sorgu sayısını, bellek kullanımını, timeline ve exception detaylarını tarayıcı üzerinde anlık raporlayan debug aracı.
GD ve Imagick sürücü desteğiyle görsel yeniden boyutlandırma, kırpma, watermark ve format dönüştürme işlemlerini zarif bir API ile sunar.
PHPSpreadsheet tabanlı Excel/CSV okuma-yazma kütüphanesi. Chunk import, queue export ve özel formatlar ile büyük veri setlerini kolayca yönetin.
Laravel ile Neler Yapıyoruz?
Farklı sektörlerden büyük ve küçük ölçekli projelerde Laravel'in gücünü pratiğe dökme deneyimlerimiz.
E-ticaret Backend
Ürün kataloğu, sepet yönetimi, ödeme entegrasyonu (Iyzico, Stripe), kargo takibi ve müşteri paneli içeren tam özellikli e-ticaret altyapısı.
SaaS Platformu
Abonelik yönetimi, kullanım bazlı faturalama, feature flag sistemi ve kullanıcı onboarding akışları içeren modern SaaS mimarisi.
Kurumsal CRM / ERP
Müşteri yönetimi, satış pipeline, teklif/fatura oluşturma, envanter takibi ve raporlama modülleriyle kurumsal iş süreçlerini dijitalleştirme.
REST API Servisi
Mobil ve SPA uygulamalar için Sanctum veya Passport tabanlı OAuth2 korumalı, versiyonlu, kapsamlı dokümantasyona sahip RESTful API'ler.
Admin Paneli & Dashboard
Filament veya özel arayüzlerle geliştirilmiş, gerçek zamanlı analitik, raporlama ve içerik yönetimi araçları sunan yönetim ekranları.
Multi-tenant Uygulamalar
Spatie Multitenancy veya özel mimariyle her müşteriye izole veritabanı/şema sunan, farklı domain'lerde çalışan kiracı bazlı uygulamalar.
Laravel Projenizi Birlikte İnşa Edelim
Fikrinizi veya mevcut projenizi ele alalım; mimari tasarımdan deployment'a kadar her adımda yanınızdayız.