شرح العلاقات Relationships في Laravel
عندما تبدأ في بناء تطبيق حقيقي باستخدام Laravel، ستكتشف سريعًا أن أغلب قوة Eloquent لا تأتي فقط من سهولة كتابة الاستعلامات، بل من الطريقة الذكية التي يتعامل بها مع العلاقات بين الجداول. فبدلًا من أن تكتب استعلامات معقدة في كل مرة تريد فيها جلب المستخدم مع ملفه الشخصي، أو المقال مع التعليقات، أو الطلب مع المنتجات، يمكنك أن تعبّر عن هذه العلاقات بطريقة واضحة داخل الموديلات نفسها، ثم تترك Eloquent يتكفل بالباقي. والوثائق الرسمية الحالية لـ Laravel توضّح أن Eloquent يدعم مجموعة واسعة من العلاقات مثل One to One، One to Many، Belongs To، Many to Many، Has One Through، Has Many Through، والعلاقات متعددة الأشكال Polymorphic، إضافة إلى ميزات حديثة مثل Has One of Many، التحميل المسبق Eager Loading، والتعامل مع العلاقات ديناميكيًا.
هذا المقال ليس مجرد تعريف سريع، بل دليل عملي طويل ومفصل يشرح لك العلاقات في Laravel من الصفر إلى مستوى يجعلك تفهم لماذا تُبنى هكذا، ومتى تستخدم كل نوع، وكيف تكتبها بشكل نظيف، وكيف تتعامل معها في المشاريع الكبيرة دون أن تتحول قاعدة البيانات إلى فوضى. سأحاول أن أكتب الشرح بروح مطوّر يتحدث إلى مطوّر آخر، لا بروح كتابٍ جامد، لأن العلاقات ليست مجرد مصطلحات تقنية؛ هي في الحقيقة طريقة تفكير تنعكس على جودة التطبيق كله.
لماذا العلاقات مهمة أصلًا؟
فكّر في أي تطبيق بسيط: متجر إلكتروني، مدونة، منصة تعليمية، نظام حجوزات، أو حتى تطبيق لإدارة المهام. ستجد أن البيانات لا تعيش وحدها. المستخدم لديه ملف شخصي، وقد يكتب عدة منشورات، وكل منشور لديه عدة تعليقات، وكل تعليق قد يكون عليه ردود، والطلب يحتوي على عدة عناصر، وكل عنصر مرتبط بمنتج. لو حاولت أن تعيش بدون علاقات واضحة، ستجد نفسك تكرر البيانات، أو تكتب استعلامات طويلة، أو تضيع وقتك في الربط اليدوي بين الجداول.
Laravel جاء ليجعل هذا الجزء أكثر إنسانية. بدل أن تقول: "أحتاج الاستعلام من الجدول A ثم أدمج النتيجة مع الجدول B يدويًا"، تقول: "هذا النموذج له علاقة بكذا"، ثم تستدعيها مباشرة. هذا أسلوب أجمل في القراءة، أسهل في الصيانة، وأقل عرضة للأخطاء. والأهم أنه يترك لك مساحة تفكير أعلى: بدل أن تنشغل بكيفية الربط، تنشغل بماهية الربط نفسه.
الفكرة الأساسية: العلاقة ليست مجرد JOIN
كثير من المبتدئين يظنون أن العلاقات في Laravel مجرد غطاء لطيف فوق SQL JOIN. الحقيقة أعمق من ذلك قليلًا. نعم، في النهاية Eloquent سيتعامل مع قاعدة البيانات، لكنه يمنحك نموذجًا ذهنيًا مريحًا للتعبير عن بنية بياناتك. عندما تقول hasMany فأنت لا تعلن فقط عن استعلام، بل تعلن عن معنى: هذا الكيان يملك عدة عناصر من هذا النوع. وعندما تقول belongsTo فأنت تقول: هذا السجل ينتمي إلى سجل آخر.
هذا المعنى مهم جدًا؛ لأنه يساعدك في تصميم قاعدة بيانات سليمة من البداية. قاعدة البيانات الجيدة لا تبدأ من الجداول فقط، بل تبدأ من العلاقات المنطقية بين الكيانات. لهذا السبب يفيدك فهم العلاقات في Laravel حتى لو كنت قويًا في SQL؛ لأن Eloquent يفرض عليك أن تفكر بشكل أوضح.
أنواع العلاقات الأساسية في Laravel
قبل أن ندخل في الكود، من المفيد أن نرسم خريطة ذهنية للعلاقات الأساسية:
One to One
One to Many
Many to One / Belongs To
Many to Many
Has One Through
Has Many Through
Polymorphic One to One
Polymorphic One to Many
Polymorphic Many to Many
Laravel يغطّي هذه الأنماط وغيرها ضمن Eloquent، والوثائق الرسمية تعرض أيضًا موضوعات مثل eager loading، التحقق من وجود العلاقات، عدّ العلاقات، وحفظ البيانات المرتبطة بها.
والآن لنأخذ كل نوع على حدة، مع أمثلة واقعية.
أولًا: علاقة One to One
هذه العلاقة بسيطة جدًا: سجل واحد يرتبط بسجل واحد فقط. مثالها المشهور: المستخدم وملفّه الشخصي. لكل مستخدم ملف شخصي واحد، ولكل ملف شخصي مستخدم واحد.
مثال جداول
لدينا جدول users وجدول profiles:
// users
id
name
email
// profiles
id
user_id
bio
phone
avatar
هنا profiles.user_id هو المفتاح الأجنبي الذي يشير إلى المستخدم.
تعريف العلاقة داخل الموديلات
في موديل User:
public function profile()
{
return $this->hasOne(Profile::class);
}
وفي موديل Profile:
public function user()
{
return $this->belongsTo(User::class);
}
كيف تفكر في هذه العلاقة؟
hasOne تعني أن الموديل الحالي يملك عنصرًا واحدًا من النوع الآخر.belongsTo تعني أن الموديل الحالي ينتمي إلى موديل أب.
القاعدة هنا مهمة: عادةً المفتاح الأجنبي يكون في الجدول التابع أو الأصغر، وليس في الجدول الأب. لذلك profiles يحمل user_id.
الاستخدام
$user = User::find(1);
echo $user->profile->bio;
أو بالعكس:
$profile = Profile::find(1);
echo $profile->user->name;
ملاحظة مهمة
إذا لم يكن الملف الشخصي موجودًا، فإن الوصول المباشر إلى $user->profile->bio قد يسبب خطأ. لذلك من الأفضل أحيانًا استخدام optional() أو التحقق من وجود العلاقة:
echo optional($user->profile)->bio;
أو مع PHP الحديثة:
echo $user->profile?->bio;
هذه التفاصيل الصغيرة تصنع فرقًا كبيرًا في التطبيقات الحقيقية، خصوصًا عندما تكون البيانات غير مكتملة أو في مرحلة إدخال أولي.
ثانيًا: علاقة One to Many
هذه هي العلاقة الأشهر في العالم البرمجي كله تقريبًا. سجل واحد يملك عدة سجلات من نوع آخر. مثالها: المقال والتعليقات. المقال الواحد يمكن أن يحتوي على عشرات التعليقات.
مثال جداول
// posts
id
user_id
title
content
// comments
id
post_id
body
تعريف العلاقة
في موديل Post:
public function comments()
{
return $this->hasMany(Comment::class);
}
وفي موديل Comment:
public function post()
{
return $this->belongsTo(Post::class);
}
الاستخدام
$post = Post::find(1);
foreach ($post->comments as $comment) {
echo $comment->body;
}
ولو أردت الوصول من التعليق إلى المقال:
$comment = Comment::find(1);
echo $comment->post->title;
متى تستخدمها؟
استخدمها عندما يكون لديك كيان رئيسي، وتحت هذا الكيان توجد عدة عناصر تابعة له. من الأمثلة:
مستخدم → مقالات
مقالة → تعليقات
طلب → عناصر الطلب
دورة → دروس
كتاب → مراجعات
لماذا يحبها Laravel؟
لأنها تعبّر عن بنية شائعة جدًا، ومعها تستطيع أن تُبقي الكود نظيفًا. بدل أن تكتب كل مرة:
Comment::where('post_id', $post->id)->get();
تكتب ببساطة:
$post->comments;
وهذه اللغة أقرب إلى التفكير البشري. أنت لا تقول "هات كل التعليقات حيث post_id يساوي كذا"، بل تقول "هات تعليقات هذا المقال".
ثالثًا: علاقة Belongs To
هذه العلاقة هي الوجه الآخر لـ hasOne و hasMany. كثير من الناس يفهمون hasMany بسرعة، لكنهم يخلطون في belongsTo. الطريقة الأسهل لفهمها هي: إذا كان هذا السجل يحتوي على مفتاح أجنبي يشير إلى سجل آخر، فهو غالبًا belongsTo ذلك السجل.
مثال
في جدول comments لدينا post_id. هذا يعني أن كل تعليق ينتمي إلى مقال واحد.
public function post()
{
return $this->belongsTo(Post::class);
}
وفي Post:
public function comments()
{
return $this->hasMany(Comment::class);
}
كيف تتذكرها؟
فكّر في الجهة التي تحمل المفتاح الأجنبي. الجهة التي تحمل المفتاح عادة تقول: "أنا أنتمي إلى هذا السجل".
هذا التبسيط مفيد جدًا، لأن الكثير من المشاكل في العلاقات تنبع من سوء فهم اتجاه العلاقة لا من الكود نفسه.
رابعًا: Has Many Through
هذه العلاقة جميلة جدًا عندما تريد الوصول إلى بيانات بعيدة عبر مستوى وسيط. مثال: دولة → مستخدمون → طلبات. أو شركة → موظفون → ملفات.
فأنت هنا لا تريد المرور اليدوي في كل مرة، بل تريد أن تقول: "أعطني كل الطلبات المرتبطة بهذه الدولة عبر المستخدمين".
مثال تخيلي
لنفرض أن عندك:
countriesusersorders
المستخدم ينتمي إلى دولة، والطلب ينتمي إلى مستخدم.
يمكنك في موديل Country أن تقول:
public function orders()
{
return $this->hasManyThrough(Order::class, User::class);
}
الفكرة
هذه العلاقة تختصر مسارًا فيه جدول وسيط. بدل أن تجلب المستخدمين ثم تجلب طلباتهم بشكل منفصل، تترك Eloquent يربط المسار.
متى تكون مفيدة؟
دولة → مستخدمون → منشورات
قسم → موظفون → مشاريع
شركة → فرق → مهام
هذه العلاقة مفيدة جدًا في لوحات الإدارة والتقارير، حيث تحتاج الوصول إلى بيانات بعيدة بطريقة مباشرة.
خامسًا: Has One Through
تشبه hasManyThrough لكنها تعيد سجلًا واحدًا فقط من خلال وسيط. مثال شائع: بلد يملك عاصمة عبر جدول مدينة، أو مستخدم يملك سجلًا طبيًا عبر حساب وسيط، أو شركة تملك مديرًا ماليًا عبر قسم معين.
public function latestInvoice()
{
return $this->hasOneThrough(Invoice::class, Order::class);
}
لكن عمليًا قد لا تحتاج هذه العلاقة كثيرًا مثل hasManyThrough. مع ذلك من المهم أن تعرف أنها موجودة، لأنها تنقذك في سيناريوهات محددة.
سادسًا: Many to Many
هذه من أهم العلاقات، وربما الأكثر استعمالًا بعد One to Many. هنا يكون الكيان الأول مرتبطًا بعدة عناصر من النوع الثاني، والعكس صحيح، ولكن ليس مباشرة، بل عبر جدول وسيط يسمى pivot table.
مثال واضح جدًا
المستخدمون والأدوار:
المستخدم يمكن أن يمتلك عدة أدوار
الدور يمكن أن يكون مرتبطًا بعدة مستخدمين
إذًا العلاقة Many to Many.
الجداول
users
roles
role_user
والجدول الوسيط غالبًا يحتوي على:
user_id
role_id
تعريف العلاقة
في User:
public function roles()
{
return $this->belongsToMany(Role::class);
}
وفي Role:
public function users()
{
return $this->belongsToMany(User::class);
}
الاستخدام
$user = User::find(1);
foreach ($user->roles as $role) {
echo $role->name;
}
لماذا نحتاج جدولًا وسيطًا؟
لأن العلاقة ليست "واحدًا إلى واحد" ولا "واحدًا إلى كثير" بشكل مباشر. المستخدم لا يحمل role_id واحدًا فقط، والدور لا يحمل user_id واحدًا فقط. لذلك نحتاج جدولًا يحفظ الأزواج. هذا من أساسيات التصميم العلاقي في قواعد البيانات.
التسمية الافتراضية
Laravel عادة يتوقع اسم الجدول الوسيط بترتيب أبجدي: role_user بدل user_role. وهذه نقطة قد تربك المبتدئين. إذا استخدمت اسمًا مختلفًا، تستطيع تحديده يدويًا:
return $this->belongsToMany(Role::class, 'custom_pivot_table');
العمل مع pivot data
أحيانًا لا يكفي أن تعرف أن المستخدم مرتبط بالدور، بل تريد معلومة إضافية على الجدول الوسيط مثل تاريخ الإسناد أو حالة التفعيل.
public function roles()
{
return $this->belongsToMany(Role::class)
->withPivot('assigned_at', 'status')
->withTimestamps();
}
ثم تستطيع الوصول إلى بيانات pivot هكذا:
foreach ($user->roles as $role) {
echo $role->pivot->assigned_at;
}
هذه الجزئية بالذات مهمة جدًا في الأنظمة الحقيقية، لأن جداول الربط لا تكون مجرد "أسلاك" فارغة دائمًا؛ أحيانًا تحمل معنى تجاريًا مهمًا.
سابعًا: التعامل مع العلاقات many to many بذكاء
في العلاقات many to many لديك عدة عمليات مهمة:
الإضافة
$user->roles()->attach($roleId);
الإزالة
$user->roles()->detach($roleId);
الاستبدال الكامل
$user->roles()->sync([1, 2, 5]);
الإضافة دون حذف الموجود
$user->roles()->syncWithoutDetaching([3, 4]);
هذه الأوامر مهمة جدًا، وخصوصًا sync التي تُستخدم كثيرًا في إدارة الأدوار والصلاحيات. كثير من الأخطاء في تطبيقات Laravel تأتي من اختيار الطريقة غير المناسبة: أحيانًا تحتاج attach وأحيانًا تحتاج sync.
القاعدة البسيطة هي:attach يضيف فقط.detach يحذف فقط.sync يجعل البيانات متطابقة تمامًا مع ما أرسلته.
ثامنًا: العلاقات متعددة الأشكال Polymorphic Relationships
الجزء الذي يجعل Laravel أكثر مرونة هو العلاقات متعددة الأشكال. الفكرة هنا أن سجلًا واحدًا يمكن أن يرتبط بعدة أنواع مختلفة من النماذج.
مثال عملي
لديك جدول images أو attachments، وتريد أن يستعمله:
المستخدم
المنشور
المنتج
بدل أن تنشئ جداول منفصلة لكل نوع، يمكنك إنشاء علاقة polymorphic.
مثال جدول
images
id
imageable_id
imageable_type
path
هنا imageable_id و imageable_type يحددان الموديل المرتبط.
في الموديلات
في Image:
public function imageable()
{
return $this->morphTo();
}
في User:
public function images()
{
return $this->morphMany(Image::class, 'imageable');
}
في Post:
public function images()
{
return $this->morphMany(Image::class, 'imageable');
}
كيف تعمل؟
إذا كان عندك صورة مرتبطة بمستخدم، فـ imageable_type سيكون نوع User، وimageable_id سيكون رقم المستخدم. وإذا كانت مرتبطة بمنشور، سيتغير النوع فقط.
لماذا هذا رائع؟
لأنه يمنع التكرار. بدل أن تنشئ:
user_images
post_images
product_images
تستعمل جدولًا واحدًا مرنًا. هذا أنيق، لكنه يحتاج فهمًا جيدًا حتى لا تتحول المرونة إلى فوضى.
تاسعًا: Polymorphic One to Many
هذا النوع هو تطبيق مباشر للفكرة السابقة. مثال: منشور يمكن أن يملك عدة صور، ومنتج يمكن أن يملك عدة صور، ومستخدم يمكن أن يملك عدة صور. العلاقة هنا واحدة لكنها تخدم أنواعًا متعددة.
public function images()
{
return $this->morphMany(Image::class, 'imageable');
}
أنت هنا لا تغيّر المنطق، بل تغيّر فقط الكيان المرتبط.
عاشرًا: Polymorphic Many to Many
هذا النوع أكثر تقدّمًا. مثال مشهور: الوسوم Tags.
قد يكون لديك:
منشورات تحمل وسومًا
منتجات تحمل وسومًا
مقالات تحمل وسومًا
والوسم نفسه يمكن أن يرتبط بعدة أنواع.
في Tag
public function posts()
{
return $this->morphedByMany(Post::class, 'taggable');
}
public function products()
{
return $this->morphedByMany(Product::class, 'taggable');
}
في Post
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
في Product
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
هذا النوع قد يبدو معقدًا في البداية، لكنه في الحقيقة منطقي جدًا عندما ترى المشكلة الحقيقية التي يحلها: نفس "الكيان" يمكن أن يُستعمل من عدة نماذج دون نسخ البنية.
العلاقة من ناحية التصميم: لا تبدأ بالكود، ابدأ بالمعنى
واحدة من أكبر الأخطاء التي يقع فيها بعض المطورين أنهم يبدأون بالموديل ثم يسألون: أي دالة أستعمل؟ hasMany أم belongsTo أم morphMany؟
الأفضل أن تبدأ بالسؤال التالي: ما المعنى الحقيقي بين الكيانين؟
هل يوجد سجل واحد يقابله سجل واحد؟ → One to One
هل يوجد سجل واحد يقابله عدة سجلات؟ → One to Many
هل هناك علاقة متبادلة عبر جدول وسيط؟ → Many to Many
هل هناك وسيط بين الكيانين؟ → Through
هل نفس السجل يمكن أن يخص أكثر من نوع؟ → Polymorphic
حين تفكر بهذا الشكل، يصبح الكود نتيجة طبيعية للتصميم، لا مجرد محاولة لحل مشكلة وقتية.
التحميل المسبق Eager Loading ولماذا يهمك جدًا
واحدة من أكثر المشكلات شهرة في العلاقات هي مشكلة N+1 Queries.
وهي تحدث عندما تجلب مجموعة من السجلات، ثم في كل مرة تحاول الوصول إلى العلاقة المرتبطة بها في استعلام منفصل. النتيجة: عدد مهول من الاستعلامات غير الضرورية.
مثال سيء
$posts = Post::all();
foreach ($posts as $post) {
echo $post->user->name;
}
إذا كان لكل منشور مستخدم، فقد يحدث استعلام لكل منشور عند الوصول إلى user. ومع عدد كبير من المنشورات، يتحول الأمر إلى عبء حقيقي.
الحل
$posts = Post::with('user')->get();
foreach ($posts as $post) {
echo $post->user->name;
}
الآن Laravel سيجلب البيانات المرتبطة مسبقًا بدل أن يكرر الاستعلامات.
متى تستخدم eager loading؟
استخدمه كلما كنت متأكدًا أنك ستحتاج العلاقة مع مجموعة من السجلات. هذه قاعدة ذهبية في المشاريع العملية.
أما إذا كنت ستحتاج العلاقة في حالات قليلة جدًا، فقد لا تحتاجها دائمًا.
التحميل المتداخل
$posts = Post::with('comments.user')->get();
هنا تجلب المنشورات، وتعليقاتها، ومستخدمي التعليقات في وقت واحد. هذا ممتاز عندما تبني صفحات تعرض بيانات مترابطة.
منع Lazy Loading في البيئات المناسبة
Laravel يوفّر إمكانيات لمنع lazy loading في حالات معينة، خصوصًا أثناء التطوير، حتى لا يمر خطأ الأداء دون أن تنتبه له. هذا مفيد جدًا في الفرق الكبيرة، لأن أحد المطورين قد يضيف سطرًا بسيطًا يسبب عشرات الاستعلامات دون أن يشعر.
الفكرة ليست أن تمنع كل شيء دائمًا، بل أن تجعل الأداء جزءًا من ثقافة الفريق، لا مجرد إصلاح لاحق.
الاستعلام عبر العلاقات
واحدة من نقاط القوة في Eloquent هي أنك تستطيع أن تستعمل العلاقات داخل الاستعلام نفسه.
whereHas
$posts = Post::whereHas('comments', function ($query) {
$query->where('body', 'like', '%Laravel%');
})->get();
هنا تجلب المقالات التي لديها تعليق يحتوي على كلمة Laravel.
doesntHave / whereDoesntHave
$posts = Post::doesntHave('comments')->get();
هذا يجلب المقالات التي لا تحتوي على تعليقات.
withCount
$posts = Post::withCount('comments')->get();
الآن كل مقال سيأتي معه عدد التعليقات.
withSum و withAvg
تستطيع أيضًا أن تجمع أرقامًا مرتبطة بعلاقاتك، مثل إجمالي الطلبات، أو متوسط التقييمات، أو عدد المشاهدات. هذه الأدوات مفيدة جدًا في لوحات الإدارة والتقارير.
Has One of Many: عندما تريد عنصرًا واحدًا من عدة عناصر
من الميزات المهمة في Laravel أن العلاقة hasOne يمكن تحويلها إلى نوع "واحد من عدة" باستخدام ofMany. الفكرة هنا أن لديك عدة سجلات مرتبطة، لكنك تريد واحدًا فقط وفق قاعدة محددة، مثل أحدث سجل أو أعلى سعر أو آخر تعليق. الوثائق الرسمية تشير إلى هذا النمط ضمن العلاقات المدعومة والمستندة إلى ofMany.
مثال
لنفرض أن المستخدم لديه عدة طلبات، لكنك تريد أحدث طلب فقط:
public function latestOrder()
{
return $this->hasOne(Order::class)->latestOfMany();
}
أو نسخة أكثر تخصيصًا:
public function highestOrder()
{
return $this->hasOne(Order::class)->ofMany('total', 'max');
}
هذا ممتاز عندما تحتاج "أفضل" أو "أحدث" أو "أكبر" سجل من بين عدة سجلات مرتبطة.
العلاقات المخصصة Custom Relationships
في المشاريع المتقدمة قد لا تكفي العلاقات الجاهزة فقط، بل تحتاج أحيانًا إلى تخصيصات إضافية مثل:
تغيير اسم المفتاح الأجنبي
تغيير اسم المفتاح المحلي
تحديد جدول وسيط مختلف
تحديد مفتاح owner key غير افتراضي
مثال
public function comments()
{
return $this->hasMany(Comment::class, 'post_reference_id', 'id');
}
أو في many to many:
public function roles()
{
return $this->belongsToMany(Role::class, 'users_roles', 'user_id', 'role_id');
}
هذه التفاصيل تبدو صغيرة، لكنها مهمة جدًا عندما تعمل مع قاعدة بيانات قديمة أو تصميم ليس من صنعك.
استخدام العلاقات مع عمليات الحفظ
العلاقات لا تتوقف عند القراءة فقط. يمكنك أيضًا إنشاء البيانات المرتبطة وحفظها بطريقة نظيفة.
إنشاء سجل تابع
$post = Post::find(1);
$post->comments()->create([
'body' => 'تعليق جميل جدًا'
]);
حفظ كائن موجود
$comment = new Comment([
'body' => 'هذا تعليق جديد'
]);
$post->comments()->save($comment);
many to many
$user->roles()->attach($roleId);
أو:
$user->roles()->sync([1, 2, 3]);
لماذا هذا مهم؟
لأنك عندما تحفظ عبر العلاقة، تضمن أن المفاتيح الأجنبية تُربط بشكل صحيح، وتقلل الأخطاء، وتحافظ على منطق واضح في الكود.
ماذا عن الأسماء: singular أم plural؟
قاعدة مفيدة جدًا:
العلاقات من نوع
hasOneأوbelongsToعادة تكون بصيغة المفردالعلاقات من نوع
hasManyأوbelongsToManyتكون بصيغة الجمع
مثل:
public function profile()
public function user()
public function comments()
public function roles()
هذا ليس مجرد ذوق لغوي، بل يساعدك أيضًا في قراءة الكود بسرعة. عندما ترى comments() تعرف أنك ستتعامل مع مجموعة، وعندما ترى profile() تتوقع عنصرًا واحدًا.
بناء مثال متكامل: مدونة صغيرة
لنأخذ مثالًا عمليًا يربط كل ما سبق.
الكيانات
User
Profile
Post
Comment
Tag
العلاقات
User hasOne Profile
User hasMany Post
Post belongsTo User
Post hasMany Comment
Comment belongsTo Post
Comment belongsTo User
Post belongsToMany Tag
Tag belongsToMany Post
تعريفات الموديلات
User
class User extends Model
{
public function profile()
{
return $this->hasOne(Profile::class);
}
public function posts()
{
return $this->hasMany(Post::class);
}
public function comments()
{
return $this->hasMany(Comment::class);
}
}
Profile
class Profile extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
Post
class Post extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
public function comments()
{
return $this->hasMany(Comment::class);
}
public function tags()
{
return $this->belongsToMany(Tag::class);
}
}
Comment
class Comment extends Model
{
public function post()
{
return $this->belongsTo(Post::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
}
Tag
class Tag extends Model
{
public function posts()
{
return $this->belongsToMany(Post::class);
}
}
مثال استخدام
$posts = Post::with(['user', 'comments.user', 'tags'])->get();
foreach ($posts as $post) {
echo $post->title;
echo $post->user->name;
foreach ($post->comments as $comment) {
echo $comment->user->name . ': ' . $comment->body;
}
foreach ($post->tags as $tag) {
echo $tag->name;
}
}
هذا المثال وحده يوضح لك لماذا العلاقات مهمة: لأنك تستطيع أن تبني صفحة كاملة من بيانات مترابطة بطريقة مفهومة جدًا.
أخطاء شائعة يقع فيها المبتدئون
1) نسيان المفتاح الأجنبي أو تسميته بطريقة غير متوقعة
إذا لم يتبع اسم المفتاح الأجنبي النمط الافتراضي، قد لا يفهم Laravel العلاقة كما تتوقع. عندها يجب أن تحدد المفتاح يدويًا.
2) الخلط بين hasOne و belongsTo
هذه من أكثر الأخطاء شيوعًا. تذكّر: من يحمل المفتاح الأجنبي غالبًا belongsTo.
3) عدم استخدام eager loading
قد يعمل الكود في البداية، لكنه يصبح بطيئًا مع زيادة البيانات.
4) حفظ البيانات المرتبطة بطرق غير متسقة
أحيانًا ينشئ المطور العلاقة يدويًا في كل مرة بدل أن يستعمل create أو save أو attach، فيدخل في تكرار أخطاء غير ضرورية.
5) التعامل مع العلاقات وكأنها سحر
العلاقات ليست سحرًا؛ هي قواعد واضحة. كلما فهمتها من ناحية قاعدة البيانات، أصبحت أوضح بكثير داخل Laravel.
نصائح عملية من واقع المشاريع
إذا كنت تعمل على مشروع جديد، فابدأ من نموذج البيانات قبل كتابة الشاشات. ارسم الكيانات والعلاقات على ورقة، حتى لو كانت بسيطة. هذا سيوفر عليك ساعات لاحقًا.
إذا كانت لديك علاقة many to many، اسأل نفسك: هل pivot table يحتاج أعمدة إضافية؟
إذا كانت هناك علاقة عبر وسيط، اسأل: هل hasManyThrough أو hasOneThrough كافية؟
إذا كان نفس النوع من الملفات أو الصور سيُستخدم مع أكثر من كيان، فكر في polymorphic مبكرًا.
إذا كنت تعرض بيانات مترابطة في الصفحة، ففكر في with() قبل أن تكتب الحلقة.
هذه العادات الصغيرة تصنع تطبيقًا أكثر استقرارًا وأقل ألمًا في الصيانة.
لماذا العلاقات في Laravel محبوبة إلى هذا الحد؟
السبب الحقيقي ليس فقط أنها "مريحة". السبب أنها تجعل الكود قريبًا من اللغة الطبيعية، وقريبًا من طريقة التفكير في المشكلة. أنت لا تضيع وقتك في كتابة تفاصيل متكررة، بل تصف العلاقة مرة واحدة ثم تعيد استخدامها في كل مكان.
ولهذا السبب، عندما يتقن المطور العلاقات، يشعر أن Eloquent تحول من مجرد ORM إلى أداة تصميم.
Laravel لا يجبرك على أسلوب واحد جامد، بل يمنحك طريقة واضحة لبناء علاقاتك، ثم يفتح لك المجال للتخصيص عندما تحتاج. وهذه توازن جميل جدًا بين البساطة والمرونة.
خاتمة
فهم العلاقات Relationships في Laravel ليس فصلًا صغيرًا في تعلم Eloquent، بل هو من أهم المفاتيح التي تنقلك من مرحلة "أكتب كودًا يعمل" إلى مرحلة "أبني نظامًا مفهومًا وسهل التطوير".
العلاقات تمنحك لغة للتعبير عن البيانات، وتساعدك على تقليل التكرار، وتحسين الأداء، وتبسيط الاستعلامات، وتنظيم الموديلات، ورفع جودة البنية الداخلية للتطبيق.
إذا أتقنت One to One و One to Many و Belongs To و Many to Many، فأنت بالفعل في طريق جيد جدًا. وإذا أضفت Has Many Through و Polymorphic و eager loading و withCount و sync و attach و detach، فأنت لا تعرف العلاقات فقط، بل تعرف كيف تستخدمها بذكاء في مشاريع حقيقية.
والأجمل من ذلك كله أن Laravel لا يطلب منك أن تحفظ كل شيء دفعة واحدة؛ يكفي أن تفهم المعنى، ثم يبقى الكود وسيلة جميلة لتجسيد هذا المعنى.