قواعد البيانات و Eloquent في Laravel
في درس قاعدة البيانات من كورس PHP كتبت استعلامات SQL بنفسك عبر PDO. Laravel يجعل هذا أسهل وأوضح بكثير بأداتين: Migrations لبناء الجداول بكود PHP، و Eloquent للتعامل مع البيانات وكأنها كائنات عادية. وهذا هو حرف M (Model) في نمط MVC. في هذا الدرس ستبني جدول مقالات وتتعامل معه بالكامل.
إعداد قاعدة البيانات
Section titled “إعداد قاعدة البيانات”إعدادات الاتصال موجودة في ملف .env في جذر المشروع. المشاريع الجديدة تستخدم SQLite افتراضياً، وهي قاعدة بيانات في ملف واحد لا تحتاج أي تثبيت، ومناسبة جداً للتعلم:
DB_CONNECTION=sqliteوإذا أردت MySQL لاحقاً، تغيّر هذه الإعدادات فقط:
DB_CONNECTION=mysqlDB_HOST=127.0.0.1DB_PORT=3306DB_DATABASE=blogDB_USERNAME=rootDB_PASSWORD=والجميل أن باقي الكود لا يتغير: نفس الـ Migrations ونفس Eloquent يعملان مع أي قاعدة بيانات.
Model و Migration بأمر واحد
Section titled “Model و Migration بأمر واحد”php artisan make:model Post -mهذا الأمر ينشئ ملفين:
- الـ Model:
app/Models/Post.php. - الـ Migration (بسبب
-m): ملف فيdatabase/migrationsاسمه يبدأ بالتاريخ وينتهي بـcreate_posts_table.php.
لاحظ العُرف: اسم الـ Model مفرد بحرف كبير Post، و Laravel يربطه تلقائياً بجدول جمع بحروف صغيرة posts.
الـ Migration: مخطط الجدول
Section titled “الـ Migration: مخطط الجدول”الـ Migration مثل مخطط هندسي للجدول، مكتوب بـ PHP:
public function up(): void{ Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->text('body'); $table->boolean('published')->default(false); $table->timestamps(); });}
public function down(): void{ Schema::dropIfExists('posts');}id(): رقم تلقائي يزيد مع كل سجل.string('title'): نص قصير. وtext('body'): نص طويل.boolean('published')->default(false): صح أو خطأ، وقيمته الافتراضية خطأ.timestamps(): عمودانcreated_atوupdated_atيملؤهما Laravel تلقائياً.up: ما يُنفَّذ عند التشغيل، وdown: كيف نتراجع عنه.
ثم تنفّذ المخطط:
php artisan migrateINFO Running migrations.
2026_11_10_230319_create_posts_table .......... DONEميزة Migrations الكبرى أنها سجل لتاريخ قاعدة البيانات: أي زميل في فريقك يشغّل php artisan migrate فيحصل على نفس الجداول بالضبط. وللتراجع عن آخر دفعة: php artisan migrate:rollback.
الـ Model والحماية من الإسناد الجماعي
Section titled “الـ Model والحماية من الإسناد الجماعي”<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model{ protected $fillable = ['title', 'body', 'published'];}الخاصية $fillable قائمة بيضاء بالأعمدة المسموح ملؤها دفعة واحدة. لماذا؟ لو أرسل مهاجم حقلاً إضافياً مثل is_admin=1 مع النموذج، يتجاهله Laravel تلقائياً لأنه ليس في القائمة. هذه الحماية تسمى Mass Assignment Protection.
الإضافة
Section titled “الإضافة”use App\Models\Post;
// طريقة 1: create مع مصفوفة (تعتمد على $fillable)$post = Post::create([ 'title' => 'أول مقال', 'body' => 'مرحباً بكم في مدونتي الجديدة.',]);echo $post->id; // 1
// طريقة 2: كائن جديد ثم save$post = new Post();$post->title = 'مقال ثانٍ';$post->body = 'محتوى المقال الثاني.';$post->published = true;$post->save();لاحظ: لم تكتب سطر SQL واحداً! كل صف في الجدول أصبح كائناً، وكل عمود خاصية فيه. هذه الفكرة اسمها ORM (Object-Relational Mapping): Eloquent يترجم بين عالم الكائنات وعالم الجداول.
القراءة
Section titled “القراءة”افترض أن الجدول فيه ثلاثة مقالات، الثاني والثالث منها منشوران:
Post::all(); // كل المقالاتPost::count(); // 3Post::find(2)->title; // "مقال ثانٍ"Post::find(99); // null: غير موجود
Post::where('published', true)->pluck('title');// ["مقال ثانٍ", "مقال ثالث"]
Post::where('published', true)->orderBy('id', 'desc')->first()->title;// "مقال ثالث"whereتضيف شرطاً، ويمكنك سلسلة عدة دوال وراء بعضها.get()تُرجع مجموعة النتائج، وfirst()أول نتيجة فقط، وpluckعموداً واحداً.latest()اختصار لترتيب الأحدث أولاً.findOrFail($id): مثلfind، لكن إذا لم يجد السجل يعرض صفحة 404 تلقائياً. مثالية في دالةshowبالمتحكم.
التعديل والحذف
Section titled “التعديل والحذف”$post = Post::find(1);$post->title = 'أول مقال (معدّل)';$post->save(); // يحدّث updated_at تلقائياً
Post::find(3)->delete();Post::count(); // 2من قاعدة البيانات إلى الصفحة
Section titled “من قاعدة البيانات إلى الصفحة”الآن يجتمع كل شيء: المتحكم يجلب البيانات بـ Eloquent ويرسلها لـ Blade:
public function index(){ $posts = Post::latest()->get(); return view('posts.index', ['posts' => $posts]);}
public function show(int $id){ $post = Post::findOrFail($id); return view('posts.show', compact('post'));}وفي الـ View تقرأ الأعمدة كخصائص: {{ $post->title }}. وزيارة /posts/999 لمقال غير موجود تعطي 404 بفضل findOrFail.
جرّب بنفسك
Section titled “جرّب بنفسك”- أنشئ
Postمع Migration، وأضف الأعمدة الثلاثة، ثمphp artisan migrate. - أضف
$fillableفي الـ Model. - افتح
php artisan tinkerوأضف ثلاثة مقالات بـPost::create. - جرّب
Post::count()وPost::find(1)وPost::where('published', true)->get(). - تحدٍّ: اربط
PostController@indexبصفحة Blade تعرض عناوين المقالات من قاعدة البيانات.
أخطاء شائعة
Section titled “أخطاء شائعة”- خطأ
Add [title] to fillable property: نسيت إضافة العمود إلى$fillableقبل استخدامcreate. - خطأ
no such table: posts: نسيت تشغيلphp artisan migrate. - تعديل Migration قديمة بعد تشغيلها: لن يتغير الجدول. أنشئ Migration جديدة للتعديل، أو أثناء التطوير فقط استخدم
php artisan migrate:fresh(تحذف كل البيانات!).
اختبر نفسك
Section titled “اختبر نفسك”سؤال
ما اسم الجدول الذي يرتبط به Model اسمه Post تلقائياً؟
عُرف Laravel: الـ Model مفرد بحرف كبير، والجدول جمع بحروف صغيرة.
سؤال
ما فائدة $fillable في الـ Model؟
$fillable قائمة بيضاء تمنع المهاجم من ملء أعمدة حساسة عبر حقول إضافية في النموذج.
الخلاصة
Section titled “الخلاصة”- إعدادات قاعدة البيانات في
.env، و SQLite افتراضية ومريحة للتعلم. make:model Post -mينشئ الـ Model والـ Migration، وphp artisan migrateينفّذها.$fillableيحمي من الإسناد الجماعي لأعمدة غير مقصودة.- Eloquent:
createوallوfindوwhereوsaveوdeleteبدل كتابة SQL. findOrFailتعرض 404 تلقائياً، وphp artisan tinkerللتجربة السريعة.