تتعامل PHP مع الكثير من المهام المتكررة في جانب الخادم (server side). فقد يتطلب طلب واحد مسح مجلد مليء بالصور، أو مراقبة قاعدة بيانات حتى تتغير حالة معينة، أو الاستمرار في طلب مدخلات من المستخدم حتى تستوفي القواعد الخاصة بك. إن القيام بأي من ذلك عن طريق نسخ ولصق نفس كتلة الكود سيكون أمراً هشاً ويجعل الصيانة شبه مستحيلة. وُجدت الحلقات التكرارية (Loops) لحل هذه المشكلة تحديداً؛ فهي تغلف المنطق البرمجي في كتلة قابلة لإعادة الاستخدام، وتقلل من الأخطاء، وتحافظ على تطبيقك مدمجاً ومنظماً.

يلجأ معظم مطوري PHP غريزياً إلى حلقة for عندما يعرفون عدد التكرارات المطلوبة، أو حلقة while عندما يعتمد العدد على شرط ديناميكي. ومع ذلك، هناك خيار ثالث، ويتصرف بشكل مختلف بما يكفي ليجعل اختيارك له في اللحظة المناسبة يجعل الكود الخاص بك أكثر وضوحاً بشكل ملحوظ. تقوم حلقة do-while بتنفيذ الكود الموجود داخل كتلتها أولاً، ثم تتحقق مما إذا كان يجب تشغيلها مرة أخرى.

ما الذي يجعل حلقة do-while مختلفة

في حلقة while القياسية، تقوم PHP بتقييم الشرط قبل الدخول إلى الكتلة البرمجية. إذا كان الشرط خاطئاً (false) في البداية، يتم تخطي الكتلة بالكامل. أما حلقة do-while فتعكس هذا الترتيب؛ فهي تشغل الكود مرة واحدة، وتصل إلى النهاية، وعندها فقط تتحقق من الشرط لتقرر ما إذا كانت ستعود للتكرار أم لا.

تبدو الصيغة البرمجية (syntax) كالتالي:

do {
    // statements
} while ($condition);

لاحظ وجود الفاصلة المنقوطة (semicolon) بعد عبارة while. هذه الفاصلة المنقوطة مطلوبة، ونسيانها سيؤدي إلى خطأ في الصيغة (syntax error). يتم تنفيذ الكتلة الموجودة بين do و while مرة واحدة على الأقل، حتى لو كان الشرط خاطئاً منذ البداية. سلوك "الاختبار اللاحق" (post-test) هذا ليس مجرد ميزة غريبة، بل هو السبب الرئيسي لوجود هذه الحلقة.

متى تلجأ إلى do-while

هناك ثلاث سيناريوهات شائعة يتألق فيها هذا الهيكل: عرض قائمة (menu)، والتحقق من صحة المدخلات (validating input)، وإعادة محاولة مهمة قد تفشل. في كل حالة، يتطلب المنطق البرمجي حدوث الإجراء قبل أن تتمكن من الحكم على ما إذا كان يجب الاستمرار أم لا.

تخيل أداة تعمل عبر سطر الأوامر (command-line) أو لوحة تحكم نصية للمسؤول. تحتاج إلى طباعة قائمة من الخيارات، والانتظار حتى يختار المستخدم أحدها، ثم تقرر ما إذا كنت ستعرض القائمة مرة أخرى. لا يمكنك التحقق مما إذا كان المستخدم قد اختار الخروج إلا بعد ظهور القائمة بالفعل على الشاشة.

$choice = 0;

do {
    echo "1. View Reports\n";
    echo "2. Export Data\n";
    echo "3. Exit\n";
    
    $choice = (int) readline("Select an option: ");
    
    if ($choice === 1) {
        // fetch and display reports
    } elseif ($choice === 2) {
        // run export logic
    }
} while ($choice !== 3);

يمكن لحلقة while العادية التعامل مع هذا تقنياً، لكنها ستجبرك على تكرار إخراج القائمة مرة واحدة قبل بدء الحلقة، أو تهيئة المتغير $choice بقيمة وهمية فقط لاستيفاء الشرط الأولي. هذا يشتت المنطق البرمجي المرتبط في مكانين مختلفين. أما نسخة do-while فتحافظ على العرض، والقراءة، والتحقق داخل كتلة واحدة منظمة.

التحقق من صحة المدخلات

غالباً ما يتبع التحقق من صحة المدخلات النمط نفسه. تريد مطالبة المستخدم، وفحص ما قدمه لك، والتكرار إذا فشل في استيفاء قواعدك. ولأن المطالبة يجب أن تحدث قبل الفحص، فإن حلقة do-while تناسب هذا السياق بشكل طبيعي.

do {
    $username = readline("Choose a username (min 3 chars): ");
    $isValid = strlen($username) >= 3;
    
    if (!$isValid) {
        echo "That username is too short.\n";
    }
} while (!$isValid);

باستخدام حلقة while القياسية، سيتعين عليك تهيئة $username بسلسلة نصية فارغة وتكرار سطر المطالبة فوق الحلقة حتى يكون لدى الشرط شيء لتقييمه. هذا التكرار يفتح الباب للأخطاء البرمجية (bugs). إذا قمت لاحقاً بتغيير رسالة المطالبة، فقد تتذكر تعديلها داخل الحلقة ولكنك تنسى النسخة الموجودة فوقها. يزيل هيكل do-while هذا الخطر من خلال ضمان تشغيل المطالبة داخل الكتلة قبل اختبار أي شرط.

إعادة محاولة مهمة فاشلة

قد تفشل طلبات الشبكة، أو أقفال الملفات، أو عمليات الكتابة في قاعدة البيانات في المحاولة الأولى، ولكنها تنجح بعد فترة وجيزة. عادةً ما ترغب في المحاولة مرة واحدة على الأقل، ثم الاستمرار في المحاولة فقط طالما استمر الفشل وطالما لا يزال لديك صبر.

$attempts = 0;
$maxRetries = 3;
$success = false;

do {
    $attempts++;
    $response = @file_get_contents('https://api.example.com/status');
    
    if ($response !== false) {
        $success = true;
    } elseif ($attempts < $maxRetries) {
        sleep(1);
    }
} while (!$success && $attempts < $maxRetries);

if (!$success) {
    // log the permanent failure
}

التفصيل الحاسم هنا هو أن الطلب يتم إرساله قبل أن تسأل PHP: "هل نجح الأمر؟". سيتعين على حلقة while إجراء طلب وهمي أو تعيين علامة نجاح زائفة قبل دخول الحلقة، مما قد يحجب ما يحدث فعلياً. أما نسخة do-while فتجعل التسلسل صادقاً: محاولة، فحص، ثم قرار.

مقارنة بين do-while و while

الفرق بين هاتين الحلقتين هو مسألة توقيت. لنفترض وجود متغير يبدأ في حالة ستفشل في أي اختبار:

$x = 10;

while ($x < 5) {
    echo $x;        // Never runs
}

مقابل نفس القيمة الابتدائية، تتصرف حلقة do-while كالتالي:

$x = 10;

do {
    echo $x;        // Runs once, outputs 10
} while ($x < 5);

هذا التنفيذ الواحد المضمون هو العامل الحاسم. إذا كان منطقك يتطلب صفر أو أكثر من التكرارات، فإن while هي الأداة الأكثر أماناً. أما إذا كان منطقك يتطلب تكراراً واحداً أو أكثر، فعادة ما تكون do-while هي الأداة الأكثر نظافة وترتيباً.

الأخطاء الشائعة وأفضل الممارسات

نظرًا لأن حلقة do-while تضمن تنفيذ الكود مرة واحدة على الأقل، فقد تؤدي أيضًا إلى حلقة لانهائية إذا لم تكن حذرًا. إذا لم تتغير المتغيرات التي يتم فحصها في الشرط داخل الكتلة البرمجية، فسيستمر PHP في الدوران إلى الأبد أو حتى يصل إلى الحد الأقصى لوقت التنفيذ.

خطأ شائع آخر هو إغفال الفاصلة المنقوطة في النهاية. الكود التالي سيفشل:

do {
    // code
} while ($x < 5)    // syntax error

يجب عليك أيضًا تجنب اللجوء إلى do-while بدافع العادة عندما تكون هناك حلقة أخرى أكثر تعبيرًا. إذا كنت تقوم بالتكرار عبر مصفوفة، فإن foreach هي الأفضل دائمًا تقريبًا. وإذا كنت تدير عدادًا رقميًا بسيطًا، فإن حلقة for تبقي على التهيئة والشرط والزيادة في سطر واحد، مما يجعل قراءتها أسهل.

من حيث الأداء، لا تسبب do-while أي عبء إضافي ملموس مقارنة بـ while في PHP. الفائدة هيكلية تمامًا. فعندما يرى مطور آخر حلقة do-while فإنه يفهم فورًا أن الكتلة البرمجية يجب أن تعمل قبل تقييم أي شرط. هذه المعلومة الوحيدة توفر عليه عناء تتبع المتغيرات الوهمية أو كود الإعداد المكرر فوق حلقة while القياسية.

الحالات الاستثنائية والأنماط البديلة

أحيانًا تكون حلقة do-while مفيدة حتى في حالة عدم وجود تفاعل من المستخدم. على سبيل المثال، عند استهلاك generator أو cursor ينتج عنصرًا واحدًا على الأقل، يمكنك معالجة العنصر الأول ثم التحقق مما إذا كان المؤشر قد انتقل إلى الموضع التالي الصالح. في قواعد الأكواد القديمة التي تستخدم موارد قاعدة بيانات خام بدلاً من مجموعات النتائج الحديثة، قد ترى نمطًا مثل هذا:

$row = $result->fetch();
do {
    // process $row
} while ($row = $result->fetch());

بينما يفضل PHP الحديث استخدام foreach مع PDO، يوضح هذا النمط كيف تتعامل do-while مع التكرار الأول كحالة خاصة دون الحاجة إلى تفرعات إضافية.

الخلاصة الحقيقية

حلقة do-while هي أداة محدودة الاستخدام، ولكن في المكان المناسب تكون هي الأداة الوحيدة المنطقية. فهي تبقي منطق الإعداد والتحقق في مكان واحد، وتضمن محاولة تنفيذ الإجراء قبل الحكم عليه، وتوصل نيتك مباشرة إلى الشخص التالي الذي يقرأ الكود. عندما يجب أن يأتي الشرط بعد العمل، استخدم do-while. وفي كل الحالات الأخرى، اترك المهام البسيطة لـ while أو for أو foreach.