الـ Migrations في Laravel: تعديل الجداول وتعبئة البيانات
في درس Eloquent أنشأت جدول المقالات بملف Migration واحد. لكن المشاريع الحقيقية لا تتوقف عن التغيّر: اليوم تحتاج عموداً لعدد المشاهدات، وغداً جدولاً للتعليقات. في هذا الدرس ستتعلم كيف تطوّر قاعدة بياناتك خطوة بخطوة دون أن تفقد بياناتك، وكيف تملؤها ببيانات تجريبية بأمر واحد.
Migrations: سجل تاريخ قاعدة البيانات
Section titled “Migrations: سجل تاريخ قاعدة البيانات”تخيّل دفتر الصيانة لسيارة: كل تعديل يُكتب في صفحة جديدة بتاريخه، ولا أحد يمسح الصفحات القديمة. ملفات Migrations كذلك: كل ملف تغيير واحد، وكلها معاً تحكي قصة قاعدة البيانات من أول يوم.
لذلك القاعدة الذهبية: لا تعدّل ملف Migration قديماً نُفّذ من قبل، خصوصاً إذا كان المشروع منشوراً أو معك فريق. إذا أردت تغييراً، فأنشئ ملفاً جديداً.
إضافة أعمدة لجدول موجود
Section titled “إضافة أعمدة لجدول موجود”نريد أن نضيف لجدول posts عمودين: عدد المشاهدات، وصورة الغلاف. ننشئ ملفاً جديداً باسم يصف التغيير:
php artisan make:migration add_views_to_posts_tableINFO 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. املأ الدالتين:
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 migrateINFO 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:statusMigration name .................................. Batch / Status0001_01_01_000000_create_users_table .................. [1] Ran0001_01_01_000001_create_cache_table .................. [1] Ran0001_01_01_000002_create_jobs_table ................... [1] Ran2026_11_10_230319_create_posts_table .................. [2] Ran2026_11_11_174458_add_views_to_posts_table ............ [3] Ranالرقم بين القوسين هو الدفعة (Batch): كل مرة تشغّل فيها migrate تُحسب دفعة. وإذا اكتشفت خطأ في آخر ما نفّذته، تتراجع عن الدفعة الأخيرة:
php artisan migrate:rollbackINFO 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<?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 الرئيسي:
public function run(): void{ $this->call([ PostSeeder::class, ]);}والآن أمر واحد يبني القاعدة من الصفر ويملؤها:
php artisan migrate:fresh --seedFactories: عشرات السجلات في سطر
Section titled “Factories: عشرات السجلات في سطر”ماذا لو أردت خمسين مقالاً لتجرّب تقسيم الصفحات؟ الـ Factory قالب يصنع سجلات ببيانات عشوائية:
php artisan make:factory PostFactorypublic function definition(): array{ return [ 'title' => fake()->sentence(4), 'body' => fake()->paragraph(), 'published' => fake()->boolean(70), ];}الدالة fake() تولّد بيانات وهمية: جملة من أربع كلمات، وفقرة، وقيمة منطقية تكون true في 70% من الحالات. ولكي يتعرف الـ Model على المصنع، أضف له HasFactory:
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. (نص لاتيني وهمي يستخدمه المصممون لملء الفراغ).
جرّب بنفسك
Section titled “جرّب بنفسك”- أنشئ Migration يضيف لجدول
postsعموداً اسمهslugمن نوعstringوخاصيتهuniqueوnullable. - نفّذه، ثم تأكد بـ
migrate:status، ثم تراجع عنه بـrollback، ثم نفّذه مرة أخرى. - عدّل
PostFactoryليملأ العمود الجديد بـfake()->unique()->slug(). - شغّل
migrate:fresh --seedوافتح صفحة/postsلترى المقالات.
أخطاء شائعة
Section titled “أخطاء شائعة”- تعديل ملف Migration قديم: لن يُنفَّذ مرة ثانية، لأنه مسجّل كمنفَّذ. أنشئ ملفاً جديداً.
- دالة
downفارغة: عندها لا يعيدrollbackشيئاً إلى أصله. اكتب دائماً عكس ما فيup. - عمود إجباري بلا قيمة افتراضية في جدول فيه بيانات: السجلات القديمة لا تعرف ماذا تضع فيه، فيفشل التنفيذ. أضف
defaultأوnullable. - نسيان
HasFactory: استدعاءPost::factory()بدونه يعطي خطأ بأن التابع غير موجود.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
ماذا يفعل الأمر php artisan migrate:rollback؟
rollback يعكس آخر دفعة فقط عبر down. أما حذف كل شيء فهو migrate:fresh.
سؤال
تريد إضافة عمود لجدول موجود في مشروع منشور. ما التصرف الصحيح؟
الملفات المنفَّذة لا تُعدَّل. كل تغيير له ملف جديد، فيبقى التاريخ كاملاً ويحصل كل الفريق على التغيير نفسه.
الخلاصة
Section titled “الخلاصة”- كل تغيير في قاعدة البيانات له ملف Migration جديد، ولا نعدّل الملفات المنفَّذة.
Schema::tableتعدّل جدولاً موجوداً، وupوdownمتعاكستان.migrate:statusللمتابعة، وrollbackللتراجع، وfreshللبناء من الصفر.- الـ Seeders تملأ الجداول ببيانات تجريبية، وتُشغَّل بـ
--seed. - الـ Factories تصنع أي عدد من السجلات الوهمية بـ
fake().