پرش به مطلب اصلی

راهنمای مهاجرت

اصول اساسی

مهاجرت‌ها بخش مهمی از مدیریت پایگاه داده در برنامه‌های Laravel هستند. پیروی از این دستورالعمل‌ها تکامل روان پایگاه داده را تضمین می‌کند و از مشکلات رایج جلوگیری می‌کند.

مزیت کلیدی

مهاجرت‌های با ساختار خوب، تغییرات پایگاه داده شما را قابل اعتماد، برگشت‌پذیر و قابل نگهداری در محیط‌های مختلف می‌کند.

بهترین شیوه‌های مهاجرت

1. اصل مسئولیت واحد

important

هر مهاجرت باید فقط یک عملیات منطقی انجام دهد.

ایجاد مهاجرت جدول کاربران
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}

عملیات‌های رایج با مسئولیت واحد شامل:

  • ایجاد یک جدول جدید
  • افزودن یک ستون به جدول موجود
  • اصلاح داده‌های موجود
  • ایجاد شاخص‌ها

2. نام‌گذاری توصیفی

نام مهاجرت توصیفی
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamps();
});
}

3. همیشه متدهای Down را تعریف کنید

مهاجرت با متد down
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamps();
});
}

public function down()
{
Schema::dropIfExists('users');
}

4. انواع مختلف عملیات را جدا کنید

2025_04_12_000001_create_categories_table.php
public function up()
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->timestamps();
});
}

5. مدیریت مناسب وقفه‌ها

Laravel به طور خودکار وقفه‌ها را در مهاجرت‌ها تشخیص نمی‌دهد، که می‌تواند منجر به وضعیت‌های ناسازگار شود.

خطرات وقفه

اگر یک مهاجرت با وقفه مواجه شود، Laravel ممکن است آن را به عنوان تکمیل شده علامت‌گذاری کند، حتی اگر اجرای آن به پایان نرسیده باشد.

راه‌حل‌ها:

  • بهینه‌سازی عملیات‌های داده‌ای بزرگ
  • تقسیم مهاجرت‌های بزرگ به موارد کوچکتر
  • استفاده از پردازش تکه‌ای برای مجموعه داده‌های بزرگ
تکه‌بندی عملیات‌های داده بزرگ
public function up()
{
User::chunk(1000, function ($users) {
foreach ($users as $user) {
// پردازش هر کاربر
$user->update(['status' => 'active']);
}
});
}

6. تغییرناپذیری مهاجرت‌های اجرا شده

توجه

پس از اعمال یک مهاجرت به پایگاه داده تولید، هرگز نباید آن را تغییر داد.

به جای اصلاح یک مهاجرت موجود، یک مهاجرت جدید برای ایجاد تغییرات اضافی ایجاد کنید.

7. هاردکد کردن Enum ها در مهاجرت‌ها

important

همیشه مقادیر enum را به صورت هاردکد مستقیماً در مهاجرت‌ها بنویس، به جای اینکه به کلاس enum ارجاع بدی.

این کار تضمین می‌کند که مهاجرت‌ها پایدار و مستقل از تغییرات کد باقی بمانند. اگر در آینده یک کلاس enum تغییر کند یا حذف شود، مهاجرت همچنان به درستی کار می‌کند.

مقادیر enum هاردکد شده
public function up()
{
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->enum('status', ['pending', 'processing', 'completed', 'cancelled']);
$table->timestamps();
});
}

چرا این موضوع مهمه:

  • مهاجرت‌ها سوابق تاریخی هستند و باید تغییرناپذیر باشند
  • کلاس‌های enum ممکنه در طول زمان تغییر کنند و مهاجرت‌های قدیمی رو خراب کنند
  • مقادیر هاردکد شده تضمین می‌کنند که مهاجرت‌ها در هر زمانی قابل اجرا باشند

8. استراتژی‌های مدیریت کلید خارجی

رویکرد دو مرحله‌ای برای جداول موجود

اخطار

هرگز یک کلید خارجی جدید را به یک جدول موجود در همان مایگریشنی که ستون آن را ایجاد می‌کند، اضافه نکنید. همیشه آن را در دو مایگریشن مجزا اعمال کنید تا ایمنی در بازگشت (Rollback) حفظ شده و از تشدید قفل (Lock Escalation) جلوگیری شود.

این رویکرد شامل دو مایگریشن جداگانه است:

  1. مایگریشن ۱: افزودن ستون: ستون جدید (مانند customer_id) اضافه می‌شود اما به صورت nullable() تعریف می‌گردد.
  2. مایگریشن ۲: افزودن محدودیت: محدودیت کلید خارجی به ستون اعمال می‌شود.
2025_04_12_000003_add_customer_id_to_orders.php
public function up()
{
Schema::table('orders', function (Blueprint $table) {
$table->unsignedBigInteger('customer_id')->nullable()->after('id');
});
}

public function down()
{
Schema::table('orders', function (Blueprint $table) {
$table->dropColumn('customer_id');
});
}

این جداسازی به شما فرصت می‌دهد تا بین استقرارها داده‌ها را تکمیل کنید، زمان قطعی (Downtime) را به حداقل برسانید و اصل مسئولیت واحد را رعایت کنید.

استثنا: ایجاد جدول جدید

هنگام ایجاد یک جدول کاملاً جدید، تعریف کلیدهای خارجی در همان مایگریشن ایمن است. MySQL کل دستور CREATE TABLE، شامل محدودیت‌ها، را در یک تراکنش واحد پردازش می‌کند. اگر هر بخشی با شکست مواجه شود، کل عملیات بازگردانده (Rollback) می‌شود.

2025_05_21_000001_create_posts_table.php
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->string('title');
$table->text('body');
$table->timestamps();

// این کار ایمن است زیرا بخشی از ایجاد اولیه جدول است
$table->foreign('user_id')->references('id')->on('users');
});
}

قانون کلی برای تغییرات

برای هر سناریوی دیگری که شامل جداول موجود است، باید عملیات را به مایگریشن‌های اتمی و جداگانه تقسیم کنید. هر یک از تغییرات زیر نیازمند فایل مایگریشن اختصاصی خود است:

  • افزودن یک محدودیت کلید خارجی جدید.
  • تغییر نام ستونی که قرار است کلید خارجی داشته باشد.
  • تغییر نوع داده ستونی که قرار است کلید خارجی داشته باشد.
قانون کلی

اگر نیاز به انجام چندین تغییر در ساختار یک ستون دارید (مثلاً تغییر نام، سپس افزودن ایندکس و سپس افزودن کلید خارجی)، برای هر عمل یک مایگریشن جداگانه ایجاد کنید. این کار حداکثر ایمنی و قابلیت بازگشت را تضمین می‌کند.

9. پاکسازی داده‌ها قبل از اعمال محدودیت

گام حیاتی برای داده‌های زنده

قبل از افزودن یک محدودیت foreign key یا unique به یک ستون موجود که دارای داده است، باید ابتدا یک مایگریشن برای پاکسازی داده‌ها اجرا کنید. استقرار بدون این مرحله می‌تواند منجر به شکست‌های فاجعه‌بار در محیط تولید شود.

فرض کنید که ستون هدف حاوی داده‌های نامعتبر است. مایگریشن پاکسازی شما باید این مشکلات را قبل از اعمال هرگونه محدودیت شناسایی و حل کند.

  • برای کلیدهای خارجی: داده نامعتبر به معنای مقداری در ستون است که با هیچ شناسه‌ای در جدول مرجع مطابقت ندارد (مثلاً customer_id برابر 999 در حالی که هیچ مشتری با این شناسه وجود ندارد).
  • برای کلیدهای یکتا: داده نامعتبر به معنای مقادیر تکراری در ستونی است که قصد دارید آن را یکتا کنید.

این مسئله به ویژه در پروژه‌های متعددی که ساختار پایگاه داده مشابه اما داده‌های متفاوتی دارند، حیاتی است. یک مایگریشن ممکن است در محیط آزمایشی با داده‌های تمیز موفقیت‌آمیز باشد، اما در حین استقرار در تولید، جایی که داده‌های نامعتبر انباشته شده‌اند، با شکست مواجه شود.

در اینجا یک فرآیند ایمن و سه مرحله‌ای برای افزودن کلید خارجی به ستونی که از قبل داده دارد، آمده است:

2025_05_22_000001_clean_up_invalid_customer_ids_in_orders.php
public function up()
{
// سفارش‌هایی را پیدا کنید که customer_id آن‌ها در جدول customers وجود ندارد
// و آن‌ها را به null تغییر دهید. استراتژی دیگر می‌تواند حذف آن‌ها باشد.
DB::table('orders')
->whereNotNull('customer_id')
->whereNotExists(function ($query) {
$query->select(DB::raw(1))
->from('customers')
->whereColumn('customers.id', 'orders.customer_id');
})
->update(['customer_id' => null]);
}

public function down()
{
// این عملیات معمولاً غیرقابل بازگشت است، زیرا شناسه‌های نامعتبر اصلی را نمی‌دانیم.
// این موضوع را به وضوح مستند کنید.
}

الگوهای رایج مهاجرت

افزودن یک ستون

افزودن یک ستون به جدول موجود
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('phone_number')->nullable()->after('email');
});
}

public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('phone_number');
});
}

تغییر نام یک ستون

تغییر نام یک ستون
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->renameColumn('email', 'email_address');
});
}

public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->renameColumn('email_address', 'email');
});
}

ایجاد یک شاخص

افزودن یک شاخص
public function up()
{
Schema::table('posts', function (Blueprint $table) {
$table->index(['user_id', 'created_at']);
});
}

public function down()
{
Schema::table('posts', function (Blueprint $table) {
$table->dropIndex(['user_id', 'created_at']);
});
}

آزمایش مهاجرت‌ها

آزمایش مهاجرت‌ها
# بازنشانی پایگاه داده و اجرای تمام مهاجرت‌ها
php artisan migrate:fresh

# برگرداندن آخرین دسته مهاجرت‌ها
php artisan migrate:rollback

# برگرداندن تمام مهاجرت‌ها و اجرای مجدد آنها
php artisan migrate:refresh

نتیجه‌گیری

پیروی از این دستورالعمل‌های مهاجرت به حفظ ساختار پایگاه داده تمیز و قابل اعتماد در طول چرخه عمر برنامه شما کمک می‌کند. به یاد داشته باشید که مهاجرت‌ها به عنوان یک سابقه تاریخی از تکامل پایگاه داده شما عمل می‌کنند، بنابراین وضوح و دقت ضروری است.