البرمجة مبتدئ 9 دقيقة

كيفية ترحيل وبذر البيانات بأمان في Laravel

الـ migrations والـ seeders تحل مشكلتين مختلفتين. الـ migrations تدير مخطط قاعدة البيانات — الجداول والأعمدة والفهارس والمفاتيح الخارجية. هي تغييرات تحت التحكم في الإصدار يمكن تطبيقها والتراجع عنها. الـ seeders تدير البيانات — السجلات الافتراضية، بيانات الاختبار، جداول البحث. الخلط بينهما خطأ شائع يسبب صداعاً في الإنتاج.

هذا الدليل يغطي سير العمل الكامل: كتابة migration آمن، معاينة SQL الذي سينفذه، كتابة seeders ذات مفعول متكرر (idempotent)، ونشر كليهما في الإنتاج دون مفاجآت.

الخطوات

  1. 1

    فهم الفرق بين Migrations وSeeders

    القاعدة بسيطة: إذا كان يغير شكل قاعدة البيانات (المخطط)، فهو migration. وإذا كان يغير محتويات قاعدة البيانات (الصفوف)، فهو seeder. الـ migration الذي يدرج صفوفاً هو نمط خاطئ — يربط تغييرات المخطط ببيانات محددة، مما يجعل التراجع مؤلماً وبيئات الاختبار غير متسقة.

  2. 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. 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. 4

    معاينة SQL قبل التشغيل

    استخدم --pretend لرؤية SQL الذي سينفذ بالضبط دون لمس قاعدة البيانات. هذا لا يقدر بثمن قبل تشغيل migration في الإنتاج — تحقق من عدم وجود جمل DROP لم تتوقعها، ومن صحة صياغة SQL لإصدار قاعدة بياناتك.

    bash
    php 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. 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. 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. 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. 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 قبل الذهاب إلى الإنتاج.

#Laravel #Migrations #Seeders
العودة إلى جميع الأدلة

هل تحتاج مساعدة في مشروعك؟

احجز استشارة مجانية لمدة 30 دقيقة لمناقشة تحدياتك التقنية واستكشاف الحلول معًا.