تخطَّ إلى المحتوى

قواعد البيانات و Eloquent في Laravel

الدرس 5 من 11 · Laravel

في درس قاعدة البيانات من كورس PHP كتبت استعلامات SQL بنفسك عبر PDO. Laravel يجعل هذا أسهل وأوضح بكثير بأداتين: Migrations لبناء الجداول بكود PHP، و Eloquent للتعامل مع البيانات وكأنها كائنات عادية. وهذا هو حرف M (Model) في نمط MVC. في هذا الدرس ستبني جدول مقالات وتتعامل معه بالكامل.

إعدادات الاتصال موجودة في ملف .env في جذر المشروع. المشاريع الجديدة تستخدم SQLite افتراضياً، وهي قاعدة بيانات في ملف واحد لا تحتاج أي تثبيت، ومناسبة جداً للتعلم:

.env
DB_CONNECTION=sqlite

وإذا أردت MySQL لاحقاً، تغيّر هذه الإعدادات فقط:

.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=blog
DB_USERNAME=root
DB_PASSWORD=

والجميل أن باقي الكود لا يتغير: نفس الـ Migrations ونفس Eloquent يعملان مع أي قاعدة بيانات.

نافذة طرفيّة
php artisan make:model Post -m

هذا الأمر ينشئ ملفين:

  • الـ Model: app/Models/Post.php.
  • الـ Migration (بسبب -m): ملف في database/migrations اسمه يبدأ بالتاريخ وينتهي بـ create_posts_table.php.

لاحظ العُرف: اسم الـ Model مفرد بحرف كبير Post، و Laravel يربطه تلقائياً بجدول جمع بحروف صغيرة posts.

الـ Migration مثل مخطط هندسي للجدول، مكتوب بـ PHP:

database/migrations/..._create_posts_table.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 migrate
الناتج
INFO Running migrations.
2026_11_10_230319_create_posts_table .......... DONE

ميزة Migrations الكبرى أنها سجل لتاريخ قاعدة البيانات: أي زميل في فريقك يشغّل php artisan migrate فيحصل على نفس الجداول بالضبط. وللتراجع عن آخر دفعة: php artisan migrate:rollback.

الـ Model والحماية من الإسناد الجماعي

Section titled “الـ Model والحماية من الإسناد الجماعي”
app/Models/Post.php
<?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.

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 يترجم بين عالم الكائنات وعالم الجداول.

افترض أن الجدول فيه ثلاثة مقالات، الثاني والثالث منها منشوران:

Post::all(); // كل المقالات
Post::count(); // 3
Post::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 بالمتحكم.
$post = Post::find(1);
$post->title = 'أول مقال (معدّل)';
$post->save(); // يحدّث updated_at تلقائياً
Post::find(3)->delete();
Post::count(); // 2

من قاعدة البيانات إلى الصفحة

Section titled “من قاعدة البيانات إلى الصفحة”

الآن يجتمع كل شيء: المتحكم يجلب البيانات بـ Eloquent ويرسلها لـ Blade:

app/Http/Controllers/PostController.php
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.

  1. أنشئ Post مع Migration، وأضف الأعمدة الثلاثة، ثم php artisan migrate.
  2. أضف $fillable في الـ Model.
  3. افتح php artisan tinker وأضف ثلاثة مقالات بـ Post::create.
  4. جرّب Post::count() و Post::find(1) و Post::where('published', true)->get().
  5. تحدٍّ: اربط PostController@index بصفحة Blade تعرض عناوين المقالات من قاعدة البيانات.
  • خطأ Add [title] to fillable property: نسيت إضافة العمود إلى $fillable قبل استخدام create.
  • خطأ no such table: posts: نسيت تشغيل php artisan migrate.
  • تعديل Migration قديمة بعد تشغيلها: لن يتغير الجدول. أنشئ Migration جديدة للتعديل، أو أثناء التطوير فقط استخدم php artisan migrate:fresh (تحذف كل البيانات!).

سؤال

ما اسم الجدول الذي يرتبط به Model اسمه Post تلقائياً؟

سؤال

ما فائدة $fillable في الـ Model؟

  • إعدادات قاعدة البيانات في .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 للتجربة السريعة.