تُعتبر Laravel Migrations من أهم الأدوات التي يوفرها إطار Laravel لإدارة بنية قاعدة البيانات بطريقة منظمة وقابلة للتتبع.
يمكن النظر إلى Migration على أنها نوع من:
Version Control for Database Schemaفبدل أن يقوم كل مبرمج بإنشاء الجداول أو تعديل الأعمدة يدويًا، يتم تسجيل جميع التغييرات داخل ملفات Migration يمكن مشاركتها وتشغيلها والتراجع عنها بسهولة.
في هذا المقال سنستعرض مجموعة من أهم النصائح والأوامر المتعلقة بـLaravel Migration، بداية من إنشاء Foreign Keys وحتى Rollback وSoft Deletes وTimestamp وMigration Stubs وبعض الأدوات المفيدة في المشاريع الحقيقية.
1. إنشاء Foreign Key باستخدام foreignId()
في الإصدارات القديمة من Laravel كان من الشائع إنشاء Foreign Key على مرحلتين.
على سبيل المثال، إذا كان لدينا جدول:
booksوجدول:
reviewsوكان كل Review مرتبطًا بكتاب، فقد كنا نكتب:
$table->unsignedBigInteger('book_id');
$table->foreign('book_id')
->references('id')
->on('books');أي أننا نقوم أولًا بإنشاء العمود:
book_idثم نقوم بإنشاء Foreign Key له.
الطريقة المختصرة
Laravel يوفر صيغة مختصرة وأكثر وضوحًا:
$table->foreignId('book_id')
->constrained();يقوم:
foreignId('book_id')بإنشاء عمود من النوع المناسب لـID التقليدي في Laravel، ثم تقوم:
constrained()باستخدام Laravel naming conventions لمعرفة الجدول الذي يجب الربط به.
فمن:
book_idيستنتج Laravel عادة:
books.idالشكل النهائي
books
+----+----------------+
| id | title |
+----+----------------+
| 1 | Laravel Guide |
+----+----------------+
reviews
+----+---------+-------------+
| id | book_id | review |
+----+---------+-------------+
| 1 | 1 | Excellent |
| 2 | 1 | Very Good |
+----+---------+-------------+
reviews.book_id
│
└───────────────→ books.id2. استخدام $table->id()
بدل كتابة:
$table->bigIncrements('id');يمكن استخدام:
$table->id();وهي الطريقة المختصرة والأكثر شيوعًا في Laravel الحديث.
مثال:
Schema::create('books', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->timestamps();
});3. استخدام foreignIdFor()
يوفر Laravel أيضًا طريقة أخرى مفيدة عند التعامل مع Models وهي:
foreignIdFor()مثال:
use App\Models\Book;
$table->foreignIdFor(Book::class)
->constrained();بدل كتابة:
$table->foreignId('book_id')
->constrained();يمكن لهذه الطريقة أن تكون مفيدة لأنها تعتمد على الـModel نفسه.
إذا كان المشروع يستخدم UUID أو ULID كمفتاح أساسي، فراجع نوع المفتاح المستخدم قبل الاعتماد علىforeignId()التقليدي. Laravel يوفر كذلك أدوات مثلforeignUuid()وforeignUlid().
4. ترتيب nullable() مع constrained()
إذا أردنا أن يكون Foreign Key اختياريًا ويسمح بالقيمة:
NULLيجب وضع:
nullable()قبل:
constrained()الصحيح
$table->foreignId('book_id')
->nullable()
->constrained();لا تعتمد على هذا الترتيب
$table->foreignId('book_id')
->constrained()
->nullable();Laravel ينص على أن Column Modifiers الإضافية مثلnullable()يجب استدعاؤها قبلconstrained().
5. ماذا يحدث عند حذف السجل الأب؟
لنفترض أن لدينا:
Book
│
├── Review 1
├── Review 2
└── Review 3وقاعدة البيانات تحتوي:
reviews.book_id → books.idإذا حاولنا حذف Book بينما توجد Reviews مرتبطة به، فإن سلوك قاعدة البيانات يعتمد على قاعدة البيانات المستخدمة وعلى الـForeign Key Action المحددة.
في MySQL مثلًا قد تحصل على خطأ مشابه:
Cannot delete or update a parent row:
a foreign key constraint failsوهذا لأن قاعدة البيانات تحمي Referential Integrity ولا تريد ترك:
reviews.book_idيشير إلى Book لم يعد موجودًا.
6. التحكم في onDelete
يمكن تحديد ما الذي يجب أن يحدث للـChild Records عندما يتم حذف Parent.
Laravel يوفر عدة خيارات.
CASCADE
عند حذف Book يتم حذف Reviews التابعة له تلقائيًا.
$table->foreignId('book_id')
->constrained()
->onDelete('cascade');والصيغة الأكثر وضوحًا:
$table->foreignId('book_id')
->constrained()
->cascadeOnDelete();النتيجة
DELETE Book
Book
│
├── Review 1 ── Delete
├── Review 2 ── Delete
└── Review 3 ── DeleteRESTRICT
يتم منع حذف Parent إذا كان يحتوي على Child Records مرتبطة به.
$table->foreignId('book_id')
->constrained()
->restrictOnDelete();أي:
Book has Reviews
│
▼
Delete Book
│
▼
RejectedSET NULL
بدل حذف Reviews، يتم تحويل:
book_idإلى:
NULLمثال:
$table->foreignId('book_id')
->nullable()
->constrained()
->nullOnDelete();لاحظ أهمية:
nullable()لأن قاعدة البيانات تحتاج أن يكون العمود قادرًا على استقبال:
NULLقبل حذف Book
review.id = 1
review.book_id = 5بعد حذف Book رقم 5
review.id = 1
review.book_id = NULLNO ACTION
يوفر Laravel:
noActionOnDelete()مثال:
$table->foreignId('book_id')
->constrained()
->noActionOnDelete();السلوك الدقيق لـNO ACTION قد يختلف حسب نظام قاعدة البيانات المستخدم، لذلك لا يُفضل التعامل معه باعتباره مطابقًا دائمًا لـRESTRICT في جميع قواعد البيانات.
7. التحكم في onUpdate
يمكن تطبيق القواعد نفسها تقريبًا عند تحديث المفتاح المشار إليه.
مثال:
$table->foreignId('book_id')
->constrained()
->cascadeOnUpdate();Laravel يوفر:
cascadeOnUpdate()
restrictOnUpdate()
nullOnUpdate()
noActionOnUpdate()8. مثال كامل على Foreign Key
Schema::create('reviews', function (Blueprint $table) {
$table->id();
$table->foreignId('book_id')
->constrained()
->cascadeOnDelete();
$table->text('review');
$table->timestamps();
});9. تحديد اسم جدول مختلف مع constrained()
Laravel يعتمد على Naming Conventions.
لكن إذا كان اسم الجدول لا يتبع تلك القواعد، يمكن تحديده يدويًا.
$table->foreignId('book_id')
->constrained('library_books');في Laravel الحديث يمكن أيضًا استخدام Named Arguments:
$table->foreignId('book_id')
->constrained(
table: 'library_books'
);10. معرفة Migrations التي تم تنفيذها
لمعرفة حالة جميع ملفات Migration نستخدم:
php artisan migrate:statusسيعرض Laravel قائمة بالـMigrations وحالتها.
يمكننا من خلالها معرفة:
- أي Migrations تم تشغيلها.
- أي Migrations ما زالت Pending.
- الـBatch التي تنتمي إليها Migration.
وهذا الأمر مفيد جدًا عندما تحصل على مشروع من Git وتريد معرفة:
هل جميع التغييرات الموجودة داخل database/migrations تم تطبيقها بالفعل على قاعدة البيانات؟
11. مشاهدة SQL قبل تشغيل Migration
من الأدوات المفيدة جدًا، خصوصًا قبل تنفيذ تغييرات حساسة:
php artisan migrate --pretendيتيح لك هذا الأمر مشاهدة SQL الذي ستقوم Migration بتنفيذه دون تطبيق التغييرات فعليًا.
هذه أداة مفيدة عند مراجعة Migrations قبل تشغيلها على قواعد البيانات المهمة.
12. استخدام useCurrent()
إذا أردنا إنشاء Timestamp يأخذ الوقت الحالي تلقائيًا كقيمة افتراضية:
$table->timestamp('reviewed_at')
->useCurrent();وهذا يعادل مفهوم:
DEFAULT CURRENT_TIMESTAMPتقريبًا على قواعد البيانات التي تدعمه.
13. استخدام useCurrentOnUpdate()
إذا أردنا تحديث Timestamp إلى الوقت الحالي كلما تم تعديل السجل:
$table->timestamp('reviewed_at')
->useCurrentOnUpdate();يمكن أيضًا الجمع بينهما:
$table->timestamp('reviewed_at')
->useCurrent()
->useCurrentOnUpdate();دعم useCurrentOnUpdate() يعتمد على Database Driver؛ Laravel يوثقه خصوصًا لـMySQL وMariaDB.14. إضافة Soft Deletes إلى Migration
إذا كان Model يستخدم:
SoftDeletesفيجب أن يحتوي الجدول عادة على:
deleted_atويمكن إنشاؤه من Migration بواسطة:
$table->softDeletes();مثال:
Schema::create('cars', function (Blueprint $table) {
$table->id();
$table->string('model');
$table->timestamps();
$table->softDeletes();
});سيضيف Laravel عمودًا مشابهًا لـ:
deleted_at TIMESTAMP NULL15. تعديل Migration Stub الافتراضي
إذا كنت تضيف نفس الحقول في كل Migration جديدة، يمكنك تخصيص الـStubs التي يستخدمها Artisan لإنشاء الملفات.
نفذ:
php artisan stub:publishسيقوم Laravel بنشر مجموعة من Stub Files داخل مجلد:
stubs/في جذر المشروع.
المجلد في Laravel الحديث يكون عادةstubsفي Root المشروع، وليسapp/stubs.
بعد ذلك يمكنك تعديل Migration Stub المناسب.
على سبيل المثال:
Schema::create('{{ table }}', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->softDeletes();
});بعد ذلك، عندما تقوم بإنشاء Migration جديدة باستخدام:
php artisan make:migration create_cars_tableيمكن أن تحصل على:
Schema::create('cars', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->softDeletes();
});لاحظ أن الأمر الصحيح هوmake:migrationوليسmake:migrate.
16. متى يكون تعديل Stub فكرة جيدة؟
يمكن أن يكون مفيدًا إذا كان المشروع يمتلك Convention ثابتة، مثل أن جميع الجداول تقريبًا تحتوي:
$table->id();
$table->timestamps();
$table->softDeletes();أو حقول خاصة بالشركة.
لكن لا تضف حقولًا لكل Migration فقط لأنها مستخدمة في جدولين أو ثلاثة.
الـStub يجب أن يعكس قاعدة عامة داخل المشروع، وليس استثناءً.
17. حذف عمود من جدول
لنفترض أن جدول:
carsيحتوي على:
model
numberلحذف عمود:
Schema::table('cars', function (Blueprint $table) {
$table->dropColumn('model');
});18. حذف عدة أعمدة في سطر واحد
Schema::table('cars', function (Blueprint $table) {
$table->dropColumn([
'model',
'number'
]);
});ملاحظة مهمة
عند تعديل جدول موجود نستخدم:
Schema::table()وليس:
Schema::create()لأن:
Schema::create()مخصص لإنشاء جدول جديد.
19. الطريقة الصحيحة لاستخدام down()
وظيفة:
down()هي عكس ما قامت به:
up()إذا كان up() يضيف أعمدة
public function up(): void
{
Schema::table('cars', function (Blueprint $table) {
$table->string('model');
$table->string('number');
});
}فيجب أن يحذفها down()
public function down(): void
{
Schema::table('cars', function (Blueprint $table) {
$table->dropColumn([
'model',
'number'
]);
});
}20. إذا كانت Migration تنشئ جدولًا كاملًا
يجب أن يكون:
public function up(): void
{
Schema::create('cars', function (Blueprint $table) {
$table->id();
$table->string('model');
$table->timestamps();
});
}وعكسها:
public function down(): void
{
Schema::dropIfExists('cars');
}قاعدة مهمة جدًا: اقرأup()واسأل نفسك "كيف أعيد قاعدة البيانات إلى حالتها قبل هذا التغيير؟" هذا هو ما يجب أن يفعلهdown().
21. التراجع عن Migrations باستخدام rollback
يمكن التراجع عن آخر Migration Batch باستخدام:
php artisan migrate:rollbackمن المهم فهم أن هذا يعني:
Last Batchوليس بالضرورة:
Last Migration Fileإذا تم تشغيل عدة Migrations في نفس عملية:
php artisan migrateفقد تكون موجودة داخل نفس الـBatch.
22. التراجع باستخدام --step
إذا أردنا التراجع عن عدد محدد من Migrations:
php artisan migrate:rollback --step=3وهذا يطلب من Laravel التراجع عن آخر ثلاث Migrations وفق سجل الـMigration.
23. استخدام migrate --step
يمكن كذلك تشغيل:
php artisan migrate --stepبحيث يتم وضع كل Migration في Batch مستقلة، ما قد يجعل عمليات Rollback الفردية أكثر مرونة لاحقًا.
24. migrate:refresh
الأمر:
php artisan migrate:refreshيقوم بالتراجع عن Migrations ثم تشغيلها من جديد.
ويمكن:
php artisan migrate:refresh --step=3للتراجع وإعادة تشغيل عدد محدد من أحدث Migrations.
25. migrate:reset
للتراجع عن جميع Migrations:
php artisan migrate:reset26. migrate:fresh
يوجد فرق مهم بين:
migrate:refreshو:
migrate:freshالأمر:
php artisan migrate:freshيقوم بحذف جميع الجداول ثم تشغيل Migrations من جديد.
ويمكن تشغيل Seeders:
php artisan migrate:fresh --seedاستخدم migrate:fresh بحذر شديد. هذا الأمر يحذف الجداول والبيانات، ولا ينبغي تشغيله على Production دون فهم كامل لما سيحدث.27. تشغيل Migration في Production
عند تشغيل Migrations في بيئة Production قد يطلب Laravel تأكيدًا قبل تنفيذ عمليات حساسة.
في عمليات Deployment الآلية يمكن استخدام:
php artisan migrate --forceوجود --force لا يعني أن Migration آمنة. يجب مراجعة التغييرات والنسخ الاحتياطي وخطة Rollback قبل تنفيذ التعديلات الحساسة على Production.28. تحديد بداية Auto Increment
عادة يبدأ:
$table->id();من القيمة:
1إذا أردنا أن يبدأ Auto Increment من رقم معين مثل:
1000يمكن في قواعد البيانات المدعومة استخدام:
$table->id()
->from(1000);مثال:
Schema::create('questions', function (Blueprint $table) {
$table->id()
->from(1000);
$table->string('question');
$table->timestamps();
});دعم from() مرتبط بنظام قاعدة البيانات؛ Laravel يوثقه لـMariaDB وMySQL وPostgreSQL.29. إنشاء Migration باسم يحتوي على مسافات
عادة نستخدم:
php artisan make:migration add_phone_to_users_tableلكن يمكن أيضًا تمرير الاسم بين علامات اقتباس:
php artisan make:migration "add phone to users table"ومع ذلك فإن استخدام:
snake_caseمثل:
add_phone_to_users_tableيبقى أوضح وأكثر شيوعًا داخل مشاريع Laravel.
30. إنشاء Migration مع تحديد الجدول
عند إضافة حقل إلى جدول موجود يمكن جعل الأمر أكثر وضوحًا:
php artisan make:migration add_phone_to_users_table --table=usersأما لإنشاء جدول جديد:
php artisan make:migration create_books_table --create=books31. Schema Dump للمشاريع التي تحتوي على مئات Migrations
مع مرور السنوات قد يحتوي المشروع على مئات ملفات Migration.
Laravel يوفر:
php artisan schema:dumpليقوم بإنشاء Schema File يمثل الحالة الحالية لقاعدة البيانات.
ويمكن استخدام:
php artisan schema:dump --pruneلإنشاء Schema Dump والتخلص من ملفات Migration القديمة وفق آلية Laravel.
هذه الميزة مفيدة خصوصًا للمشاريع الكبيرة التي أصبح تشغيل جميع Migrations التاريخية فيها بطيئًا.
32. حذف Foreign Key
إذا أردت حذف Foreign Key:
$table->dropForeign(
'reviews_book_id_foreign'
);أو باستخدام اسم العمود:
$table->dropForeign([
'book_id'
]);33. حذف Foreign Key والعمود
إذا كنت تريد إزالة العلاقة بالكامل:
Schema::table('reviews', function (Blueprint $table) {
$table->dropForeign([
'book_id'
]);
$table->dropColumn(
'book_id'
);
});وفي بعض الحالات يمكن استخدام Helpers المتاحة حسب نوع العمود وإصدار Laravel.
34. تعطيل Foreign Key Constraints مؤقتًا
Laravel يوفر:
Schema::disableForeignKeyConstraints();ولإعادة تشغيلها:
Schema::enableForeignKeyConstraints();كما يمكن حصر التعطيل داخل Closure:
Schema::withoutForeignKeyConstraints(
function () {
// operations
}
);تعطيل Foreign Keys ليس حلًا لمشاكل التصميم. استخدمه فقط عندما يكون لديك سبب واضح ومحدد.
35. الفرق بين Foreign Key وIndex
من الأخطاء الشائعة الاعتقاد أن:
Foreign Keyو:
Indexهما الشيء نفسه.
الـForeign Key وظيفته الأساسية الحفاظ على:
Referential Integrityبينما Index وظيفته الأساسية تحسين الوصول إلى البيانات والاستعلامات.
مثال Index:
$table->index('status');Unique Index:
$table->unique('email');36. Composite Index
إذا كانت الاستعلامات تستخدم أكثر من حقل معًا باستمرار:
WHERE user_id = ?
AND status = ?قد يكون من المناسب -بعد دراسة الاستعلامات- إنشاء Composite Index:
$table->index([
'user_id',
'status'
]);لا تقم بإنشاء Index لكل عمود بشكل عشوائي. الـIndexes تحسن بعض عمليات القراءة لكنها أيضًا تستهلك مساحة وتزيد تكلفة عمليات الكتابة والتحديث.
37. Naming Convention مهم جدًا
Laravel يستطيع كتابة الكثير من الإعدادات نيابةً عنك إذا التزمت بتسمية واضحة.
على سبيل المثال:
Model:
Book
Table:
books
Primary Key:
id
Foreign Key:
book_idيسمح لك ذلك بكتابة:
$table->foreignId('book_id')
->constrained();دون الحاجة إلى تحديد:
references('id')
on('books')38. مثال Migration احترافية كاملة
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create(
'reviews',
function (Blueprint $table) {
$table->id();
$table
->foreignId('book_id')
->constrained()
->cascadeOnDelete();
$table
->unsignedTinyInteger('rating');
$table
->text('review')
->nullable();
$table
->timestamp('reviewed_at')
->useCurrent();
$table->timestamps();
$table->softDeletes();
}
);
}
public function down(): void
{
Schema::dropIfExists('reviews');
}
};39. أهم أوامر Laravel Migration
إنشاء Migration
php artisan make:migration create_books_tableتشغيل Migrations
php artisan migrateمعرفة الحالة
php artisan migrate:statusمشاهدة SQL دون التنفيذ
php artisan migrate --pretendالتراجع عن آخر Batch
php artisan migrate:rollbackالتراجع عن عدد محدد
php artisan migrate:rollback --step=3التراجع عن جميع Migrations
php artisan migrate:resetRollback ثم Migrate
php artisan migrate:refreshحذف جميع الجداول ثم Migrate
php artisan migrate:freshحذف الجداول وتشغيل Seeders
php artisan migrate:fresh --seedProduction
php artisan migrate --forceنشر Stubs
php artisan stub:publishإنشاء Schema Dump
php artisan schema:dump40. نصائح مهمة قبل تشغيل Migration على Production
- راجع SQL المتوقع قبل التغييرات الكبيرة.
- خذ نسخة احتياطية من قاعدة البيانات.
- انتبه عند حذف Columns أو Tables.
- لا تعتمد على Rollback باعتباره Backup.
- اختبر Migration على Staging أولًا.
- انتبه إلى Locks عند تعديل الجداول الكبيرة.
- لا تستخدم
migrate:freshعلى Production. - اجعل
down()منطقيًا وقادرًا على عكس التغيير متى كان ذلك ممكنًا.
الخلاصة
Laravel Migrations ليست مجرد وسيلة لإنشاء الجداول، وإنما نظام كامل لإدارة تطور بنية قاعدة البيانات مع المشروع.
بدل كتابة Foreign Key بالطريقة المطولة:
$table->unsignedBigInteger('book_id');
$table->foreign('book_id')
->references('id')
->on('books');يمكن غالبًا استخدام:
$table->foreignId('book_id')
->constrained();ويمكن التحكم بسلوك الحذف:
cascadeOnDelete()
restrictOnDelete()
nullOnDelete()
noActionOnDelete()كما يجب تذكر أن:
nullable()يوضع قبل:
constrained()ويمكن إدارة Migration History باستخدام:
migrate:status
migrate:rollback
migrate:refresh
migrate:freshبالإضافة إلى أدوات أكثر تقدمًا مثل:
migrate --pretend
migrate --step
schema:dump
stub:publishالهدف من Migration ليس فقط أن تعمل قاعدة البيانات على جهازك، بل أن يستطيع أي مبرمج أو Server إعادة بناء نفس Database Schema بصورة موثوقة ومتوقعة من خلال الكود الموجود في المشروع.
مقال رائع عاش نضال الشعب الفلسطيني
تسلم عزيزي، مرورك الأروع، وإن شاء الله سنصلي بالأقصى سويا
ربنا يعطيك العافية
تسلم عزيزي، مرورك الأروع
عاشت الايادي
تسلم عزيزي، مرورك الأروع
هل يمكن عمل rollback لملف معين ف migrations ؟
نعم، لكن Laravel ما فيه أمر مباشر يعمل
rollbackلملف Migration معيّن بالاسم.الـ
rollbackيعتمد بشكل أساسي على الـbatchesأو عدد الـsteps.إذا بدك ترجع Migration معيّنة، ممكن تستخدم
--pathمع تحديد مسار الملف:معلوماتك قيمة ومفيدة جدا .. يعطيك ألف عافية ?