PHP는 서버 측에서 많은 반복적인 작업을 처리합니다. 단일 요청이 이미지가 가득 찬 폴더를 스캔하거나, 상태가 변경될 때까지 데이터베이스를 폴링하거나, 사용자가 규칙에 맞는 입력을 할 때까지 계속 입력을 요청해야 할 수도 있습니다. 동일한 코드 블록을 복사하여 붙여넣는 방식으로 이러한 작업을 수행하면 코드가 취약해지고 유지보수가 거의 불가능해집니다. 루프는 바로 이 문제를 해결하기 위해 존재합니다. 루프는 로직을 재사용 가능한 블록으로 묶어 오류를 줄이고 애플리케이션을 간결하게 유지합니다.
대부분의 PHP 개발자는 반복 횟수를 알고 있을 때는 for 루프를, 횟수가 동적인 조건에 따라 달라질 때는 while 루프를 본능적으로 사용합니다. 하지만 세 번째 옵션이 있으며, 이는 적절한 순간에 선택하면 코드를 훨씬 더 명확하게 만들 수 있을 만큼 동작 방식이 다릅니다. do-while 루프는 블록 내부의 코드를 먼저 실행한 다음, 다시 실행해야 하는지 여부를 확인합니다.
do-while 루프가 다른 점
표준 while 루프에서 PHP는 블록에 진입하기 전에 조건을 평가합니다. 조건이 처음부터 거짓(false)이면 블록은 완전히 건너뜁니다. do-while 루프는 그 순서를 뒤집습니다. 코드를 한 번 실행하고 끝에 도달한 다음, 루프를 다시 돌릴지 결정하기 위해 조건을 확인합니다.
구문은 다음과 같습니다:
do {
// statements
} while ($condition);
while 절 뒤에 세미콜론(;)이 있다는 점에 주목하세요. 이 세미콜론은 필수이며, 누락하면 구문 오류(syntax error)가 발생합니다. do와 while 사이의 블록은 조건이 처음부터 거짓이더라도 항상 최소 한 번은 실행됩니다. 이러한 사후 테스트(post-test) 동작은 단순한 특징이 아니라, 이 루프가 존재하는 이유 그 자체입니다.
do-while을 사용해야 할 때
이 구조가 빛을 발하는 세 가지 일반적인 시나리오가 있습니다: 메뉴 표시, 입력 검증, 그리고 실패할 수 있는 작업 재시도입니다. 각 경우 모두, 계속 진행할지 판단하기 전에 동작이 먼저 이루어져야 한다는 로직을 요구합니다.
메뉴 표시
명령줄 도구나 텍스트 기반 관리 패널을 상상해 보세요. 옵션 목록을 출력하고, 사용자가 하나를 선택할 때까지 기다린 다음, 메뉴를 다시 보여줄지 결정해야 합니다. 메뉴가 화면에 나타나기 전까지는 사용자가 종료(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 버전은 표시, 읽기, 확인 과정을 하나의 깔끔한 블록 안에 유지합니다.
입력 검증
입력 검증도 종종 동일한 패턴을 따릅니다. 사용자에게 프롬프트를 띄우고, 입력된 내용을 검사한 뒤, 규칙에 어긋나면 반복하고 싶을 것입니다. 검사하기 전에 프롬프트가 먼저 나타나야 하므로 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) 요청을 수행하거나 가짜 성공 플래그를 설정해야 하는데, 이는 실제로 어떤 일이 일어나고 있는지 모호하게 만듭니다. 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);
단 한 번의 보장된 실행이 결정적인 요소입니다. 로직이 0번 이상의 반복을 필요로 한다면 while이 더 안전한 도구입니다. 로직이 1번 이상의 반복을 필요로 한다면 do-while이 대개 더 깔끔한 도구입니다.
주의사항 및 권장 사항
do-while 루프는 최소 한 번의 실행을 보장하기 때문에, 주의하지 않으면 무한 루프를 유발할 수도 있습니다. 조건식에서 확인하는 변수가 블록 내부에서 전혀 변하지 않는다면, PHP는 무한히 실행되거나 최대 실행 시간(maximum execution time)에 도달할 때까지 계속 돌아가게 됩니다.
또 다른 흔한 실수는 마지막에 세미콜론을 생략하는 것입니다. 다음 코드는 실패합니다:
do {
// code
} while ($x < 5) // syntax error
또한 다른 루프가 더 명확한 표현력을 가질 때 습관적으로 do-while을 사용하는 것은 피해야 합니다. 배열을 순회하는 경우라면 foreach가 거의 항상 더 우수합니다. 단순한 숫자 카운터를 관리하는 경우라면, for 루프가 초기화, 조건, 증감식을 한 줄에 유지하므로 코드를 훑어보기가 더 쉽습니다.
성능 측면에서 PHP의 do-while은 while과 비교했을 때 의미 있는 오버헤드가 없습니다. 이점은 전적으로 구조적인 면에 있습니다. 다른 개발자가 do-while 루프를 보면, 조건이 평가되기 전에 블록이 반드시 실행되어야 한다는 것을 즉시 이해할 수 있습니다. 이 정보 하나만으로도 일반적인 while 루프 상단에 더미 변수를 만들거나 중복된 설정 코드를 작성하고 이를 추적해야 하는 수고를 덜어줍니다.
예외 사례 및 대안 패턴
때로는 사용자 상호작용이 없는 경우에도 do-while 루프가 유용할 수 있습니다. 예를 들어, 최소 하나 이상의 아이템을 생성하는 제너레이터(generator)나 커서(cursor)를 소비할 때, 첫 번째 아이템을 처리한 다음 커서가 유효한 다음 위치로 이동했는지 확인할 수 있습니다. 현대적인 결과 집합(result set) 대신 로우(raw) 데이터베이스 리소스를 사용하는 레거시 코드베이스에서는 다음과 같은 패턴을 볼 수 있습니다:
$row = $result->fetch();
do {
// process $row
} while ($row = $result->fetch());
현대적인 PHP는 PDO와 함께 foreach를 선호하지만, 이 패턴은 do-while이 추가적인 분기 없이 첫 번째 반복을 어떻게 특수한 경우로 처리하는지 잘 보여줍니다.
핵심 요약
do-while 루프는 용도가 제한적인 도구이지만, 적절한 위치에서는 유일하게 합리적인 도구가 됩니다. 설정 및 검증 로직을 한곳에 유지하고, 판단을 내리기 전에 동작이 시도됨을 보장하며, 코드를 읽는 다음 사람에게 의도를 직접적으로 전달합니다. 작업이 끝난 후에 조건이 확인되어야 하는 경우라면 do-while을 사용하세요. 그 외의 모든 경우에는 더 단순한 while, for, 또는 foreach를 사용하면 됩니다.
