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

الـ Migrations في Laravel: تعديل الجداول وتعبئة البيانات

الدرس 7 من 11 · Laravel

في درس Eloquent أنشأت جدول المقالات بملف Migration واحد. لكن المشاريع الحقيقية لا تتوقف عن التغيّر: اليوم تحتاج عموداً لعدد المشاهدات، وغداً جدولاً للتعليقات. في هذا الدرس ستتعلم كيف تطوّر قاعدة بياناتك خطوة بخطوة دون أن تفقد بياناتك، وكيف تملؤها ببيانات تجريبية بأمر واحد.

Migrations: سجل تاريخ قاعدة البيانات

Section titled “Migrations: سجل تاريخ قاعدة البيانات”

تخيّل دفتر الصيانة لسيارة: كل تعديل يُكتب في صفحة جديدة بتاريخه، ولا أحد يمسح الصفحات القديمة. ملفات Migrations كذلك: كل ملف تغيير واحد، وكلها معاً تحكي قصة قاعدة البيانات من أول يوم.

لذلك القاعدة الذهبية: لا تعدّل ملف Migration قديماً نُفّذ من قبل، خصوصاً إذا كان المشروع منشوراً أو معك فريق. إذا أردت تغييراً، فأنشئ ملفاً جديداً.

إضافة أعمدة لجدول موجود

Section titled “إضافة أعمدة لجدول موجود”

نريد أن نضيف لجدول posts عمودين: عدد المشاهدات، وصورة الغلاف. ننشئ ملفاً جديداً باسم يصف التغيير:

نافذة طرفيّة
php artisan make:migration add_views_to_posts_table
الناتج
INFO Migration [database/migrations/2026_11_11_174458_add_views_to_posts_table.php] created successfully.

Laravel ذكي: فهم من الاسم add_..._to_posts_table أنك تعدّل جدول posts، فجهّز لك Schema::table بدلاً من Schema::create. املأ الدالتين:

database/migrations/..._add_views_to_posts_table.php
public function up(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->unsignedInteger('views')->default(0);
$table->string('cover_image')->nullable();
});
}
public function down(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->dropColumn(['views', 'cover_image']);
});
}
  • up تضيف العمودين، و down تحذفهما. الدالتان متعاكستان دائماً.
  • default(0): المقالات الموجودة من قبل ستأخذ القيمة صفر تلقائياً.
  • nullable(): العمود يقبل أن يكون فارغاً، فليس لكل مقال صورة.

ثم نفّذ:

نافذة طرفيّة
php artisan migrate
الناتج
INFO Running migrations.
2026_11_11_174458_add_views_to_posts_table .......... 194.20ms DONE

لاحظ أن Laravel نفّذ الملف الجديد فقط. فهو يسجّل في جدول خاص اسمه migrations كل ملف نُفّذ، فلا يكرره.

أشهر أنواع الأعمدة وخصائصها

Section titled “أشهر أنواع الأعمدة وخصائصها”
الكود نوع العمود
$table->string('name') نص قصير (حتى 255 حرفاً)
$table->text('body') نص طويل
$table->integer('age') رقم صحيح
$table->unsignedInteger('views') رقم صحيح غير سالب
$table->decimal('price', 8, 2) رقم عشري دقيق، مناسب للأسعار
$table->boolean('active') صح أو خطأ
$table->date('birthday') تاريخ
$table->dateTime('starts_at') تاريخ ووقت
$table->foreignId('user_id') مفتاح يربط بجدول آخر

وتضيف بعد النوع خصائص بالتسلسل:

الخاصية معناها
->nullable() يقبل القيمة الفارغة
->default(قيمة) قيمة افتراضية
->unique() لا تتكرر القيمة في الجدول
->after('title') مكان العمود بعد عمود آخر (في MySQL)

متابعة الحالة والتراجع

Section titled “متابعة الحالة والتراجع”

لتعرف ما نُفّذ وما لم يُنفّذ:

نافذة طرفيّة
php artisan migrate:status
الناتج
Migration name .................................. Batch / Status
0001_01_01_000000_create_users_table .................. [1] Ran
0001_01_01_000001_create_cache_table .................. [1] Ran
0001_01_01_000002_create_jobs_table ................... [1] Ran
2026_11_10_230319_create_posts_table .................. [2] Ran
2026_11_11_174458_add_views_to_posts_table ............ [3] Ran

الرقم بين القوسين هو الدفعة (Batch): كل مرة تشغّل فيها migrate تُحسب دفعة. وإذا اكتشفت خطأ في آخر ما نفّذته، تتراجع عن الدفعة الأخيرة:

نافذة طرفيّة
php artisan migrate:rollback
الناتج
INFO Rolling back migrations.
2026_11_11_174458_add_views_to_posts_table .......... 37.11ms DONE

هذا الأمر استدعى الدالة down، فحُذف العمودان، وعادت حالة الملف إلى Pending. صحّح الملف ثم شغّل migrate من جديد.

الأمر وظيفته
migrate ينفّذ الملفات الجديدة فقط
migrate:status يعرض حالة كل ملف
migrate:rollback يتراجع عن آخر دفعة
migrate:fresh يحذف كل الجداول ويبنيها من الصفر

Seeders: بيانات تجريبية جاهزة

Section titled “Seeders: بيانات تجريبية جاهزة”

بعد كل migrate:fresh تصبح الجداول فارغة، وإدخال مقالات باليد في كل مرة ممل. الـ Seeder كلاس يملأ الجداول ببيانات تجريبية:

نافذة طرفيّة
php artisan make:seeder PostSeeder
database/seeders/PostSeeder.php
<?php
namespace Database\Seeders;
use App\Models\Post;
use Illuminate\Database\Seeder;
class PostSeeder extends Seeder
{
public function run(): void
{
Post::create([
'title' => 'مرحباً بكم في المدونة',
'body' => 'هذا أول مقال تجريبي في مدونتنا.',
'published' => true,
]);
Post::create([
'title' => 'مسودة لم تكتمل',
'body' => 'مقال ما زال قيد الكتابة.',
]);
}
}

ثم سجّله في الـ Seeder الرئيسي:

database/seeders/DatabaseSeeder.php
public function run(): void
{
$this->call([
PostSeeder::class,
]);
}

والآن أمر واحد يبني القاعدة من الصفر ويملؤها:

نافذة طرفيّة
php artisan migrate:fresh --seed

Factories: عشرات السجلات في سطر

Section titled “Factories: عشرات السجلات في سطر”

ماذا لو أردت خمسين مقالاً لتجرّب تقسيم الصفحات؟ الـ Factory قالب يصنع سجلات ببيانات عشوائية:

نافذة طرفيّة
php artisan make:factory PostFactory
database/factories/PostFactory.php
public function definition(): array
{
return [
'title' => fake()->sentence(4),
'body' => fake()->paragraph(),
'published' => fake()->boolean(70),
];
}

الدالة fake() تولّد بيانات وهمية: جملة من أربع كلمات، وفقرة، وقيمة منطقية تكون true في 70% من الحالات. ولكي يتعرف الـ Model على المصنع، أضف له HasFactory:

app/Models/Post.php
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Post extends Model
{
use HasFactory;
protected $fillable = ['title', 'body', 'published'];
}

والآن أضف هذا السطر في آخر الدالة run داخل PostSeeder:

Post::factory()->count(10)->create();

بعد migrate:fresh --seed سيكون في الجدول 12 مقالاً: اثنان كتبناهما بأيدينا، وعشرة من المصنع بعناوين مثل Et saepe dolorem sit. (نص لاتيني وهمي يستخدمه المصممون لملء الفراغ).

  1. أنشئ Migration يضيف لجدول posts عموداً اسمه slug من نوع string وخاصيته unique و nullable.
  2. نفّذه، ثم تأكد بـ migrate:status، ثم تراجع عنه بـ rollback، ثم نفّذه مرة أخرى.
  3. عدّل PostFactory ليملأ العمود الجديد بـ fake()->unique()->slug().
  4. شغّل migrate:fresh --seed وافتح صفحة /posts لترى المقالات.
  • تعديل ملف Migration قديم: لن يُنفَّذ مرة ثانية، لأنه مسجّل كمنفَّذ. أنشئ ملفاً جديداً.
  • دالة down فارغة: عندها لا يعيد rollback شيئاً إلى أصله. اكتب دائماً عكس ما في up.
  • عمود إجباري بلا قيمة افتراضية في جدول فيه بيانات: السجلات القديمة لا تعرف ماذا تضع فيه، فيفشل التنفيذ. أضف default أو nullable.
  • نسيان HasFactory: استدعاء Post::factory() بدونه يعطي خطأ بأن التابع غير موجود.

سؤال

ماذا يفعل الأمر php artisan migrate:rollback؟

سؤال

تريد إضافة عمود لجدول موجود في مشروع منشور. ما التصرف الصحيح؟

  • كل تغيير في قاعدة البيانات له ملف Migration جديد، ولا نعدّل الملفات المنفَّذة.
  • Schema::table تعدّل جدولاً موجوداً، و up و down متعاكستان.
  • migrate:status للمتابعة، و rollback للتراجع، و fresh للبناء من الصفر.
  • الـ Seeders تملأ الجداول ببيانات تجريبية، وتُشغَّل بـ --seed.
  • الـ Factories تصنع أي عدد من السجلات الوهمية بـ fake().