Eloquent ORM: Laravel için desenler ve optimizasyonlar
Eloquent ORM'i ileri düzey desenler ve optimizasyon teknikleriyle ustalaşın. Laravel uygulamaları için eager loading, query scope, accessor, mutator ve performans.

Eloquent ORM, veritabanı etkileşimlerini akıcı ve ifade gücü yüksek işlemlere dönüştürür. Şık söz diziminin ötesinde, ileri düzey desenleri ve optimizasyon tekniklerini ustalaşmak Laravel uygulamalarının üretimdeki performansını belirler.
N+1 problemi Eloquent uygulamalarındaki yavaşlığın başlıca nedenidir. Optimize edilmemiş her ilişki, kayıt başına ek bir SQL sorgusu üretir.
N+1 problemini eager loading ile çözmek
N+1 problemi, bir koleksiyon üzerinde her yineleme ilişkileri yüklemek için ek bir sorgu tetiklediğinde ortaya çıkar. 100 makale ve yazarlarıyla bu, tek optimize edilmiş sorgu yerine 101 sorgu demektir.
Eager loading, tüm ilişkileri en fazla bir veya iki sorguda alır ve yanıt süresini önemli ölçüde azaltır.
// Demonstration of N+1 problem and its solution
namespace App\Http\Controllers;
use App\Models\Article;
use Illuminate\Http\Request;
class ArticleController extends Controller
{
// ❌ N+1 problem: 1 articles query + N author queries
public function indexWithProblem()
{
$articles = Article::all(); // 1 query
foreach ($articles as $article) {
echo $article->author->name; // N additional queries
}
}
// ✅ Eager loading: 2 queries maximum
public function indexOptimized()
{
$articles = Article::with('author')->get(); // 2 queries total
foreach ($articles as $article) {
echo $article->author->name; // No additional queries
}
}
// ✅ Nested eager loading for multiple relationships
public function indexWithNestedRelations()
{
// Loads articles → authors → profiles + articles → comments → users
$articles = Article::with([
'author.profile',
'comments.user'
])->get();
return view('articles.index', compact('articles'));
}
}with() ile eager loading, ihtiyaçları öngörerek verileri önceden yükler. Performans farkı büyük koleksiyonlarda olağanüstü hale gelir.
Koşullu ve kısıtlı eager loading
Geniş ilişkiler bazen kısmi yükleme gerektirir. Eager loading üzerindeki kısıtlar, alınan verileri sınırlayarak N+1'den kaçınır.
// Eager loading with constraints to optimize queries
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
class UserController extends Controller
{
public function showWithRecentOrders(int $id)
{
// Load only the 5 most recent paid orders
$user = User::with(['orders' => function (Builder $query) {
$query->where('status', 'paid')
->orderByDesc('created_at')
->limit(5);
}])->findOrFail($id);
return view('users.show', compact('user'));
}
public function indexActiveWithStats()
{
// Conditional eager loading with withCount
$users = User::query()
->where('active', true)
->with(['profile', 'subscription'])
->withCount(['orders', 'reviews']) // Adds orders_count and reviews_count
->withSum('orders', 'total') // Adds orders_sum_total
->get();
return view('users.index', compact('users'));
}
public function showWithConditionalRelation(int $id)
{
// Load relationship only if user is premium
$user = User::findOrFail($id);
$user->loadMissing(
$user->isPremium() ? ['premiumFeatures', 'analytics'] : []
);
return view('users.show', compact('user'));
}
}withCount() ve withSum() yöntemleri, tam koleksiyonları yüklemeden toplama ekler ve dashboard istatistikleri için idealdir.
Tekrar kullanılabilir sorgular için query scope
Query scope'lar filtreleme mantığını model içinde kapsüller. Yerel scope'lar esneklik sağlarken global scope'lar otomatik olarak tüm sorgulara uygulanır.
Yerel scope'lar modelde scope önekini kullanır ancak onsuz çağrılır: scopeActive() çağırırken User::active() olur.
// Local and global scopes to encapsulate business logic
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
#[ScopedBy([PublishedScope::class])] // Global scope via PHP 8 attribute
class Article extends Model
{
// Simple local scope: Article::published()
public function scopePublished(Builder $query): Builder
{
return $query->whereNotNull('published_at')
->where('published_at', '<=', now());
}
// Local scope with parameter: Article::byCategory('tech')
public function scopeByCategory(Builder $query, string $category): Builder
{
return $query->where('category', $category);
}
// Local scope with optional parameter
public function scopePopular(Builder $query, int $minViews = 1000): Builder
{
return $query->where('views_count', '>=', $minViews)
->orderByDesc('views_count');
}
// Dynamic scope for flexible search
public function scopeSearch(Builder $query, ?string $term): Builder
{
if (empty($term)) {
return $query;
}
return $query->where(function (Builder $q) use ($term) {
$q->where('title', 'like', "%{$term}%")
->orWhere('content', 'like', "%{$term}%")
->orWhereHas('tags', fn($t) => $t->where('name', 'like', "%{$term}%"));
});
}
}// Reusable global scope across models
namespace App\Models\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class PublishedScope implements Scope
{
public function apply(Builder $builder, Model $model): void
{
// Automatically applied to all Article queries
$builder->whereNotNull('published_at')
->where('published_at', '<=', now());
}
}// Using scopes in a controller
$articles = Article::query()
->byCategory('technology')
->popular(500)
->search($request->input('q'))
->with('author')
->paginate(20);
// Disable a global scope temporarily
$allArticles = Article::withoutGlobalScope(PublishedScope::class)->get();Scope'ların zincirlenmesi, iş mantığını modelde merkezileştirerek okunabilir ve sürdürülebilir sorgular üretir.
Laravel mülakatlarında başarılı olmaya hazır mısın?
İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.
Attribute ile accessor ve mutator
Laravel 9+, accessor ve mutator için Attribute sınıfı aracılığıyla birleşik bir söz dizimi sunar. Bu modern yaklaşım get*Attribute ve set*Attribute yöntemlerinin yerini alır.
// Modern accessors and mutators with the Attribute class
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
class User extends Model
{
protected $fillable = ['first_name', 'last_name', 'email', 'password'];
// Accessor: generates a virtual attribute from other fields
protected function fullName(): Attribute
{
return Attribute::make(
get: fn () => "{$this->first_name} {$this->last_name}",
);
}
// Mutator: transforms value before saving
protected function password(): Attribute
{
return Attribute::make(
set: fn (string $value) => Hash::make($value),
);
}
// Combined accessor + mutator
protected function email(): Attribute
{
return Attribute::make(
get: fn (string $value) => Str::lower($value),
set: fn (string $value) => Str::lower(trim($value)),
);
}
// Cached accessor to avoid recalculations
protected function initials(): Attribute
{
return Attribute::make(
get: fn () => Str::upper(
Str::substr($this->first_name, 0, 1) .
Str::substr($this->last_name, 0, 1)
),
)->shouldCache(); // Caches result during request
}
// Computed attribute based on a relationship
protected function ordersTotal(): Attribute
{
return Attribute::make(
get: fn () => $this->orders->sum('total'),
);
}
}// Transparent usage of accessors and mutators
$user = new User();
$user->first_name = 'John';
$user->last_name = 'Doe';
$user->email = ' JOHN@EXAMPLE.COM '; // Automatically normalized
$user->password = 'secret123'; // Automatically hashed
$user->save();
echo $user->full_name; // "John Doe"
echo $user->initials; // "JD"
echo $user->email; // "john@example.com"shouldCache() yöntemi, aynı modeldeki birden fazla yeniden hesaplamayı önleyerek pahalı accessor'ları optimize eder.
Karmaşık tipler için özel cast'ler
Cast'ler, değerleri PHP ile veritabanı arasında otomatik olarak dönüştürür. Özel cast'ler, iş tipleri için serileştirme mantığını kapsüller.
// Custom cast for handling monetary amounts
namespace App\Casts;
use App\ValueObjects\Money;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
use InvalidArgumentException;
class MoneyCast implements CastsAttributes
{
public function __construct(
protected string $currency = 'USD'
) {}
// DB → PHP conversion: cents to Money object
public function get(Model $model, string $key, mixed $value, array $attributes): ?Money
{
if ($value === null) {
return null;
}
return new Money(
amount: (int) $value,
currency: $this->currency
);
}
// PHP → DB conversion: Money object to cents
public function set(Model $model, string $key, mixed $value, array $attributes): ?int
{
if ($value === null) {
return null;
}
if ($value instanceof Money) {
return $value->getAmountInCents();
}
if (is_numeric($value)) {
return (int) ($value * 100);
}
throw new InvalidArgumentException('Value must be Money instance or numeric');
}
}// Immutable Value Object representing amounts
namespace App\ValueObjects;
use JsonSerializable;
final readonly class Money implements JsonSerializable
{
public function __construct(
private int $amount, // Stored in cents
private string $currency
) {}
public function getAmountInCents(): int
{
return $this->amount;
}
public function getAmountInUnits(): float
{
return $this->amount / 100;
}
public function format(): string
{
return number_format($this->getAmountInUnits(), 2) . ' ' . $this->currency;
}
public function add(Money $other): self
{
return new self($this->amount + $other->amount, $this->currency);
}
public function jsonSerialize(): array
{
return [
'amount' => $this->getAmountInUnits(),
'currency' => $this->currency,
];
}
}// Using the custom cast
namespace App\Models;
use App\Casts\MoneyCast;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
protected function casts(): array
{
return [
'total' => MoneyCast::class, // USD by default
'shipping_cost' => MoneyCast::class . ':USD',
'tax_amount' => MoneyCast::class . ':USD',
'paid_at' => 'datetime',
'metadata' => 'array',
];
}
}// Natural usage with the cast
$order = Order::find(1);
echo $order->total->format(); // "149.99 USD"
echo $order->total->getAmountInCents(); // 14999
$order->total = 199.99; // Automatically converted
$order->save(); // Stored as 19999 in DBValue Object'ler özel cast'lerle birleştirildiğinde, zarif bir API'yi koruyarak iş verilerinin bütünlüğünü garanti altına alır.
Büyük ölçekli sorguları optimize etmek
Milyonlarca kayıt üzerindeki işlemler bellek tükenmesini önlemek için belirli teknikler gerektirir. Chunking ve cursor'lar verileri partiler halinde işler.
Model::all() tüm kayıtları belleğe yükler. 100.000 satırlık bir tabloda bu birkaç gigabayt RAM tüketebilir ve uygulamayı çökertebilir.
// Batch processing techniques for large tables
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class ProcessUsersCommand extends Command
{
protected $signature = 'users:process';
public function handle(): int
{
// ✅ Chunk: processes in batches of 1000, reloads from DB
User::query()
->where('needs_processing', true)
->chunk(1000, function ($users) {
foreach ($users as $user) {
$user->processAccount();
}
});
// ✅ Chunk with updates: avoids infinite loop during modifications
User::query()
->where('status', 'pending')
->chunkById(1000, function ($users) {
foreach ($users as $user) {
$user->update(['status' => 'processed']);
}
});
// ✅ Lazy collection: single record in memory at a time
foreach (User::lazy(1000) as $user) {
$user->sendNewsletter();
}
// ✅ Cursor: for read-only operations, minimal memory
foreach (User::cursor() as $user) {
$this->info("Processing: {$user->email}");
}
// ✅ Mass update without Eloquent: maximum performance
User::query()
->where('last_login_at', '<', now()->subYear())
->update(['status' => 'inactive']);
// ✅ Optimized mass deletion
User::query()
->where('deleted_at', '<', now()->subMonths(6))
->forceDelete();
return self::SUCCESS;
}
}chunk(), lazy() ve cursor() arasındaki seçim kullanım senaryosuna bağlıdır: değişiklikler için chunk(), ara işlemler için lazy() ve minimum bellek kullanımıyla basit okumalar için cursor().
İleri düzey polimorfik ilişkiler
Polimorfik ilişkiler bir modelin tek bir ilişki üzerinden birden fazla farklı model türüne ait olmasına olanak tanır. Bu esneklik yorumlar, etiketler veya eklenmiş dosyalar için idealdir.
// Model with inverse polymorphic relationship
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class Comment extends Model
{
protected $fillable = ['body', 'user_id'];
// A comment can belong to Article, Video, or any other model
public function commentable(): MorphTo
{
return $this->morphTo();
}
public function user()
{
return $this->belongsTo(User::class);
}
}// Parent model with polymorphic relationship
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany;
class Article extends Model
{
public function comments(): MorphMany
{
return $this->morphMany(Comment::class, 'commentable');
}
}// Another parent model using the same relationship
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany;
class Video extends Model
{
public function comments(): MorphMany
{
return $this->morphMany(Comment::class, 'commentable');
}
}// Migration for polymorphic comments table
// database/migrations/2026_01_15_create_comments_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->text('body');
$table->foreignId('user_id')->constrained();
$table->morphs('commentable'); // Creates commentable_type and commentable_id
$table->timestamps();
// Composite index for polymorphic queries
$table->index(['commentable_type', 'commentable_id']);
});
}
};// Using polymorphic relationships
$article = Article::find(1);
$article->comments()->create([
'body' => 'Excellent article!',
'user_id' => auth()->id(),
]);
$video = Video::find(1);
$video->comments()->create([
'body' => 'Very instructive video',
'user_id' => auth()->id(),
]);
// Retrieve parent from comment
$comment = Comment::with('commentable')->find(1);
echo get_class($comment->commentable); // App\Models\Article or App\Models\VideoPolimorfik ilişkiler tablo tekrarlarını önler ve yatay özellikler için mantığı merkezileştirir.
Laravel mülakatlarında başarılı olmaya hazır mısın?
İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.
Tekrar kullanılabilir mantık için trait ve observer
Trait'ler modeller arasında tekrar kullanılabilir davranışları kapsüller. Observer'lar yaşam döngüsü olayları üzerindeki hook'ları merkezileştirir.
// Trait for automatic slug generation
namespace App\Models\Concerns;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
trait HasSlug
{
public static function bootHasSlug(): void
{
static::creating(function (Model $model) {
if (empty($model->slug)) {
$model->slug = $model->generateUniqueSlug();
}
});
}
protected function generateUniqueSlug(): string
{
$slug = Str::slug($this->getSlugSource());
$originalSlug = $slug;
$counter = 1;
// Check uniqueness and add suffix if needed
while (static::where('slug', $slug)->exists()) {
$slug = "{$originalSlug}-{$counter}";
$counter++;
}
return $slug;
}
// Can be overridden in the model
protected function getSlugSource(): string
{
return $this->title ?? $this->name;
}
}// Trait for using UUIDs as primary key
namespace App\Models\Concerns;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
trait HasUuid
{
public static function bootHasUuid(): void
{
static::creating(function (Model $model) {
if (empty($model->{$model->getKeyName()})) {
$model->{$model->getKeyName()} = (string) Str::uuid();
}
});
}
public function getIncrementing(): bool
{
return false;
}
public function getKeyType(): string
{
return 'string';
}
}// Observer to centralize hooks on Article model
namespace App\Observers;
use App\Models\Article;
use App\Jobs\NotifySubscribersJob;
use App\Services\SearchIndexService;
use Illuminate\Support\Facades\Cache;
class ArticleObserver
{
public function __construct(
private SearchIndexService $searchIndex
) {}
public function created(Article $article): void
{
// Invalidate recent articles cache
Cache::tags(['articles', 'recent'])->flush();
// Index for search
$this->searchIndex->index($article);
}
public function updated(Article $article): void
{
// Update search index
$this->searchIndex->update($article);
// Notify subscribers if article was just published
if ($article->wasChanged('published_at') && $article->published_at !== null) {
NotifySubscribersJob::dispatch($article);
}
Cache::tags(['articles'])->flush();
}
public function deleted(Article $article): void
{
$this->searchIndex->remove($article);
Cache::tags(['articles'])->flush();
}
// Prevent deletion if article has comments
public function deleting(Article $article): bool
{
if ($article->comments()->exists()) {
return false; // Cancel deletion
}
return true;
}
}// Model using traits and observer
namespace App\Models;
use App\Models\Concerns\HasSlug;
use App\Models\Concerns\HasUuid;
use App\Observers\ArticleObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Model;
#[ObservedBy(ArticleObserver::class)]
class Article extends Model
{
use HasSlug, HasUuid;
protected $fillable = ['title', 'content', 'published_at'];
}Trait'lerin boot* yöntemleri model başlatma sırasında otomatik olarak çalıştırılır ve şeffaf bir entegrasyon sağlar.
Sonuç
Eloquent ORM'i ustalaşmak, alttaki mekanizmaları anlamaya ve uygun desenleri uygulamaya dayanır. Sunulan teknikler saf sorguları performanslı ve sürdürülebilir koda dönüştürür.
Eloquent optimizasyon kontrol listesi:
✅ Görüntülenen ilişkiler için sistematik olarak with() kullanın
✅ Saymak için koleksiyonları yüklemek yerine withCount() uygulayın
✅ Filtreleme mantığını query scope'larda kapsülleyin
✅ View'lerde tekrarlanan hesaplamalar yerine accessor'ları tercih edin
✅ İş Value Object'leri için özel cast'ler uygulayın
✅ Büyük tablolardaki işlemler için chunk() veya lazy() kullanın
✅ Yan etkileri observer'larda merkezileştirin
✅ Ortak davranışları trait'lere çıkarın
php artisan telescope veya laravel-debugbar araçları üretilen SQL sorgularını görselleştirir ve eksik optimizasyonları belirler.
Pratik yapmaya başla!
Mülakat simülatörleri ve teknik testlerle bilgini test et.
Etiketler
Paylaş
İlgili makaleler

2026 Yilinda En Cok Sorulan 25 Laravel ve PHP Mulakat Sorusu
Laravel mulakat sorulari: Service Container, Eloquent ORM, middleware, kuyruklar, guvenlik, test ve mimari desenler hakkinda 25 soru ve kod ornekleriyle kapsamli bir rehber.

Laravel 11: Sifirdan Eksiksiz Bir Uygulama Gelistirme
Kimlik dogrulama, REST API, Eloquent ORM ve production dağıtımı ile Laravel 11 uygulaması geliştirme rehberi. Başlangıç ve orta seviye geliştiriciler için pratik eğitim.

Laravel Middleware Derinlemesine: Kimlik Dogrulama, Rate Limiting ve Ozel Middleware
Laravel middleware hakkinda kapsamli rehber - kimlik dogrulamadan rate limitinge, ozel middleware siniflari olusturmaya kadar. Pratik kod ornekleri ve uretim ortami desenleri.