جدول المحتويات
في هذه المقالة سأحاول تبسيط مفهوم pivot table، بشكل مبسط يمكن تعريفة أنه جدول يربط بين جدولين.
لنفرض أن لدي الجداول التالية books, authors بحيث أن كل مؤلف له أكثر من كتاب، وكل كتاب شارك به أكثر من مؤلف، إذا ستكون الجداول لدي على الشكل التالي:
Authors -id -name
Books -id. -name.
السؤال هنا، كيف سيتم ربط الجدولين، بمعنى كيف سيتم ربط الكتاب بالمؤلفين، وكذلك المؤلف بالكتب.
هنا نحتاج لجدول وسيط وهو ما يطلق علية pivot table ويجب أن يكون إسمة author_book لأنة حسب النظام المتعارف عليه يجب أن يكون الإسم فردي، وبترتيب أبجدي، فلو كان لدي مثلا جدول shops وجدول products إذا إسم pivot table يجب أن يكون product_shop.
يوجد مجموعة من الإعتبارات التي يجب الإنتبهاه لها عند إنشاء pivot table وأهمها:
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(); }); }
نستطيع وضع العلاقة في أي 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.
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); }
لعرض البيانات بإستخدام 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 } } ] }
في إدخال البيانات تم إستخدام دالة attach أما في حذف البيانات فنستخدم دالة detach.
public function delete($id, Request $request){ $author=Author::findOrFail($id); $author->Books()->detach($request->book_id); }
كما نستطيع أيضا حذف مصفوفة كاملة
$author->Books ()->deattach([123, 456, 789]);
لفرض أننا نريد التعديل الكتب للمؤلف بالمعرف 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]);
هذه الدالة في عملية التعديل أيضا لكن لا تقوم بحذف السجلات القديمه من pivot table ، فقط يتم إضافة الحقول الغير موجوده.
public function update($id, Request $request){ $author=Author::findOrFail($id); $author->Books()->syncWithoutDetaching($request->book_id); }
ببعض الأحيان يتطلب التطبيق أن يتم إضافة حقول إضافية إلى جدول 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]);
لدي مشكلة دائما يعيد هذا الخطأ call to a member function attach() on null يرجى المساعدة وشكراً
زائر
رائع جدااااااا استمر استمر استمر