PHP จัดการงานที่ต้องทำซ้ำๆ จำนวนมากในฝั่งเซิร์ฟเวอร์ คำขอ (request) เพียงครั้งเดียวอาจต้องสแกนโฟลเดอร์ที่เต็มไปด้วยรูปภาพ, ตรวจสอบฐานข้อมูล (poll a database) จนกว่าสถานะจะเปลี่ยน หรือถามข้อมูลจากผู้ใช้ไปเรื่อยๆ จนกว่าจะเป็นไปตามกฎที่คุณตั้งไว้ การทำสิ่งเหล่านี้ด้วยการคัดลอกและวางโค้ดเดิมซ้ำๆ จะทำให้โค้ดเปราะบางและแทบจะเป็นไปไม่ได้เลยที่จะบำรุงรักษา ลูป (Loops) ถูกสร้างขึ้นมาเพื่อแก้ปัญหานี้โดยเฉพาะ โดยจะรวมตรรกะเข้าเป็นบล็อกที่นำกลับมาใช้ใหม่ได้ ช่วยลดข้อผิดพลาด และทำให้แอปพลิเคชันของคุณมีความกระชับ

นักพัฒนา PHP ส่วนใหญ่มักจะเลือกใช้ลูป for โดยสัญชาตญาณเมื่อพวกเขารู้จำนวนรอบที่แน่นอน หรือใช้ลูป while เมื่อจำนวนรอบขึ้นอยู่กับเงื่อนไขที่เปลี่ยนแปลงได้ อย่างไรก็ตาม ยังมีทางเลือกที่สาม ซึ่งมีพฤติกรรมที่แตกต่างกันมากพอจนการเลือกใช้ให้ถูกจังหวะสามารถทำให้โค้ดของคุณชัดเจนขึ้นอย่างมาก ลูป do-while จะทำงานกับโค้ดภายในบล็อกก่อน จากนั้นจึงตรวจสอบว่าควรจะทำงานอีกครั้งหรือไม่

ความแตกต่างของลูป do-while

ในลูป while แบบมาตรฐาน PHP จะประเมินเงื่อนไขก่อนที่จะเริ่มเข้าสู่บล็อกโค้ด หากเงื่อนไขเป็นเท็จตั้งแต่เริ่มต้น บล็อกนั้นจะถูกข้ามไปทั้งหมด แต่ลูป do-while จะสลับลำดับนั้น โดยจะทำงานกับโค้ดหนึ่งครั้ง เมื่อถึงจุดสิ้นสุดแล้วจึงค่อยตรวจสอบเงื่อนไขเพื่อตัดสินใจว่าจะวนลูปกลับไปหรือไม่

ไวยากรณ์ (syntax) มีลักษณะดังนี้:

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

สังเกตเครื่องหมายเซมิโคลอน (semicolon) หลังเงื่อนไข while เครื่องหมายนี้จำเป็นต้องมี หากลืมใส่จะทำให้เกิดข้อผิดพลาดทางไวยากรณ์ (syntax error) บล็อกระหว่าง do และ while จะทำงานอย่างน้อยหนึ่งครั้งเสมอ แม้ว่าเงื่อนไขจะเป็นเท็จตั้งแต่เริ่มต้นก็ตาม พฤติกรรมแบบตรวจสอบภายหลัง (post-test behavior) นี้ไม่ใช่แค่เรื่องบังเอิญ แต่มันคือเหตุผลหลักที่ลูปนี้ถูกสร้างขึ้นมา

เมื่อไหร่ควรใช้ do-while

มีสามสถานการณ์ทั่วไปที่โครงสร้างนี้แสดงประสิทธิภาพได้ดีที่สุด: การแสดงเมนู, การตรวจสอบความถูกต้องของข้อมูล (input validation) และการพยายามทำงานซ้ำในกรณีที่อาจล้มเหลว ในแต่ละกรณี ตรรกะกำหนดว่าการกระทำต้องเกิดขึ้นก่อนที่คุณจะตัดสินใจได้ว่าจะทำต่อไปหรือไม่

การแสดงเมนู

ลองนึกถึงเครื่องมือแบบ command-line หรือแผงควบคุมผู้ดูแลระบบแบบข้อความ คุณต้องพิมพ์รายการตัวเลือก รอให้ผู้ใช้เลือกหนึ่งอย่าง แล้วจึงตัดสินใจว่าจะแสดงเมนูอีกครั้งหรือไม่ คุณไม่สามารถตรวจสอบได้ว่าผู้ใช้เลือก "ออก" (exit) หรือไม่ จนกว่าเมนูจะปรากฏขึ้นบนหน้าจอแล้ว

$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 จะเก็บการแสดงผล, การอ่านค่า และการตรวจสอบไว้ในบล็อกเดียวอย่างเป็นระเบียบ

การตรวจสอบความถูกต้องของข้อมูล

การตรวจสอบความถูกต้องของข้อมูล (Input validation) มักจะมีรูปแบบเดียวกัน คุณต้องการแจ้งเตือน (prompt) ผู้ใช้, ตรวจสอบสิ่งที่เขาส่งมา และทำซ้ำหากไม่เป็นไปตามกฎ เนื่องจากต้องมีการแจ้งเตือนก่อนการตรวจสอบ ลูป 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 เป็นสตริงว่าง และต้องเขียนบรรทัดแจ้งเตือนซ้ำอีกครั้งเหนือลูปเพื่อให้เงื่อนไขมีค่าสำหรับประเมิน การทำซ้ำเช่นนี้เป็นการเปิดช่องให้เกิดบั๊ก หากภายหลังคุณเปลี่ยนข้อความแจ้งเตือน คุณอาจจำได้ว่าต้องแก้ไขในลูป แต่ลืมแก้ไขตัวที่อยู่เหนือลูป โครงสร้าง do-while จะช่วยขจัดความเสี่ยงนั้นโดยรับประกันว่าการแจ้งเตือนจะทำงานภายในบล็อกก่อนที่จะมีการทดสอบเงื่อนไขใดๆ

การพยายามทำงานซ้ำเมื่อล้มเหลว

การส่งคำขอผ่านเครือข่าย (network requests), การล็อกไฟล์ (file locks) และการเขียนฐานข้อมูล บางครั้งอาจล้มเหลวในการพยายามครั้งแรก แต่จะสำเร็จในเวลาต่อมา โดยปกติคุณต้องการลองอย่างน้อยหนึ่งครั้ง จากนั้นจึงพยายามต่อไปตราบเท่าที่ความล้มเหลวยังคงอยู่และคุณยังมีเวลาเหลือพอ

$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 มักจะเป็นเครื่องมือที่สะอาดตา (cleaner) กว่า

ข้อควรระวังและแนวทางปฏิบัติที่ดีที่สุด

เนื่องจากลูป do-while รับประกันว่าจะมีการทำงานอย่างน้อยหนึ่งครั้ง มันจึงอาจทำให้เกิดลูปไม่สิ้นสุด (infinite loop) ได้หากคุณไม่ระวัง หากตัวแปรที่ถูกตรวจสอบในเงื่อนไขไม่มีการเปลี่ยนแปลงภายในบล็อก PHP จะทำงานวนไปเรื่อยๆ หรือจนกว่าจะถึงเวลาทำงานสูงสุดที่กำหนดไว้ (maximum execution time)

ข้อผิดพลาดทั่วไปอีกอย่างหนึ่งคือการลืมใส่เครื่องหมายเซมิโคลอน (semicolon) ที่ท้ายคำสั่ง ตัวอย่างต่อไปนี้จะทำงานผิดพลาด:

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

คุณควรหลีกเลี่ยงการใช้ do-while เพียงเพราะความเคยชิน ในเมื่อมีลูปแบบอื่นที่สื่อความหมายได้ชัดเจนกว่า หากคุณกำลังวนลูปผ่านอาร์เรย์ (array) การใช้ foreach มักจะดีกว่าเกือบทุกกรณี หากคุณกำลังจัดการตัวนับตัวเลข (numeric counter) แบบง่ายๆ การใช้ลูป for จะช่วยให้การกำหนดค่าเริ่มต้น (initialization), เงื่อนไข (condition) และการเพิ่มค่า (increment) อยู่ในบรรทัดเดียว ซึ่งทำให้อ่านและทำความเข้าใจได้ง่ายกว่า

ในแง่ของประสิทธิภาพ do-while ไม่ได้สร้างภาระ (overhead) ที่มีนัยสำคัญเมื่อเทียบกับ while ใน PHP ประโยชน์ของมันเป็นเรื่องของโครงสร้างล้วนๆ เมื่อนักพัฒนาคนอื่นเห็นลูป do-while พวกเขาจะเข้าใจได้ทันทีว่าบล็อกคำสั่งนั้นต้องทำงานก่อนที่จะมีการตรวจสอบเงื่อนไข ข้อมูลเพียงอย่างเดียวนี้ช่วยให้พวกเขาไม่ต้องเสียเวลาไล่ดูตัวแปรหลอก (dummy variables) หรือโค้ดเตรียมการ (setup code) ที่เขียนซ้ำซ้อนเหนือลูป while แบบปกติ

กรณีขอบเขต (Edge Cases) และรูปแบบทางเลือก

ในบางครั้ง ลูป do-while ก็มีประโยชน์แม้จะไม่มีการโต้ตอบจากผู้ใช้ก็ตาม ตัวอย่างเช่น เมื่อใช้งาน generator หรือ cursor ที่ส่งค่า (yield) ออกมาอย่างน้อยหนึ่งรายการ คุณสามารถประมวลผลรายการแรกก่อน แล้วจึงตรวจสอบว่า cursor ได้เลื่อนไปยังตำแหน่งถัดไปที่ถูกต้องแล้วหรือไม่ ในโค้ดรุ่นเก่า (legacy codebases) ที่ใช้ทรัพยากรฐานข้อมูลโดยตรง (raw database resources) แทนที่จะใช้ result sets สมัยใหม่ คุณอาจเห็นรูปแบบดังนี้:

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

แม้ว่า PHP สมัยใหม่จะนิยมใช้ foreach ร่วมกับ PDO แต่รูปแบบนี้ก็แสดงให้เห็นว่า do-while จัดการกับการวนลูปครั้งแรกในฐานะกรณีพิเศษได้โดยไม่ต้องมีการแยกเงื่อนไข (branching) เพิ่มเติม

บทสรุปที่สำคัญ

ลูป do-while เป็นเครื่องมือที่มีขอบเขตการใช้งานเฉพาะเจาะจง แต่หากใช้ในจุดที่เหมาะสม มันจะเป็นเครื่องมือเดียวที่สมเหตุสมผลที่สุด มันช่วยให้ตรรกะการเตรียมการ (setup) และการตรวจสอบ (validation) อยู่รวมกันในที่เดียว รับประกันว่าการกระทำนั้นจะถูกพยายามทำก่อนที่จะมีการตัดสินเงื่อนไข และสื่อเจตนาของคุณไปยังนักพัฒนาคนถัดไปที่มาอ่านโค้ดได้อย่างตรงไปตรงมา เมื่อเงื่อนไขต้องเกิดขึ้นหลังจากที่งานเสร็จสิ้นแล้ว ให้ใช้ do-while แต่ในกรณีอื่นๆ ทั้งหมด ให้ใช้ while, for หรือ foreach ที่เรียบง่ายกว่าในการทำงานแทน