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

ما هو Pivot Table في علاقة Many To Many ، ومتى يستخدم وكيفية إدخال وتعديل وحذف البيانات.

ما هو Pivot Table في علاقة Many To Many ، ومتى يستخدم وكيفية إدخال وتعديل وحذف البيانات.

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

جدول المحتويات



في هذه المقالة سأحاول تبسيط مفهوم pivot table، بشكل مبسط يمكن تعريفة أنه جدول يربط بين جدولين.

لنفرض أن لدي الجداول التالية books, authors بحيث أن كل مؤلف له أكثر من كتاب، وكل كتاب شارك به أكثر من مؤلف، إذا ستكون الجداول لدي على الشكل التالي:

Authors
  -id
  -name
Books
  -id.
  -name.

السؤال هنا، كيف سيتم ربط الجدولين، بمعنى كيف سيتم ربط الكتاب بالمؤلفين، وكذلك المؤلف بالكتب.


هنا نحتاج لجدول وسيط وهو ما يطلق علية pivot table ويجب أن يكون إسمة author_book لأنة حسب النظام المتعارف عليه يجب أن يكون الإسم فردي، وبترتيب أبجدي، فلو كان لدي مثلا جدول shops وجدول products إذا إسم pivot table يجب أن يكون product_shop.


إنشاء الجدول الوسيط Pivot table

يوجد مجموعة من الإعتبارات التي يجب الإنتبهاه لها عند إنشاء pivot table وأهمها:

  • إسم جدول pivot table: يجب أن يكون الإسم فردي، ومكون من إسم الجدولين، ويتم الفصل بينهما بـ underscore ويكون الإسم الأول حسب الترتيب الأبجدي، إذا هنا سيكون author_book.
  • ملف migration: لإنشاء الجدول الوسيط ننشئ نستخدم الأمر التالي، لإنشاء ملف migration 
php artisan make:migration create_author_book_table

ويجب أن يحتوي على الحقول التالية author_id, book_id

public function up()
{
    Schema::create('author_book', function (Blueprint $table) {
         $table->foreignId('author_id')->constrained();
         $table->foreignId('book_id')->constrained();
    });
}

 

  • الحقول : بالوضع الإفتراضي، يجب أن يحتوي الجدول على حقلين وهما author_id, book_id، كما نستطيع إضافة المزيد من الحقول، لكن يجب تحديد هذه الحقول في العلاقة، سيتم توضيحها لاحقا.



العلاقة بين Models في علاقة Many To Many.

نستطيع وضع العلاقة في أي model وكذلك في الـ two model :

الخيار الأول:

App/Models/Author
class Author extends Model
{
    use HasFactory;
 
    public function Books(){
        return $this->belongsToMany('App\Book');
    }
}

 

الخيار الثاني

App/Models/Book
class Book extends Model
{
    use HasFactory;
 
    public function Authors(){
        return $this->belongsToMany('App\Models\Author');
    }
}

 

 كما تم ذكره نستطيع أيضا أن نضع العلاقه في الـ two model وهذا يعتمد على طريقة وكيفية إستخدامنا للعلاقة.

كما تم ذكره فإطار العمل لارافيل يفترض أن إسم pivot table سيكون author_book وذلك حسب التعليمات التي تم ذكرها سابقا، لكن لفرض أنه تم تغيير إسم الجدول مثلا إلى authors_books هنا لن يتعرف عليه لارافيل، وحتى يتعرف عليه، يجب إضافة إسم الجدول كخيار ثاني في العلاقة.

class Author extends Model
{
    use HasFactory;
 
    public function Books(){
        return $this->belongsToMany('App\Book', 'authors_books');
    }
}

 

كذلك يفترض لارافيل أن أسماء الحقول هو author_id, book_id لكن لفرض أن الأسماء مختلفة مثلا authors_id, books_id إذا يجب أن يتم تعريف ذلك في العلاقة أيضا

class Author extends Model
{
    use HasFactory;
 
    public function Books(){
        return $this->belongsToMany('App\Book', 'authors_books','authors_id','books_id');
    }
}

 

واحدة من أفضل الفوائد هنا، إننا لن نحتاج إلى model لجدول author_book ، فنستطيع إدارة الجدول من خلال أوامر pivot، والتي سيتم مناقشتها لاحقا.


عمليات الإدخال والحذف والتعديل attach-detach-sync.

الإدخال إلى جدول pivot table في علاقة many to many

على سبيل المثال إذا أردنا إضافة كتاب جديد لمؤلف، فإننا نستخدم العلاقه ومن ثم نستخدم دالة attach.

public function store(Request $request){
    $author=Author::findOrFail($request->author_id);
    $author->Books()->attach($request->book_id);
}

 

كما نستطيع عمل attach لمصفوفة من القيم

$author->Books ()->attach([123, 456, 789]);

لو إنه لدينا الفورم التالي لإدخال البيانات

 

                                                                                                                                    

<div class="card-body">
    <form method="POST" action="{{ route('store') }}">
    @csrf
    <table class="table table-bordered">
        <tr>
            <td>
                <select name="author_id" class="form-control">
                    <option value="">Please Select</option>
                     @foreach($authors as $author)
                        <option value="{{ $author->id }}">{{ $author->name }}</option>
                    @endforeach
                </select>
            </td>
        </tr>
        <tr>
            <td>
                <select name="book_id[]" class="form-control" multiple>
                    <option value="">Please Select</option>
                     @foreach($books as $book)
                        <option value="{{ $book->id }}">{{ $book->name }}</option>
                    @endforeach
                </select>
            </td>
        </tr>
        <tr>
            <td><button class="btn btn-dark">Add</button></td>
        </tr>
    </table>
</form>

 ولحفظ البيانات من خلال دالة store.

public function store(Request $request){
     $author=Author::findOrFail($request->author_id);
     $author->Books()->attach($request->book_id);
}

 

عرض البيانات في علاقة many to many

لعرض البيانات بإستخدام eloquent نستخدم with  لإضافة العلاقة

public function index(){
    $author=Author::with('Books')->findOrFail(1);
    return $author;
}

 

حيث نحصل على النتائج التالية

{
  "id": 1,
  "name": "Author 1",
  "created_at": null,
  "updated_at": null,
  "books": [
      {
         "id": 1,
         "name": "book 1",
         "created_at": null,
         "updated_at": null,
         "pivot": {
             "author_id": 1,
             "book_id": 1
            }
      }
   ]
}

 

حذف البيانات من جدول pivot في علاقة many to many

في إدخال البيانات تم إستخدام دالة attach أما في حذف البيانات فنستخدم دالة detach.

 

public function delete($id, Request $request){
     $author=Author::findOrFail($id);
     $author->Books()->detach($request->book_id);
}

 

كما نستطيع أيضا حذف مصفوفة كاملة

$author->Books ()->deattach([123, 456, 789]);

 

تعديل البيانات في جدول pivot في علاقة many to many

تعديل البيانات بإستخدام async في جدول pivot table.

لفرض أننا نريد التعديل الكتب للمؤلف بالمعرف id=1 

قاعدة بيانات authors


قاعدة بيانات الكتب

 


جدول pivot

public function edit($id){
     $author=Author::with('Books')->findOrFail(1);
     $books=Book::select('id','name')->get();
    return view('author',compact('author','books'));
}

وفي ملف blade

<div class="card">
     <form method="POST" action="{{ route('author.update',$author->id)}}">
         @method('PUT')
         @csrf
         <div class="card-header">{{ $author->name }}</div>
         <div class="card-body">
             @foreach($books as $book)
                 <div class="form-check">
                    <input class="form-check-input" type="checkbox"
                            name="book_id[]"
                           value="{{ $book->id }}" {{ $author->books->pluck('id')->contains($book->id) ? 'checked' : '' }}>
                    <label class="form-check-label">{{ $book->name }}</label>
                 </div>
             @endforeach
         </div>
         <button class="btn btn-danger">Edit</button>
     </form>
</div>

 

ولتعديل البيانات في eloquent

public function update($id, Request $request){
     $author=Author::findOrFail($id);
     $author->Books()->sync($request->book_id);
}

 

عند عملية التعديل نستخدم sync ، لكن في عملية التعديل بإستخدام sync فإنه سيتم حذف جميع الكتب التابعة للمؤلف بالمعرف id=1  وإعادة إدخالها من جديد.

كما نلاحظ أن الدالة sync تقبل مصفوفة 

$author->Books()->sync([1,2,3]);

 

تعديل البيانات بإستخدام syncWithoutDetaching  لبيانات جدول pivot table:

 هذه الدالة في عملية التعديل أيضا لكن لا تقوم بحذف السجلات القديمه من pivot table ، فقط يتم إضافة الحقول الغير موجوده.

public function update($id, Request $request){
    $author=Author::findOrFail($id);
    $author->Books()->syncWithoutDetaching($request->book_id);
}


إضافة حقول إضافية إلى Pivot Tables

ببعض الأحيان يتطلب التطبيق أن يتم إضافة حقول إضافية إلى جدول pivot table، على سبيل المثال قد نرغب بإضافة حقل price، إلا أن لارافيل لن تتعرف عليه، وحتى تتعرف عليه يجب تعريف ذلك في الموديل

class Book extends Model
{
    use HasFactory;
 
    public function Authors(){
         return $this->belongsToMany('App\Models\Author')
             ->withPivot('price');
    }
}

 

أما إذا كنا نرغب في إضافة timestamps فبعد إضافة الحقل يجب التعديل في الموديل أيضا وإضافة withTimestamps

class Book extends Model
{
    use HasFactory;
 
    public function Authors(){
         return $this->belongsToMany('App\Models\Author')
             ->withPivot('price')
             ->withTimestamps();
    }
}

 

ولطباعة السعر في ملف blade يتم ذلك من خلال property تسمى pivot

foreach ($author->books as $author)
{
    echo $author->pivot->price;
}

 

ولإدخال السعر إلى جدول السعر 

$author->Books()->attach($request->book_id,['price'=>49.99]);

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

رائع جدااااااا استمر استمر استمر

Mohammad
منذ سنة

لدي مشكلة دائما يعيد هذا الخطأ call to a member function attach() on null يرجى المساعدة وشكراً

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