بناء التطبيق

قاعدة بيانات التطبيق (Migrations)

ملفات الترحيل في مستودعك هي مصدر الحقيقة. لا يوجد مصمّم مخططات في البوّابة يرسم لك جدولاً — كان موجوداً وحُذف، لأنه لم يكن يكتب شيئاً في مستودعك، فصار الجدول المرسوم حقيقة ثالثة لا تطابق المستودع ولا القاعدة الحيّة. تكتب ملف Laravel migration عادياً، وتدفعه، وتستورده.

السلسلة كاملة

database/migrations/ في مستودعك
        │  «استيراد من المستودع» (أو import_migrations_from_repo)
        ▼
متجر الترحيلات في البوّابة  ──→ manifest.database.migrations[]
        │
        ▼  provision_database (sandbox) / زر البوّابة (production)
schema خاص بتطبيقك في PostgreSQL

كل حلقة لازمة. بلا الاستيراد يُصدِر البيان requiresDatabase:false وmigrations:[]، فترفض okta-web إنشاء القاعدة بحجّة «لا شيء لتجهيزه» — والاستيراد نفسه هو ما يقلب requires_database ويملأ database_schema على إصداراتك.

شكل الملف

database/migrations/2026_07_20_101500_create_exam_papers.php
                    └─────┬────────┘ └────────┬───────────┘
                       version                اسم الترحيل

الصيغة إلزامية: YYYY_MM_DD_HHMMSS_<name>.php. ما لا يطابقها يُتجاوَز بصمت.

version هو مفتاح كل شيء بعد ذلك: ترتيب التنفيذ، ومطابقة الاستيراد المتكرّر، وسجلّ ما نُفِّذ. تغيير الطابع الزمني لملف قائم = ترحيل جديد في نظر المنصّة، لا تعديل للقديم.

ملف الأساس 000000_create_<app>_database.php

يشحنه القالب باسم يبدأ بـ000000 عمداً: هذا الاسم لا يطابق الصيغة أعلاه، فلا يُستورَد أبداً. إنشاء القاعدة نفسها خطوة تقوم بها okta-web، وملف يفعلها مجدداً يصطدم بها.

لكن التجاوز بالاسم لا بالنيّة: إن أعدت تسميته إلى طابع زمني حقيقي (2026_07_26_100000_create_<app>_database.php) فهو ترحيل عادي في نظر المنصّة، ويُستورَد ويُشحَن — وغالباً بطابع زمني أقدم من ترحيلاتك الحقيقية، وهو ما يقود مباشرةً إلى الخطأ التالي.

«Out-of-order migration» — ولماذا يوقف كل شيء

Out-of-order migration "2026_07_26_100000" precedes already-applied
"2026_07_27_100001" on binding #21. Migrations are immutable once applied
— add a new corrective migration instead.

المُطبِّق يمرّ على الترحيلات بترتيب version، ويرفض تنفيذ ترحيل أقدم من أحدث ما نُفِّذ فعلاً على تلك القاعدة. السبب أن العكس كذبة: تشغيل ترحيل قديم بعد أحدث منه يعطيك مخططاً لم يمرّ به أي تثبيت آخر، ويجعل ترتيب التنفيذ مختلفاً بين قاعدتين تدّعيان النسخة نفسها.

والرفض يُسقِط التشغيل كلّه، لا ذلك الصفّ وحده. فترحيل واحد بطابع زمني خاطئ يجمّد كل ما بعده: تقرأ «مطبَّقة ٧، معلَّقة ١» ولا تتحرّك مهما ضغطت «مزامنة الآن».

كيف تخرج منه:

  1. إن كان الصفّ لا يجب أن يُشحَن أصلاً — ملف الأساس أعلاه هو الحالة النموذجية — احذفه من الجدول. لم يُنفَّذ على شيء، فالحذف نظيف تماماً، ويزول الحاجز فوراً.
  2. إن كان ترحيلاً حقيقياً وصل متأخراً بطابع زمني قديم: أعطه طابعاً زمنياً بعد آخر ما نُفِّذ (أي ملفاً جديداً في مستودعك)، ثم احذف الصفّ القديم وأعد الاستيراد.
  3. إن كان قد نُفِّذ فعلاً على قاعدة ما، فلا تحذفه ولا تعدّله: أضف ترحيلاً تصحيحياً جديداً. هذا هو معنى «immutable once applied» في نصّ الخطأ.

حالتان: draft و published

الحالة من أين قابل للتعديل يدخل الـ manifest
published الاستيراد من المستودع ✗ SQL مجمَّد ✓ في كل بيان لاحق
draft SQL مباشر تكتبه في البوّابة ✓ ✗ حتى يُنشَر

الاستيراد يكتب published مباشرةً — هذه ملفات إصدار فعلية لا مسودّات تُجرَّب. لهذا لا تعني «منشور» هنا أن أحداً قرّر الإصدار: ملف كان ناقصاً يوم دفعتَه يصل مجمَّداً.

تصحيح ترحيل بعد وصوله

الطريق الأول — ترحيل جديد. هذا هو الطبيعي: ALTER يصحّح ما فعله سابقه. SQL المنشور مجمَّد لسبب: قاعدة مدرسة نفّذته بالفعل، وإعادة كتابته تحت قدمها تجعل ما في القاعدة مختلفاً عمّا يقوله البيان.

الطريق الثاني — الحذف. متاح لكل صفّ، منشوراً كان أو مسودّة، من جدول قاعدة البيانات في البوّابة. لكن اعرف ما يفعله وما لا يفعله:

  • ✓ يزيله من كل بيان قادم → التثبيتات الجديدة لن تحصل عليه.
  • ✗ لا يتراجع عن شيء نُفِّذ. جدولٌ أُنشئ الأسبوع الماضي يبقى منشأً.
  • ✗ لا يمسّ البيانات المُرسَلة سابقاً إلى okta-web؛ الفرق يبدأ من النشرة التالية.
  • ✗ لا يمسّ سجلّ التنفيذ. ذلك السجلّ يقول ما نفّذته قاعدة حقيقية، وتعديله ليطابق قراراً اتُّخذ بعده يفقد المنصّة قدرتها على معرفة ما في تلك القاعدة.

فإن كان الترحيل قد نُفِّذ على قواعد قائمة، الحذف يُنشئ اختلافاً دائماً بينها وبين أي تثبيت جديد. البوّابة تعرض على الصفّ شارة «منفَّذ على N قاعدة» وتغيّر نصّ التأكيد بحسبها — اقرأه، فهو الفرق بين تنظيف قائمة وبين مخططين لن يتطابقا مجدداً.

متى الحذف هو الجواب الصحيح؟ ترحيل ناقص أو معطوب لم يُنفَّذ بعد على أي قاعدة، أو صفٌّ لم يعد له ملف في مستودعك أصلاً.

الصفوف التي لم يعد لها ملف

الاستيراد يضيف ويحدّث ولا يحذف. فلو حذفت ملفاً من مستودعك ودفعت وأعدت الاستيراد، يعود إليك «تم الاستيراد بنجاح» بينما الملف المحذوف ما يزال يُشحَن في كل بيان.

لذلك يُبلِغك الاستيراد بعددها ويضع على صفوفها شارة «غير موجود في المستودع». لا تُحذف تلقائياً: الاستيراد يجري على مرجع (ref) واحد، وغياب الملف عنه قد يعني غيابه عن ذلك الفرع فقط، وقد يكون نُفِّذ فعلاً على قاعدة حيّة. القرار قرارك على الصفّ.

SQL مباشر داخل البوّابة

للتصحيحات الصغيرة التي لا تستحقّ دورة كاملة عبر المستودع. تُحفَظ draft وتُنشَر مع الإصدار التالي. تُرفَض عبارات لا شأن لترحيل تطبيق بها — DROP DATABASE/SCHEMA/ROLE/USER، وGRANT/REVOKE، وALTER ROLE/USER، وCOPY … FROM PROGRAM، ودوالّ قراءة ملفات الخادم. الشبكة الحقيقية ليست هذه القائمة بل الـ schema المعزول: دور الاتصال لا يستطيع لمس بيانات تطبيق آخر أصلاً.

أدوات MCP

الأداة ماذا تفعل
list_migrations الترحيلات بترتيب التنفيذ + علم requires_database لكل إصدار
read_migration جسم ترحيل واحد — النسخة التي تنفّذها المنصّة فعلاً، وقد تختلف عن مستودعك
import_migrations_from_repo نفس زر «استيراد من المستودع»، ويبلّغ عن الصفوف المفقودة
provision_database إنشاء قاعدة sandbox وتطبيق ما لم يُطبَّق (production من البوّابة فقط)
database_status هل جُهِّزت القاعدة، ونتيجة آخر تشغيل، وكم مطبَّق مقابل معلَّق
diff_schema ما تصفه ملفاتك مقابل ما في قاعدة sandbox فعلاً

diff_schema يذكر تغطيته دائماً: ترحيل مخزَّن كمسار ملف لا يحمل SQL هنا، فلا يستطيع الفاحص ادّعاء «غير مُعلَن» على ما لم يقرأه.


عرض الدليل كاملاً في صفحة واحدة · ↧ Markdown خام