لارافيل, Model / 2024-05-14

العلاقات HasManyThrough - belongs-to-through - hasManyDeep في لارافيل

العلاقات HasManyThrough - belongs-to-through - hasManyDeep في لارافيل

2024-05-14 وقت القراءه : 5 دقائق

في كل مشروع فإننا نتعامل مع العلاقات بين الجداول (hasMany, belongsTo, hasOne.....)، في هذه المقالة سنتحدث عن العلاقات بشكل معمق.

لفرض أن لدي الجداول التالية

public function up()
{
    Schema::create('countries', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->timestamps();
    });
}
public function up()
{
    Schema::create('cities', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->foreignId('country_id')->constrained();
        $table->timestamps();
    });
}
public function up()
{
    Schema::create('doctors', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->foreignId('city_id')->constrained();
        $table->timestamps();
    });
}


كما نلاحظك أن لدي جدول countries وهو يحتوي على مجموعة من المدن وكل مدينة يتواجد بها مجموعة من الأطباء.

لنفرض أننا نريد أن يتم إظهار الدولة مع عدد الأطباء المتواجدين بها؟

هنا في Country Model نحتاج لتحديد العلاقة بين جدولي countries و doctors

class Country extends Model
{
    use HasFactory;
    public function doctors(){
        return $this->hasManyThrough(Doctor::class, City::class);
    }
}

كما نلاحظ أن نوع العلاقة هو hasManyThrough اي أن الدولة تحتوي على العديد من الأطباء ويتم الربط بين الجدولين من خلال City Model.


لكتابة جملة الإستعلام في الـ CountryController

class DoctorController extends Controller
{
    public function index(){
       $countries =  Country::with('doctors')->get();
       return view('country',compact('countries'));
    }
}

وفي ملف blade للعرض

@foreach($countries as $country)
    <tr>
        <td>{{ $country->name }}</td>
        <td>{{ $country->doctors->count() }}</td>
    </tr>
@endforeach

لكن نلاحظ في جملة الإستعلام أننا قمنا بتحميل جميع الـ Doctor Model ونحن نحتاج فقط إلى العدد، لذلك يمكن تعديل جملة الإستعلام وإستخدام الدالة 

class DoctorController extends Controller
{
    public function index(){
       $countries =  Country::withCount('doctors')->get();
       return view('country',compact('countries'));
    }
}

وفي ملف blade يمكن الوصول للعدد من خلال doctors_count كـ property

@foreach($countries as $country)
    <tr>
        <td>{{ $country->name }}</td>
        <td>{{ $country->doctors_count }}</td>
    </tr>
@endforeach

لو ألقينا نظرة على laravel debug bar

نرى أنه لا مشكله حيث تم تنفيذ جملة إستعلام واحدة وبوقت قليل جداً.


ماذا إذا طلب منا أن يتم عرض قائمة الأطباء مع الدول التي يعملون بها؟

بداية نحتاج لتحديد العلاقة بين Doctor Model و City Model

class Doctor extends Model
{
    use HasFactory;
    public function city(){
        return $this->belongsTo(City::class);
    }
}

ومن ثم تحديد العلاقة بين City Model و Country Model

class City extends Model
{
    use HasFactory;
    public function country(){
        return $this->belongsTo(Country::class);
    }
}

ومن ثم كتابة جملة الإستعلام

public function index(){
    $doctors=Doctor::with('city.country')->get();
    return view('country',compact('doctors'));
}

هنا يجب الإنتباه إنه تم إستخدام eager loading

وفي ملف blade للعرض

@foreach($doctors as $doctor)
    <tr>
        <td>{{ $doctor->name }}</td>
        <td>{{ $doctor->city->country->name }}</td>
    </tr>
@endforeach

بالتالي سوف نحصل على هذه النتيجة

لو ألقينا نظرة على laravel debug bar

نرى إنه تم تنفيذ ٣ جمل إستعلام، وهذا جيد، لكن نحن لا نريد المدن ونرى إنه تم جلب City Model ونحن لسنا بحاجة إليه، ولتجنب ذلك، يمكن إستخدام حزمة 

staudenmeir/belongs-to-through
composer require staudenmeir/belongs-to-through:"^2.5"

كما نلاحظ من إسم الإضافة أنها توفر لنا علاقة جديده وهي belongs-to-through فهي لا توفرها لارافيل بالشكل الإفتراضي بل توفر hasMany-Through

بعد تثبيت الإضافة نحتاج لإستخدام trait التالي في Doctor Model

use \Znck\Eloquent\Traits\BelongsToThrough;
class Doctor extends Model
{
    use HasFactory;
    use \Znck\Eloquent\Traits\BelongsToThrough;
    public function city(){
        return $this->belongsTo(City::class);
    }

    public function country(){
        return $this->belongsToThrough(Country::class, City::class);
    }
}

بعد إضافة trait نستطيع إضافة علاقة belongsToThrough في Doctor Model وذلك حتى يتم الربط بين جدولي Doctor  و Country من خلال City::class.

الأن في eloquent نستطيع إستخدام country دون إستخدام city

public function index(){
    $doctors=Doctor::with('country')->get();
    return view('country',compact('doctors'));
}

وفي ملف blade

@foreach($doctors as $doctor)
    <tr>
        <td>{{ $doctor->name }}</td>
        <td>{{ $doctor->country->name }}</td>
    </tr>
@endforeach


لو ألقينا نظره على laravel debug bar

نرى أنه تم تنفيذ جملتين إستعلام، وكذلك لم يتم تحميل City Model


ماذا لو أن كل طبيب يتبع له مجموعة من المرضى، ونريد أن يتم عرض كل دولة مع المرضى الموجودين بها؟

بداية نحتاج لإنشاء جدول Patients 

public function up()
{
    Schema::create('patients', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->foreignId('doctor_id')->constrained();
        $table->timestamps();
    });
}

كما نلاحظ أنه تم إضافة حقل doctor_id وهو foreign key.

ولأداء أفضل فإنه يفضل إستخدام إضافة 

staudenmeir/eloquent-has-many-deep
composer require staudenmeir/eloquent-has-many-deep:"^1.7"

حيث يمكن لنا إستخدام علاقة جديدة وهي hasManyDeep

بعد تثبين الحزمة في Country Model يمكن لنا إستخدام العلاقة مع إستخدام الـ trait

    use \Staudenmeir\EloquentHasManyDeep\HasRelationships;


class Country extends Model
{
    use HasFactory;
    use \Staudenmeir\EloquentHasManyDeep\HasRelationships;

    public function patients()
    {
        return $this->hasManyDeep(Patient::class, [City::class, Doctor::class]);
    }
}

كما نلاحظ إنه تم إستخدام مصفوفة من الـ model، ويمكننا إضافة المزيد إن إحتجنا لذلك، وكذلك تم إستخدام العلاقة hasManyDeep.

في eloquent

class DoctorController extends Controller
{
    public function index(){
        $countries =  Country::withCount('patients')->get();
       return view('country',compact('countries'));
    }
}

وفي blade

@foreach($countries as $country)
    <tr>
        <td>{{ $country->name }}</td>
        <td>{{ $country->patients_count }}</td>
    </tr>
@endforeach

وبالنظر إلى debug bar

وبذلك نكون قد جلبنا الدولة مع عدد المرضى التي بداخلها.


التعليقات
زائر
منذ سنة

كيف انشاء العلاقة اذاكانت من hasMany الى belongsToMany ...

محمد
منذ سنة

عندي المنشور له عدة tags تابعة لقسم كيف احصل عل المنشور ات من الاقسام

mahmoud
منذ سنة

very good

إضافة تعليق
Loading...