علاقات Eloquent
علاقات Eloquent
علاقات Eloquent هي واحدة من أقوى ميزات Laravel. إنها تسمح لك بتعريف الاتصالات بين جداول قاعدة البيانات والعمل مع البيانات ذات الصلة باستخدام بناء جملة بديهي موجه للكائنات. في هذا الدرس، ستتقن جميع أنواع العلاقات الرئيسية.
لماذا نستخدم العلاقات؟
تلغي العلاقات الحاجة إلى استعلامات JOIN المعقدة وتجعل الكود الخاص بك أكثر قابلية للقراءة والصيانة. بدلاً من كتابة SQL، يمكنك الوصول إلى البيانات ذات الصلة كخصائص للكائن.
واحد إلى واحد (hasOne / belongsTo)
العلاقة واحد إلى واحد هي حيث يرتبط سجل واحد في جدول بسجل واحد بالضبط في جدول آخر. مثال: المستخدم لديه ملف تعريف واحد.
هيكل قاعدة البيانات
// جدول users
id | name | email | created_at | updated_at
// جدول profiles
id | user_id | bio | website | avatar | created_at | updated_at
تعريف العلاقة
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
// المستخدم لديه ملف تعريف واحد
public function profile()
{
return $this->hasOne(Profile::class);
}
}
class Profile extends Model
{
// ملف التعريف ينتمي إلى المستخدم
public function user()
{
return $this->belongsTo(User::class);
}
}
استخدام علاقات واحد إلى واحد
// الوصول إلى البيانات ذات الصلة
$user = User::find(1);
$profile = $user->profile; // يُرجع نموذج Profile
echo $profile->bio;
echo $profile->website;
// العلاقة العكسية
$profile = Profile::find(1);
$user = $profile->user; // يُرجع نموذج User
echo $user->name;
// إنشاء سجل ذي صلة
$user = User::find(1);
$user->profile()->create([
'bio' => 'مطور Laravel',
'website' => 'https://example.com',
'avatar' => 'avatar.jpg',
]);
// تحديث سجل ذي صلة
$user->profile->update([
'bio' => 'سيرة ذاتية محدثة',
]);
واحد إلى متعدد (hasMany / belongsTo)
العلاقة واحد إلى متعدد هي حيث يمكن ربط سجل واحد بسجلات متعددة ذات صلة. مثال: المستخدم لديه العديد من المنشورات.
هيكل قاعدة البيانات
// جدول users
id | name | email | created_at | updated_at
// جدول posts
id | user_id | title | content | published_at | created_at | updated_at
تعريف العلاقة
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
// المستخدم لديه العديد من المنشورات
public function posts()
{
return $this->hasMany(Post::class);
}
}
class Post extends Model
{
protected $fillable = ['title', 'content', 'published_at'];
// المنشور ينتمي إلى المستخدم
public function user()
{
return $this->belongsTo(User::class);
}
}
استخدام علاقات واحد إلى متعدد
// الوصول إلى جميع منشورات المستخدم
$user = User::find(1);
$posts = $user->posts; // يُرجع مجموعة من نماذج Post
foreach ($posts as $post) {
echo $post->title;
}
// الاستعلام عن العلاقة
$publishedPosts = $user->posts()
->where('published_at', '!=', null)
->orderBy('published_at', 'desc')
->get();
// عد السجلات ذات الصلة
$postCount = $user->posts()->count();
// التحقق من وجود العلاقة
if ($user->posts()->exists()) {
echo "المستخدم لديه منشورات";
}
// إنشاء سجل ذي صلة
$user->posts()->create([
'title' => 'منشوري الجديد',
'content' => 'محتوى المنشور هنا...',
'published_at' => now(),
]);
// حفظ نموذج موجود
$post = new Post([
'title' => 'منشور آخر',
'content' => 'المزيد من المحتوى...',
]);
$user->posts()->save($post);
// حفظ نماذج متعددة
$user->posts()->saveMany([
new Post(['title' => 'منشور 1']),
new Post(['title' => 'منشور 2']),
]);
متعدد إلى متعدد (belongsToMany)
العلاقة متعدد إلى متعدد هي حيث ترتبط سجلات متعددة في جدول واحد بسجلات متعددة في جدول آخر. مثال: المنشور لديه العديد من العلامات، والعلامة لديها العديد من المنشورات.
هيكل قاعدة البيانات
// جدول posts
id | title | content | created_at | updated_at
// جدول tags
id | name | slug | created_at | updated_at
// جدول post_tag (جدول محوري)
id | post_id | tag_id | created_at | updated_at
post_tag (وليس tag_post). يجب أن تكون المفاتيح الخارجية مفردة: post_id و tag_id.
تعريف العلاقة
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
// المنشور ينتمي إلى العديد من العلامات
public function tags()
{
return $this->belongsToMany(Tag::class);
}
}
class Tag extends Model
{
protected $fillable = ['name', 'slug'];
// العلامة تنتمي إلى العديد من المنشورات
public function posts()
{
return $this->belongsToMany(Post::class);
}
}
استخدام علاقات متعدد إلى متعدد
// الوصول إلى جميع علامات المنشور
$post = Post::find(1);
$tags = $post->tags;
foreach ($tags as $tag) {
echo $tag->name;
}
// الوصول إلى جميع المنشورات بعلامة محددة
$tag = Tag::where('slug', 'laravel')->first();
$posts = $tag->posts;
// إرفاق العلامات بمنشور (إضافة علاقات)
$post->tags()->attach([1, 2, 3]); // بالمعرفات
$post->tags()->attach($tag); // بالنموذج
// فصل العلامات (إزالة العلاقات)
$post->tags()->detach([1, 2]); // إزالة علامات محددة
$post->tags()->detach(); // إزالة جميع العلامات
// مزامنة العلامات (استبدال جميع الموجودة)
$post->tags()->sync([1, 2, 3]);
// المزامنة بدون فصل
$post->tags()->syncWithoutDetaching([4, 5]);
// تبديل العلامات (إرفاق إذا لم تكن مرفقة، فصل إذا كانت مرفقة)
$post->tags()->toggle([1, 2, 3]);
بيانات الجدول المحوري
الوصول إلى وتخزين بيانات إضافية على الجدول المحوري:
<?php
// تعريف أعمدة محورية في العلاقة
class Post extends Model
{
public function tags()
{
return $this->belongsToMany(Tag::class)
->withPivot('featured', 'order')
->withTimestamps();
}
}
// الوصول إلى بيانات محورية
$post = Post::find(1);
foreach ($post->tags as $tag) {
echo $tag->name;
echo $tag->pivot->featured;
echo $tag->pivot->order;
echo $tag->pivot->created_at;
}
// الإرفاق مع بيانات محورية
$post->tags()->attach(1, [
'featured' => true,
'order' => 1
]);
// المزامنة مع بيانات محورية
$post->tags()->sync([
1 => ['featured' => true, 'order' => 1],
2 => ['featured' => false, 'order' => 2],
]);
لديه العديد من خلال
توفر هذه العلاقة اختصارًا للوصول إلى العلاقات البعيدة من خلال علاقة وسيطة. مثال: الدولة لديها العديد من المنشورات من خلال المستخدمين.
هيكل قاعدة البيانات
// جدول countries
id | name
// جدول users
id | country_id | name
// جدول posts
id | user_id | title | content
تعريف العلاقة
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Country extends Model
{
// الدولة لديها العديد من المنشورات من خلال المستخدمين
public function posts()
{
return $this->hasManyThrough(
Post::class, // النموذج النهائي
User::class, // النموذج الوسيط
'country_id', // المفتاح الخارجي على جدول users
'user_id', // المفتاح الخارجي على جدول posts
'id', // المفتاح المحلي على جدول countries
'id' // المفتاح المحلي على جدول users
);
}
}
استخدام لديه العديد من خلال
$country = Country::find(1);
$posts = $country->posts; // جميع المنشورات من قبل المستخدمين في هذه الدولة
foreach ($posts as $post) {
echo $post->title;
}
العلاقات متعددة الأشكال
تسمح العلاقات متعددة الأشكال لنموذج بالانتماء إلى أكثر من نموذج آخر في اتحاد واحد. مثال: يمكن أن تنتمي التعليقات إلى كل من المنشورات ومقاطع الفيديو.
هيكل قاعدة البيانات
// جدول posts
id | title | content
// جدول videos
id | title | url
// جدول comments
id | body | commentable_id | commentable_type | created_at
commentable_type اسم فئة النموذج (مثل "App\Models\Post")، ويخزن commentable_id معرف النموذج ذي الصلة.
تعريف العلاقات متعددة الأشكال
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
protected $fillable = ['body'];
// التعليق ينتمي إلى commentable (منشور أو فيديو)
public function commentable()
{
return $this->morphTo();
}
}
class Post extends Model
{
// المنشور لديه العديد من التعليقات (متعدد الأشكال)
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
class Video extends Model
{
// الفيديو لديه العديد من التعليقات (متعدد الأشكال)
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
استخدام العلاقات متعددة الأشكال
// إنشاء تعليقات لمنشور
$post = Post::find(1);
$post->comments()->create([
'body' => 'منشور رائع!'
]);
// إنشاء تعليقات لفيديو
$video = Video::find(1);
$video->comments()->create([
'body' => 'فيديو جميل!'
]);
// استرجاع جميع التعليقات
$post->comments; // تعليقات للمنشور
$video->comments; // تعليقات للفيديو
// العلاقة العكسية (الحصول على الأصل)
$comment = Comment::find(1);
$commentable = $comment->commentable; // يُرجع Post أو Video
if ($commentable instanceof Post) {
echo "تعليق على منشور: " . $commentable->title;
} elseif ($commentable instanceof Video) {
echo "تعليق على فيديو: " . $commentable->title;
}
التحميل الحريص
يحل التحميل الحريص مشكلة استعلام N+1 عن طريق تحميل العلاقات مسبقًا، مما يحسن الأداء بشكل كبير.
مشكلة N+1
// سيء: استعلامات N+1 (1 + 100 = 101 استعلام لـ 100 مستخدم)
$users = User::all(); // استعلام واحد
foreach ($users as $user) {
echo $user->posts->count(); // 100 استعلام إضافي!
}
الحل: التحميل الحريص
// جيد: التحميل الحريص (استعلامان فقط)
$users = User::with('posts')->get(); // استعلامان إجماليًا
foreach ($users as $user) {
echo $user->posts->count(); // لا استعلامات إضافية!
}
// تحميل حريص لعلاقات متعددة
$posts = Post::with(['user', 'tags', 'comments'])->get();
// تحميل حريص متداخل
$countries = Country::with('users.posts')->get();
// تحميل حريص مشروط
$users = User::with([
'posts' => function ($query) {
$query->where('published_at', '!=', null)
->orderBy('published_at', 'desc')
->limit(5);
}
])->get();
// تحميل حريص لأعمدة محددة
$users = User::with('posts:id,user_id,title')->get();
التحميل الحريص الكسول
$users = User::all();
// تحميل العلاقة بعد استرجاع النماذج
if ($someCondition) {
$users->load('posts');
}
// التحميل مع القيود
$users->load([
'posts' => function ($query) {
$query->where('published', true);
}
]);
الاستعلام عن العلاقات
// Has: الحصول على المستخدمين الذين لديهم منشور واحد على الأقل
$users = User::has('posts')->get();
// Has مع العد
$users = User::has('posts', '>=', 3)->get();
// WhereHas: الحصول على المستخدمين مع المنشورات المنشورة
$users = User::whereHas('posts', function ($query) {
$query->where('published_at', '!=', null);
})->get();
// DoesntHave: الحصول على المستخدمين بدون منشورات
$users = User::doesntHave('posts')->get();
// WhereDoesntHave: الحصول على المستخدمين بدون منشورات منشورة
$users = User::whereDoesntHave('posts', function ($query) {
$query->where('published_at', '!=', null);
})->get();
// عد السجلات ذات الصلة
$users = User::withCount('posts')->get();
foreach ($users as $user) {
echo $user->posts_count; // سمة posts_count مضافة
}
قم بإنشاء هيكل علاقة مدونة كامل:
- User hasMany Posts
- Post belongsTo User
- Post hasMany Comments
- Comment belongsTo Post and User
- Post belongsToMany Tags
- إنشاء هجرات لجميع الجداول بما في ذلك الجدول المحوري
- تعريف جميع العلاقات في النماذج
- اختبار إنشاء منشورات بعلامات وتعليقات
قم بتنفيذ علاقة متعددة الأشكال للصور:
- إنشاء نموذج صورة يمكن أن ينتمي إلى المستخدمين (صورة رمزية) والمنشورات (صورة مميزة)
- إنشاء هجرة مع
imageable_idوimageable_type - تعريف
morphManyفي نماذج User و Post - تعريف
morphToفي نموذج Image - اختبار إرفاق الصور بالمستخدمين والمنشورات
- الاستعلام عن جميع المنشورات مع صورها باستخدام التحميل الحريص
بالنظر إلى هذا الكود مع مشكلة استعلام N+1:
$posts = Post::all();
foreach ($posts as $post) {
echo $post->user->name;
echo $post->comments->count();
foreach ($post->tags as $tag) {
echo $tag->name;
}
}
أعد كتابته باستخدام التحميل الحريص لتنفيذ 4 استعلامات فقط إجماليًا.
أفضل الممارسات
- استخدم دائمًا التحميل الحريص: عند الوصول إلى العلاقات في الحلقات، قم دائمًا بالتحميل الحريص لتجنب استعلامات N+1.
- قم بتسمية العلاقات بوضوح: استخدم المفرد لـ
belongsTo، الجمع لـhasManyوbelongsToMany. - استخدم نطاقات الاستعلام: اجمع العلاقات مع نطاقات الاستعلام لكود أنظف.
- استفد من طرق العلاقة: استخدم طرق العلاقة مثل
create()،save()، وattach()بدلاً من تعيين المفتاح الخارجي يدويًا. - راقب عدد الاستعلامات: استخدم Laravel Debugbar لمراقبة عدد الاستعلامات وتحديد مشاكل N+1.
- حذف متتالي: قم بإعداد قيود المفاتيح الخارجية المناسبة مع
onDelete('cascade')في الهجرات.
الخلاصة
في هذا الدرس، أتقنت:
- علاقات واحد إلى واحد (hasOne / belongsTo)
- علاقات واحد إلى متعدد (hasMany / belongsTo)
- علاقات متعدد إلى متعدد مع جداول محورية (belongsToMany)
- علاقات لديه العديد من خلال للعلاقات البعيدة
- العلاقات متعددة الأشكال للاتحادات المرنة
- التحميل الحريص للقضاء على مشاكل استعلام N+1
- الاستعلام عن العلاقات مع has و whereHas و withCount
تحول علاقات Eloquent تفاعلات قاعدة البيانات المعقدة إلى كود أنيق وقابل للقراءة. أتقنها، وستبني تطبيقات قوية وفعالة بسهولة!