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 ที่เรียบง่ายกว่าในการทำงานแทน
