PHP حجم زیادی از کارهای تکراری را در سمت سرور مدیریت میکند. یک درخواست واحد ممکن است نیاز داشته باشد پوشهای پر از تصاویر را اسکن کند، یک پایگاه داده را تا زمان تغییر وضعیت بررسی کند، یا مدام از کاربر ورودی بخواهد تا زمانی که با قوانین شما مطابقت داشته باشد. انجام هر یک از این کارها با کپی و چسباندن یک بلوک کد مشابه، کد را شکننده کرده و نگهداری از آن را تقریباً غیرممکن میکند. حلقهها دقیقاً برای حل همین مشکل وجود دارند. آنها منطق را در یک بلوک قابل استفاده مجدد بستهبندی میکنند، خطاها را کاهش میدهند و اپلیکیشن شما را فشرده و مرتب نگه میدارند.
اکثر توسعهدهندگان PHP وقتی میدانند به چند تکرار نیاز دارند، غریزی سراغ حلقه for میروند، یا وقتی تعداد تکرارها به یک شرط پویا بستگی دارد، از حلقه while استفاده میکنند. با این حال، گزینه سومی هم وجود دارد که رفتار آن به اندازه کافی متفاوت است، به طوری که انتخاب آن در لحظه مناسب میتواند کد شما را به طرز قابل توجهی شفافتر کند. حلقه do-while ابتدا کد داخل بلوک خود را اجرا میکند و سپس بررسی میکند که آیا باید دوباره اجرا شود یا خیر.
چه چیزی حلقه do-while را متفاوت میکند
در یک حلقه while استاندارد، PHP قبل از ورود به بلوک، شرط را ارزیابی میکند. اگر شرط از ابتدا false باشد، بلوک کاملاً نادیده گرفته میشود. یک حلقه do-while این ترتیب را معکوس میکند. این حلقه کد را یک بار اجرا میکند، به پایان میرسد و تنها پس از آن شرط را بررسی میکند تا تصمیم بگیرد که آیا باید دوباره به ابتدای حلقه بازگردد یا خیر.
نحو (Syntax) آن به این صورت است:
do {
// statements
} while ($condition);
به علامت نقطهویرگول (semicolon) بعد از عبارت while دقت کنید. این نقطهویرگول الزامی است و فراموش کردن آن باعث بروز یک syntax error میشود. بلوک بین do و while همیشه حداقل یک بار اجرا میشود، حتی اگر شرط از همان ابتدا false باشد. این رفتارِ «پس از تست» (post-test) صرفاً یک ویژگی عجیب نیست؛ بلکه دلیل اصلی وجود این حلقه است.
چه زمانی از do-while استفاده کنیم
سه سناریوی رایج وجود دارد که این ساختار در آنها میدرخشد: نمایش یک منو، اعتبارسنجی ورودی، و تلاش مجدد برای انجام کاری که ممکن است با شکست مواجه شود. در هر مورد، منطق کار ایجاب میکند که عمل مورد نظر ابتدا انجام شود و سپس بتوانید در مورد ادامه دادن تصمیمگیری کنید.
نمایش یک منو
یک ابزار خط فرمان یا یک پنل مدیریت متنی را تصور کنید. شما باید لیستی از گزینهها را چاپ کنید، منتظر بمانید تا کاربر یکی را انتخاب کند و سپس تصمیم بگیرید که آیا منو را دوباره نشان دهید یا خیر. شما نمیتوانید بررسی کنید که آیا کاربر گزینه خروج را انتخاب کرده است یا خیر، مگر اینکه منو قبلاً روی صفحه ظاهر شده باشد.
$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 را با یک رشته خالی مقداردهی کنید و خط پرسش را قبل از حلقه تکرار کنید تا شرط چیزی برای ارزیابی داشته باشد. این تکرار باعث بروز باگ میشود. اگر بعداً پیام پرسش را تغییر دهید، ممکن است به یاد بیاورید که آن را داخل حلقه ویرایش کنید اما کپی موجود در بالای حلقه را فراموش کنید. ساختار 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 باید قبل از ورود به حلقه، یک درخواست آزمایشی (dummy request) انجام دهد یا یک پرچم موفقیت جعلی تنظیم کند که این کار باعث مبهم شدن فرآیند واقعی میشود. نسخه 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 معمولاً ابزار تمیزتری است.
Pitfalls and Best Practices
از آنجایی که حلقه do-while تضمین میکند که حداقل یک بار اجرا شود، اگر مراقب نباشید میتواند منجر به یک حلقه بینهایت نیز بشود. اگر متغیرهایی که در شرط بررسی میشوند، در داخل بلوک هرگز تغییر نکنند، PHP تا ابد در حال چرخش خواهد بود یا تا زمانی که به حداکثر زمان اجرای مجاز برسد.
اشتباه رایج دیگر، فراموش کردن نقطه-ویرگول (semicolon) در انتهای دستور است. مورد زیر با خطا مواجه خواهد شد:
do {
// code
} while ($x < 5) // syntax error
همچنین باید از استفاده از do-while صرفاً از روی عادت، در زمانی که حلقهی دیگری گویاتر است، خودداری کنید. اگر در حال پیمایش (iterating) روی یک آرایه هستید، foreach تقریباً همیشه برتر است. اگر در حال مدیریت یک شمارنده عددی ساده هستید، یک حلقه for مقداردهی اولیه، شرط و افزایش را در یک خط نگه میدارد که خواندن و بررسی آن آسانتر است.
از نظر عملکرد، do-while در مقایسه با while در PHP بار اضافی (overhead) معناداری ندارد. مزیت آن کاملاً ساختاری است. وقتی توسعهدهنده دیگری یک حلقه do-while را میبیند، بلافاصله متوجه میشود که بلوک کد باید قبل از ارزیابی هر شرطی اجرا شود. همین یک نکته، آنها را از ردیابی متغیرهای صوری (dummy variables) یا کدهای آمادهسازی تکراری که معمولاً بالای یک حلقه while استاندارد قرار میگیرند، بینیاز میکند.
موارد خاص و الگوهای جایگزین
گاهی اوقات یک حلقه do-while حتی زمانی که هیچ تعامل کاربری در میان نیست نیز مفید است. به عنوان مثال، هنگام استفاده از یک generator یا cursor که حداقل یک آیتم برمیگرداند، میتوانید اولین آیتم را پردازش کرده و سپس بررسی کنید که آیا مکاننما (cursor) به موقعیت معتبر بعدی رفته است یا خیر. در کدهای قدیمی (legacy codebases) که به جای مجموعهنتایج (result sets) مدرن، از منابع خام پایگاه داده استفاده میکنند، ممکن است الگویی شبیه به این ببینید:
$row = $result->fetch();
do {
// process $row
} while ($row = $result->fetch());
اگرچه PHP مدرن استفاده از foreach با PDO را ترجیح میدهد، اما این الگو نشان میدهد که چگونه do-while اولین تکرار را به عنوان یک مورد خاص و بدون نیاز به شاخهبندی (branching) اضافی مدیریت میکند.
نتیجهگیری نهایی
حلقه do-while ابزار محدودی است، اما در جای درست، تنها ابزاری است که منطقی به نظر میرسد. این حلقه منطق آمادهسازی و اعتبارسنجی شما را در یک جا نگه میدارد، تضمین میکند که یک اقدام قبل از قضاوت شدن امتحان شود، و قصد شما را مستقیماً به شخص بعدی که کد را میخواند منتقل میکند. هرگاه شرط باید پس از انجام کار بررسی شود، از do-while استفاده کنید. در هر مورد دیگری، اجازه دهید حلقههای سادهترِ while ، for یا foreach کار را انجام دهند.
