الخطوات
-
1
فهم الفرق بين Migrations وSeeders
القاعدة بسيطة: إذا كان يغير شكل قاعدة البيانات (المخطط)، فهو migration. وإذا كان يغير محتويات قاعدة البيانات (الصفوف)، فهو seeder. الـ migration الذي يدرج صفوفاً هو نمط خاطئ — يربط تغييرات المخطط ببيانات محددة، مما يجعل التراجع مؤلماً وبيئات الاختبار غير متسقة.
-
2
توليد Migration
استخدم
make:migrationباسم وصفي يشرح ما يتغير. Laravel يستنتج اسم الجدول من الاسم إذا اتبعت الاصطلاح. لإضافة أعمدة إلى جدول موجود استخدم نمطadd_..._to_..._table.bash# New table php artisan make:migration create_orders_table # Add column to existing table php artisan make:migration add_status_to_orders_table # The file is created in database/migrations/ with a timestamp prefix -
3
كتابة طريقتي up() وdown()
طريقة
up()تطبق التغيير. طريقةdown()تعكسه بالضبط. Migration بدونdown()صحيح هو فخ — يمنعmigrate:rollbackفي التطوير ويجعل إعادة تعيين البيئة المحلية مؤلمة. كل تغيير فيup()يجب أن يقابله عكس مناظر فيdown().php<?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::table('orders', function (Blueprint $table) { $table->string('status', 20)->default('pending')->after('total'); $table->timestamp('shipped_at')->nullable()->after('status'); $table->index('status'); // Add index for frequent WHERE clauses }); } public function down(): void { Schema::table('orders', function (Blueprint $table) { $table->dropIndex(['status']); $table->dropColumn(['status', 'shipped_at']); }); } }; -
4
معاينة SQL قبل التشغيل
استخدم
--pretendلرؤية SQL الذي سينفذ بالضبط دون لمس قاعدة البيانات. هذا لا يقدر بثمن قبل تشغيل migration في الإنتاج — تحقق من عدم وجود جمل DROP لم تتوقعها، ومن صحة صياغة SQL لإصدار قاعدة بياناتك.bashphp artisan migrate --pretend # Sample output: # CreateOrdersTable: create table `orders` (`id` bigint unsigned not null auto_increment primary key ...) # AddStatusToOrdersTable: alter table `orders` add `status` varchar(20) not null default 'pending' -
5
تشغيل الـ Migrations
شغّل الـ migrations المعلقة بـ
php artisan migrate. Laravel يتتبع الـ migrations التي نُفذت في جدولmigrations— سيشغّل فقط الملفات غير المسجلة فيه. استخدمmigrate:statusلرؤية ما نُفذ وما لم يُنفذ.bash# Run all pending migrations php artisan migrate # Check migration status php artisan migrate:status # Rollback the last batch php artisan migrate:rollback # Rollback and re-run all (development only — destroys data) php artisan migrate:fresh -
6
كتابة Seeder ذي مفعول متكرر (Idempotent)
يجب أن يكون الـ seeder آمناً للتشغيل عدة مرات. إذا شغّلته مرتين وأدخل صفوفاً مكررة، فلديك seeder معطوب. استخدم
updateOrCreate()أوfirstOrCreate()حتى يُدرج الـ seeder إما سجلاً جديداً أو يحدّث الموجود — نفس النتيجة في كل مرة.php<?php namespace Database\Seeders; use App\Models\Product; use Illuminate\Database\Seeder; class ProductsSeeder extends Seeder { public function run(): void { $products = [ ['sku' => 'PROD-001', 'name' => 'Widget A', 'price' => 9.99], ['sku' => 'PROD-002', 'name' => 'Widget B', 'price' => 14.99], ]; foreach ($products as $data) { Product::updateOrCreate( ['sku' => $data['sku']], // Find by this key $data // Update or insert with these values ); } } } -
7
تسجيل الـ Seeders وتشغيلها
استدعِ الـ seeders الفردية من
DatabaseSeederلتحديد ترتيب الاعتمادية. شغّل seeder واحدة بمعزل عن البقية بـ--class— مفيد لبذر جدول واحد دون لمس البقية.php<?php namespace Database\Seeders; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { public function run(): void { // Order matters — seed parents before children $this->call([ CategorySeeder::class, ProductsSeeder::class, UserSeeder::class, ]); } } // Run from command line: // php artisan db:seed — runs DatabaseSeeder // php artisan db:seed --class=ProductsSeeder — runs one seeder -
8
النشر الآمن إلى الإنتاج
في الإنتاج استخدم دائماً
--force— بدونه يرفض Artisan التشغيل في بيئة غير تفاعلية. لا تشغّل أبداًmigrate:freshأوmigrate:resetفي الإنتاج؛ فهما يمحوان كل البيانات. احتفظ بنسخة احتياطية من قاعدة البيانات قبل أي migration يحذف أعمدة أو جداول.bash# Standard production deploy php artisan migrate --force # Run a specific seeder on production php artisan db:seed --class=ProductsSeeder --force # What to NEVER do on production: # php artisan migrate:fresh --seed # Drops all tables # php artisan migrate:reset # Rolls back everything
نصائح ومحاذير
- سمّ الـ migrations بعد <em>التغيير</em> لا التاريخ: <code>add_status_to_orders_table</code> يخبرك ما يفعله؛ <code>2024_01_15_142310</code> لا يخبرك بشيء الساعة 3 صباحاً أثناء حادثة.
- إذا احتجت لإدراج بيانات مرجعية (دول، عملات، أسماء أذونات)، الـ seeders مناسبة — فقط اجعلها idempotent بـ <code>updateOrCreate()</code>.
- لا تعدّل migration نُفذ بالفعل في الإنتاج. أنشئ migration جديداً بدلاً من ذلك. تعديل migration منفّذ يكسر الـ checksum ويربك <code>migrate:status</code>.
- استخدم <code>$table->after('column_name')</code> في MySQL لوضع الأعمدة الجديدة في موضع منطقي — ليس ضرورياً، لكنه يجعل المخطط أسهل قراءةً.
- قبل حذف عمود في الإنتاج، انشر أولاً migration يجعله nullable (حتى يستمر الكود القديم في العمل)، ثم في نشر ثانٍ أزل العمود والإشارات إليه في الكود.
خاتمة
الـ migrations النظيفة والـ seeders ذات المفعول المتكرر هي أساس قاعدة بيانات يمكنك التفكير فيها بثقة. اتبع الفصل بين المخطط والبيانات، اكتب دائماً down()، واختبر بـ --pretend قبل الذهاب إلى الإنتاج.