Eloquent ORM في Laravel: إتقان العلاقات والاستعلامات
Eloquent ORM في Laravel: إتقان العلاقات والاستعلامات
دليل تخصصي من علاء عامر – مطور ومصمم مواقع وتطبيقات محترف
Eloquent ORM هو قلب Laravel لإدارة قواعد البيانات. يوفر طريقة أنيقة وبديهية للتعامل مع البيانات والعلاقات المعقدة.
2️⃣ العلاقات المتقدمة والمعقدة
نموذج Post مع علاقات معقدة:
<?php
// app/Models/Post.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
use SoftDeletes;
protected $fillable = [
'title', 'slug', 'content', 'excerpt', 'featured_image',
'status', 'user_id', 'category_id', 'published_at',
'meta_title', 'meta_description', 'reading_time'
];
protected $casts = [
'published_at' => 'datetime',
'meta_keywords' => 'array',
'settings' => 'array',
'is_featured' => 'boolean'
];
// العلاقات الأساسية
public function author()
{
return $this->belongsTo(User::class, 'user_id');
}
public function category()
{
return $this->belongsTo(Category::class);
}
public function tags()
{
return $this->belongsToMany(Tag::class)->withTimestamps();
}
public function comments()
{
return $this->hasMany(Comment::class)->whereNull('parent_id');
}
public function allComments()
{
return $this->hasMany(Comment::class);
}
// علاقة polymorphic للصور
public function images()
{
return $this->morphMany(Image::class, 'imageable');
}
// علاقة للمراجعات
public function revisions()
{
return $this->hasMany(PostRevision::class)->latest();
}
// علاقة المشاهدات
public function views()
{
return $this->morphMany(View::class, 'viewable');
}
// علاقة الإعجابات
public function likes()
{
return $this->morphMany(Like::class, 'likeable');
}
// المستخدمون الذين أعجبهم المقال
public function likedByUsers()
{
return $this->morphToMany(User::class, 'likeable', 'likes')
->withTimestamps();
}
// Scopes متقدمة
public function scopePublished($query)
{
return $query->where('status', 'published')
->where('published_at', '<=', now());
}
public function scopeFeatured($query)
{
return $query->where('is_featured', true);
}
public function scopeInCategory($query, $categoryId)
{
return $query->where('category_id', $categoryId);
}
public function scopeByAuthor($query, $authorId)
{
return $query->where('user_id', $authorId);
}
public function scopePopular($query, $days = 30)
{
return $query->withCount(['views' => function($q) use ($days) {
$q->where('created_at', '>=', now()->subDays($days));
}])
->orderBy('views_count', 'desc');
}
public function scopeWithRelations($query)
{
return $query->with(['author', 'category', 'tags']);
}
public function scopeSearch($query, $term)
{
return $query->where(function($q) use ($term) {
$q->where('title', 'LIKE', "%{$term}%")
->orWhere('content', 'LIKE', "%{$term}%")
->orWhere('excerpt', 'LIKE', "%{$term}%");
});
}
// Query Scopes مع معاملات
public function scopePublishedBetween($query, $start, $end)
{
return $query->published()
->whereBetween('published_at', [$start, $end]);
}
public function scopeWithMinReadingTime($query, $minutes)
{
return $query->where('reading_time', '>=', $minutes);
}
// Local Scopes للفلترة المعقدة
public function scopeAdvancedFilter($query, array $filters)
{
return $query->when($filters['category'] ?? null, function($q, $category) {
return $q->inCategory($category);
})
->when($filters['author'] ?? null, function($q, $author) {
return $q->byAuthor($author);
})
->when($filters['status'] ?? null, function($q, $status) {
return $q->where('status', $status);
})
->when($filters['date_from'] ?? null, function($q, $date) {
return $q->where('published_at', '>=', $date);
})
->when($filters['date_to'] ?? null, function($q, $date) {
return $q->where('published_at', '<=', $date);
})
->when($filters['search'] ?? null, function($q, $term) {
return $q->search($term);
});
}
// Accessors
public function getRouteKeyName()
{
return 'slug';
}
public function getReadingTimeAttribute($value)
{
return $value ?: $this->calculateReadingTime();
}
public function getIsPublishedAttribute()
{
return $this->status === 'published' &&
$this->published_at <= now();
}
public function getExcerptAttribute($value)
{
return $value ?: Str::limit(strip_tags($this->content), 160);
}
// Helper Methods
public function calculateReadingTime()
{
$wordCount = str_word_count(strip_tags($this->content));
return ceil($wordCount / 200); // 200 words per minute
}
public function incrementViews($user = null)
{
$this->views()->create([
'user_id' => $user?->id,
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent()
]);
$this->increment('views_count');
}
public function like($user)
{
if (!$this->isLikedBy($user)) {
return $this->likes()->create(['user_id' => $user->id]);
}
}
public function unlike($user)
{
return $this->likes()->where('user_id', $user->id)->delete();
}
public function isLikedBy($user)
{
if (!$user) return false;
return $this->likes()->where('user_id', $user->id)->exists();
}
}
4️⃣ تحسين الأداء مع Eloquent
تجنب N+1 Problem:
<?php
// مشكلة N+1 - سيئة
$posts = Post::all();
foreach ($posts as $post) {
echo $post->author->name; // استعلام إضافي لكل مقال
}
// الحل - Eager Loading
$posts = Post::with('author')->get();
foreach ($posts as $post) {
echo $post->author->name; // استعلام واحد فقط
}
// Eager Loading المشروط
$posts = Post::with(['author' => function($query) {
$query->where('status', 'active');
}])->get();
// تحميل العلاقات لاحقاً
$posts = Post::all();
$posts->load('author', 'category');
// تحميل العلاقات المتداخلة
$posts = Post::with([
'comments.user',
'category.parent',
'tags' => function($query) {
$query->where('is_active', true);
}
])->get();
استخدام Chunk للبيانات الكبيرة:
<?php
// معالجة البيانات الكبيرة بكفاءة
Post::published()
->chunk(100, function($posts) {
foreach ($posts as $post) {
// معالجة كل مقال
$post->calculateReadingTime();
$post->save();
}
});
// Lazy Collection للذاكرة المحدودة
Post::published()->lazy()->each(function($post) {
// معالجة مقال واحد في كل مرة
$post->updateSeoScore();
});
// Cursor للبيانات الضخمة
foreach (Post::published()->cursor() as $post) {
// معالجة فعالة للذاكرة
ProcessPostJob::dispatch($post);
}
Query Optimization:
<?php
// استعلامات محسنة للأداء
class OptimizedQueries
{
public function getPopularPostsOptimized()
{
return Post::select([
'id', 'title', 'slug', 'excerpt',
'featured_image', 'published_at', 'views_count'
])
->with([
'author:id,name,avatar',
'category:id,name,slug'
])
->published()
->orderBy('views_count', 'desc')
->limit(10)
->get();
}
public function getCategoryStatsOptimized()
{
return Category::withCount([
'posts as total_posts',
'posts as published_posts' => function($query) {
$query->published();
}
])
->with(['posts' => function($query) {
$query->published()
->latest()
->limit(5)
->select('id', 'title', 'slug', 'category_id');
}])
->get();
}
public function getUserActivityStats($userId)
{
return DB::table('users')
->leftJoin('posts', 'users.id', '=', 'posts.user_id')
->leftJoin('comments', 'users.id', '=', 'comments.user_id')
->select([
'users.id',
'users.name',
DB::raw('COUNT(DISTINCT posts.id) as posts_count'),
DB::raw('COUNT(DISTINCT comments.id) as comments_count'),
DB::raw('SUM(posts.views_count) as total_views')
])
->where('users.id', $userId)
->groupBy('users.id', 'users.name')
->first();
}
}
💡 أفضل الممارسات مع Eloquent
- استخدم Eager Loading لتجنب مشكلة N+1
- حدد الحقول المطلوبة باستخدام select()
- استخدم Scopes لإعادة استخدام الاستعلامات
- فعّل Model Caching للبيانات الثابتة
- استخدم Database Indexes للحقول المستعلمة كثيراً
- اتبع naming conventions لـ Laravel
- استخدم Accessors/Mutators بحذر لتجنب بطء الأداء
الخطوة التالية
تعلم Authentication & Authorization في Laravel لحماية تطبيقك وإدارة المستخدمين.
📩 هل تحتاج مساعدة في تطوير Eloquent Models؟
قسم المقالة
Eloquent ORM في Laravel: إتقان العلاقات والاستعلامات
دليل شامل لـ Eloquent ORM في Laravel، تعلم كيفية بناء العلاقات المعقدة، الاستعلامات المتقدمة، وتحسين الأداء.
التواصل والاستشارة
تواصل مباشر عبر الواتساب أو الهاتف لفهم احتياجات مشروعك بدقة.
التخطيط والجدولة
وضع خطة عمل واضحة مع جدول زمني محدد لكل مرحلة من المشروع.
البرمجة والتطوير
تطوير المشروع بأحدث التقنيات لضمان الأداء والأمان العاليين.
المراجعة والتسليم
ختبار شامل ومراجعة دقيقة قبل التسليم النهائي للمشروع.