조건과 반복 - match 식까지
이 장에서 배우는 것
프로그램은 위에서 아래로 한 줄씩 실행되는 것이 기본이다. 하지만 빵집 주문 사이트는 수량에 따라 할인율이 달라지고, 주문 상태에 따라 안내 문구가 달라지고, 메뉴 목록의 길이도 그날그날 다르다. 이런 일을 하려면 실행 흐름을 갈라 주는 구조(분기)와 같은 일을 여러 번 되풀이하는 구조(반복)가 필요하다. 이 장에서는 두 구조를 PHP 문법으로 쓰는 방법과, HTML 템플릿 안에서 쓰기 편한 대체 문법(alternative syntax)까지 다룬다.
앞 장에서 == 가 값을 바꿔서 비교한다는 점을 조심하라고 했다. 이 장의 switch 는 바로 그 느슨한 비교를 쓰고 match 는 엄격한 비교를 쓴다. 둘의 차이를 실제 코드로 확인한다.
if·elseif·else로 조건에 따라 실행할 코드를 고른다.switch와match의 비교 방식과 결과 처리 방식이 어떻게 다른지 설명하고, 상황에 맞게 고른다.for·foreach·while·do-while을 목적에 맞게 쓰고,break와continue로 흐름을 조절한다.- 템플릿 파일 안에서
if:·foreach:같은 대체 문법으로 HTML 을 반복 출력한다.
문제 상황
동네 빵집 사장님이 온라인 예약 주문 사이트를 열려고 한다. 지금까지 만든 것은 가격과 수량을 곱해 합계를 내는 계산뿐이다. 그런데 사장님이 요구사항을 하나씩 더 알려 준다.
- 5개 이상 주문하면 5%, 10개 이상이면 10%, 20개 이상이면 15% 를 깎아 준다.
- 주문 상태는
received,paid,baked,picked네 가지이고, 손님에게는 한국어 문구로 보여 준다. 알 수 없는 상태 값이 들어오면 "확인 필요" 로 표시한다. - 월요일은 쉬고, 주말은 오후 2시까지만 주문을 받는다.
- 하루에 만들 수 있는 빵은 12개다. 접수한 수량이 한도를 넘으면 더 받지 않는다. 수량이 0 이하로 잘못 들어온 요청은 건너뛴다.
- 메뉴 페이지는 오늘 준비된 빵을 목록으로 보여 주고, 준비된 빵이 없으면 안내 문구를 보여 준다.
이 요구사항은 모두 "조건에 따라 다르게" 또는 "여러 번 되풀이" 중 하나다. 이 장에서 이 다섯 가지를 한 프로그램으로 구현한다.
분기: if, switch, match
if · elseif · else
가장 기본적인 분기는 if 다. 소괄호 안의 조건식이 참(true)이면 중괄호 블록(block, 중괄호로 묶은 코드 덩어리)을 실행한다. 조건이 여러 개면 elseif 를 잇고, 어느 것도 아닐 때를 위해 else 를 둘 수 있다.
if ($quantity >= 20) {
$rate = 15;
} elseif ($quantity >= 10) {
$rate = 10;
} elseif ($quantity >= 5) {
$rate = 5;
} else {
$rate = 0;
}
조건은 위에서 아래로 차례로 검사하고, 처음 참인 갈래를 실행한 뒤에는 나머지를 검사하지 않는다. 그래서 큰 값부터 검사하는 순서가 중요하다. 수량이 25개일 때 >= 5 를 먼저 두면 15% 가 아니라 5% 가 적용된다.
조건식이 참인지 거짓인지는 앞 장에서 본 규칙을 따른다. 0, 빈 문자열, "0", 빈 배열, null 은 거짓으로 취급된다. 값이 "비어 있는가" 를 묻고 싶은지 "정확히 이 값인가" 를 묻고 싶은지 구분하고, 후자라면 === 로 비교하는 편이 안전하다.
switch
switch 는 하나의 값을 여러 case 와 차례로 비교해 맞는 곳부터 실행한다. 비교에는 == 가 쓰인다. 맞는 case 를 찾으면 break 나 return 을 만날 때까지 아래 case 의 코드까지 이어서 실행한다. 이 성질을 폴스루(fall-through)라고 한다. 아래처럼 의도적으로 여러 case 를 하나로 묶을 때는 편리하다.
switch ($day) {
case 6:
case 7:
return '주말은 오후 2시까지 받는다';
default:
return '평일은 오후 6시까지 받는다';
}
반대로 break 를 빼먹으면 의도하지 않은 코드까지 실행된다. 이 문제는 뒤의 "자주 틀리는 것" 에서 다시 본다.
match
match 는 PHP 8.0 에서 들어온 식(expression)이다. 식은 값을 만들어 내는 코드 조각이라, return 이나 변수 대입의 오른쪽에 바로 쓸 수 있다. 각 갈래는 조건값 => 결과값 형태이고, 쉼표로 여러 조건값을 한 갈래에 묶을 수 있다.
$label = match ($status) {
'received', 'paid' => '준비 중',
'baked' => '픽업 가능',
'picked' => '완료',
default => '확인 필요',
};
match 는 === 로 비교하므로 타입까지 같아야 맞는다. 폴스루가 없어서 break 도 필요 없다. 또 어느 갈래에도 맞지 않고 default 도 없으면 UnhandledMatchError 예외가 발생한다. 처리하지 못한 값을 조용히 넘기지 않고 바로 드러내는 성질이다.
범위를 검사하고 싶을 때는 match (true) 를 쓰는 방법이 있다. 각 갈래의 조건식이 true 와 엄격하게 같은지를 위에서부터 검사하므로, 조건이 true 를 돌려주는 첫 갈래가 선택된다.
$size = match (true) {
$quantity >= 20 => '대량',
$quantity >= 5 => '단체',
default => '일반',
};
switch 와 match 는 무엇이 다른가
| 항목 | switch | match |
|---|---|---|
| 종류 | 문(statement) | 식(expression), 값을 돌려준다 |
| 비교 | == (느슨한 비교) | === (엄격한 비교) |
| 다음 갈래로 이어짐 | 있다, break 로 끊는다 | 없다 |
| 맞는 갈래가 없을 때 | 아무 일도 없이 지나간다 | UnhandledMatchError |
값을 골라서 돌려주는 일이면 match 가 짧고 안전하다. 갈래마다 여러 문장을 실행해야 하거나 폴스루가 정말 필요할 때만 switch 를 쓴다. 느슨한 비교가 어떤 결과를 내는지는 다음 코드로 확인할 수 있다.
$code = '1e1'; // 숫자 형태의 문자열, 값은 10
switch ($code) {
case '10':
echo 'switch: 10 번 갈래'; // 실행된다
break;
}
echo match ($code) {
'10' => 'match: 10 번 갈래',
default => 'match: 그 밖의 값', // 이쪽이 선택된다
};
'1e1' 과 '10' 은 둘 다 숫자로 읽을 수 있는 문자열이라 == 는 두 값을 숫자 10 으로 보고 같다고 판단한다. 상태 코드나 주문 번호처럼 글자 그대로 맞아야 하는 값에는 이런 비교가 위험하다.
반복: for, foreach, while
반복문 고르기
반복문(loop)은 같은 코드 블록을 조건이 유지되는 동안 되풀이한다. 무엇을 기준으로 반복하느냐에 따라 고른다.
| 문법 | 쓰는 때 | 조건을 검사하는 시점 |
|---|---|---|
for | 횟수나 범위가 정해져 있다 | 본문 실행 전 |
foreach | 목록의 원소를 처음부터 끝까지 훑는다 | 원소가 남아 있는 동안 |
while | 몇 번인지 모르고 조건이 참인 동안 계속한다 | 본문 실행 전 |
do-while | 일단 한 번은 실행해야 한다 | 본문 실행 후 |
for 는 소괄호 안에 초기화, 조건, 갱신을 세미콜론으로 나눠 적는다. 아래는 1 에서 시작해 3 씩 늘리며 7 이하일 때까지 반복한다.
for ($day = 1; $day <= 7; $day += 3) {
echo $day, "\n"; // 1, 4, 7
}
foreach 는 목록에서 원소를 하나씩 꺼내 변수에 담는다. 목록을 만드는 [1, 5, 10] 같은 배열(array)은 다음 장에서 자세히 다루므로, 여기서는 "값을 쉼표로 늘어놓은 목록" 정도로 이해하면 된다. $index => $name 처럼 쓰면 원소의 자리 번호도 함께 받는다. 자리 번호는 0 부터 시작한다.
foreach ([1, 5, 10, 20] as $quantity) {
echo $quantity, "\n";
}
while 은 조건이 참인 동안 본문을 반복한다. 본문 안에서 조건에 쓰이는 값을 바꾸지 않으면 끝나지 않는 반복이 된다.
while ($stock >= 4) {
$stock -= 4;
}
do-while 은 본문을 먼저 실행하고 조건을 나중에 검사한다. 조건이 처음부터 거짓이어도 본문이 한 번은 실행된다.
break 와 continue
break 는 가장 안쪽 반복을 즉시 끝내고, continue 는 이번 회차의 남은 본문을 건너뛰고 다음 회차로 넘어간다. 접수 한도를 넘으면 그만 받고(break), 잘못된 수량은 건너뛰는(continue) 요구사항에 쓴다.
템플릿 안의 대체 문법
PHP 파일은 <?php 와 ?> 사이만 PHP 코드로 실행하고, 그 밖의 글자는 그대로 출력한다. 이 덕분에 HTML 사이사이에 PHP 를 끼워 넣는 템플릿(template, 출력 모양을 적어 둔 파일)을 만들 수 있다. 그런데 중괄호 { 와 } 를 HTML 사이에 흩어 두면 어디서 닫히는지 알아보기 어렵다. 그래서 PHP 는 중괄호 대신 콜론과 end... 키워드를 쓰는 대체 문법을 제공한다.
| 기본 문법 | 대체 문법 | 닫는 키워드 |
|---|---|---|
if (...) { } | if (...): | endif; |
foreach (...) { } | foreach (...): | endforeach; |
for (...) { } | for (...): | endfor; |
while (...) { } | while (...): | endwhile; |
값을 바로 출력할 때는 <?= 값 ?> 라는 짧은 표기를 쓴다. <?php echo 값; ?> 와 같은 뜻이다. 사용자가 입력한 글자나 외부에서 온 글자를 HTML 에 넣을 때는 htmlspecialchars() 로 <, >, &, 따옴표를 안전한 표기로 바꿔 준다. 이 내용은 요청과 응답을 다루는 장에서 본격적으로 다룬다. 여기서는 습관으로 미리 익혀 둔다.
PHP 는 ?> 바로 뒤에 오는 줄바꿈 한 개를 출력하지 않고 삼킨다. 그래서 <?php foreach (...): ?> 처럼 줄 끝이 ?> 인 제어 줄은 빈 줄을 남기지 않는다. 반면 <li>...</li> 로 끝나는 줄은 줄바꿈이 그대로 나간다. 이 규칙을 알고 있으면 출력 모양을 정확히 예측할 수 있다.
이 장의 예제는 CLI 에서 확인하도록 템플릿 파일을 require 로 불러온다. require 는 다른 PHP 파일을 그 자리에서 실행하는 문법이고, 불러온 파일은 불러온 쪽의 변수를 그대로 볼 수 있다. 브라우저로 결과를 보는 방법은 요청과 응답을 다루는 장에서 php -S 로 내장 웹 서버를 띄우며 설명한다. 조건과 반복 문법 자체는 CLI 든 웹이든 같다.
완성 코드
두 파일을 같은 디렉터리에 둔다. main.php 가 계산과 흐름을 맡고, menu.php 가 메뉴 출력 모양을 맡는다.
main.php
<?php
declare(strict_types=1);
function discountRate(int $quantity): int
{
if ($quantity >= 20) {
return 15;
} elseif ($quantity >= 10) {
return 10;
} elseif ($quantity >= 5) {
return 5;
}
return 0;
}
function orderStatusLabel(string $status): string
{
return match ($status) {
'received', 'paid' => '준비 중',
'baked' => '픽업 가능',
'picked' => '완료',
default => '확인 필요',
};
}
function weekdayNotice(int $day): string
{
switch ($day) {
case 1:
return '월요일은 휴무다';
case 6:
case 7:
return '주말은 오후 2시까지 받는다';
default:
return '평일은 오후 6시까지 받는다';
}
}
$unitPrice = 3500;
echo "== 수량별 할인 ==\n";
foreach ([1, 5, 10, 20] as $quantity) {
$rate = discountRate($quantity);
$total = intdiv($unitPrice * $quantity * (100 - $rate), 100);
echo "{$quantity}개: {$rate}% 할인, {$total}원\n";
}
echo "== 주문 상태 ==\n";
foreach (['received', 'baked', 'picked', 'shipped'] as $status) {
echo $status, ' -> ', orderStatusLabel($status), "\n";
}
echo "== 요일별 안내 ==\n";
for ($day = 1; $day <= 7; $day += 3) {
echo $day, ': ', weekdayNotice($day), "\n";
}
echo "== 접수 ==\n";
$capacity = 12;
$accepted = 0;
foreach ([4, 0, 5, -1, 6, 2] as $quantity) {
if ($quantity <= 0) {
echo "수량 {$quantity}: 건너뜀\n";
continue;
}
if ($accepted + $quantity > $capacity) {
echo "수량 {$quantity}: 한도 초과, 접수 종료\n";
break;
}
$accepted += $quantity;
echo "수량 {$quantity}: 접수, 누계 {$accepted}개\n";
}
echo "접수 합계: {$accepted}개\n";
echo "== 재고 소진 ==\n";
$stock = 10;
$orderNo = 0;
while ($stock >= 4) {
$stock -= 4;
$orderNo++;
echo "주문 {$orderNo}: 4개 판매, 남은 재고 {$stock}개\n";
}
echo "재고 {$stock}개로는 다음 주문을 받을 수 없다\n";
echo "== 번호표 호출 ==\n";
$waiting = 0;
$called = 0;
do {
$called++;
echo "번호표 {$called} 호출\n";
} while ($called < $waiting);
echo "== 메뉴 템플릿 ==\n";
$menuTitle = '오늘의 빵';
$breadNames = ['식빵', '소보로', '단팥빵'];
require __DIR__ . '/menu.php';
$menuTitle = '내일의 빵';
$breadNames = [];
require __DIR__ . '/menu.php';
menu.php
<?php declare(strict_types=1); ?>
<h2><?= htmlspecialchars($menuTitle) ?></h2>
<?php if (count($breadNames) === 0): ?>
<p>오늘은 준비된 빵이 없다</p>
<?php else: ?>
<ul>
<?php foreach ($breadNames as $index => $name): ?>
<li><?= $index + 1 ?>. <?= htmlspecialchars($name) ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
줄별 해설
main.php: 함수 세 개
discountRate(int $quantity): int: 수량을 받아 할인율(퍼센트 정수)을 돌려준다. 큰 기준부터if,elseif로 검사하므로 20개 이상은 첫 갈래에서 끝난다. 어느 조건에도 맞지 않으면 블록을 지나 마지막return 0;이 실행된다.else없이 뒤에return을 두는 방식이다.orderStatusLabel:match식 전체가return의 값이 된다.'received', 'paid'는 한 갈래에 묶였다.default가 있어서'shipped'같은 모르는 값도 예외 없이 "확인 필요" 가 된다.weekdayNotice:switch의case 6:에는 실행문이 없어서 아래case 7:로 이어진다. 6 과 7 이 같은 문구를 쓰게 하는 의도적인 폴스루다. 모든 갈래가return하므로break는 필요 없다.
main.php: 실행 흐름
$unitPrice = 3500;: 빵 한 개의 가격이다.- 첫
foreach: 수량 목록에서 하나씩 꺼내$quantity에 담는다. 합계는단가 x 수량 x (100 - 할인율)을 100 으로 나눈 값이다. 소수점 계산을 피하려고intdiv()로 정수 나눗셈을 했다. 문자열 안의{$변수}는 값으로 치환된다. 한글이 바로 뒤에 붙어도 변수 이름의 끝이 분명하도록 중괄호를 썼다. - 상태
foreach:echo에 쉼표로 여러 값을 이어 적으면 차례로 출력한다. for ($day = 1; $day <= 7; $day += 3):$day는 1, 4, 7 이 된다. 4 는default갈래로 가서 평일 문구가 나온다.- 접수
foreach: 0 과 -1 은 첫if에서continue로 건너뛴다. 4 와 5 는 접수되어 누계가 9 가 된다. 6 은9 + 6 > 12라서break로 반복 자체를 끝낸다. 뒤에 있는 2 는 검사조차 하지 않는다. while ($stock >= 4): 재고 10 에서 4 를 두 번 빼면 2 가 남고, 조건이 거짓이 되어 끝난다. 본문에서$stock을 줄이기 때문에 반드시 끝난다.do-while: 기다리는 사람이 0 명이어도 본문이 한 번 실행되어 번호표 1 을 호출한다. 그다음1 < 0이 거짓이라 끝난다. 이런 성질 때문에 "일단 한 번 하고 판단" 하는 경우에만 쓴다.- 마지막 두
require: 같은 템플릿을 변수 값만 바꿔 두 번 실행한다.
menu.php
- 첫 줄: 템플릿 파일에도
declare(strict_types=1);를 둘 수 있다. 첫 문장이어야 하므로 파일 맨 앞에 쓴다. 뒤의?>와 줄바꿈은 출력되지 않는다. <h2><?= htmlspecialchars($menuTitle) ?></h2>: 제목을 이스케이프해서 출력한다.<?php if (...): ?>...<?php else: ?>...<?php endif; ?>: 목록이 비었을 때와 아닐 때를 나눈다. 제어 줄은?>로 끝나서 출력에 빈 줄을 남기지 않는다.foreach (... as $index => $name):: 자리 번호와 이름을 함께 받는다. 자리 번호는 0 부터라서$index + 1로 사람이 읽는 번호를 만든다.<li>줄은 줄바꿈까지 그대로 출력된다.
실행 결과
두 파일이 있는 디렉터리에서 실행한다.
$ php main.php
== 수량별 할인 ==
1개: 0% 할인, 3500원
5개: 5% 할인, 16625원
10개: 10% 할인, 31500원
20개: 15% 할인, 59500원
== 주문 상태 ==
received -> 준비 중
baked -> 픽업 가능
picked -> 완료
shipped -> 확인 필요
== 요일별 안내 ==
1: 월요일은 휴무다
4: 평일은 오후 6시까지 받는다
7: 주말은 오후 2시까지 받는다
== 접수 ==
수량 4: 접수, 누계 4개
수량 0: 건너뜀
수량 5: 접수, 누계 9개
수량 -1: 건너뜀
수량 6: 한도 초과, 접수 종료
접수 합계: 9개
== 재고 소진 ==
주문 1: 4개 판매, 남은 재고 6개
주문 2: 4개 판매, 남은 재고 2개
재고 2개로는 다음 주문을 받을 수 없다
== 번호표 호출 ==
번호표 1 호출
== 메뉴 템플릿 ==
<h2>오늘의 빵</h2>
<ul>
<li>1. 식빵</li>
<li>2. 소보로</li>
<li>3. 단팥빵</li>
</ul>
<h2>내일의 빵</h2>
<p>오늘은 준비된 빵이 없다</p>
실무에서 자주 틀리는 것
1. switch 에서 break 를 빼먹는다
틀린 코드는 received 를 넣어도 다음 case 로 흘러가서 마지막 대입이 덮어쓴다.
switch ($status) {
case 'received':
$label = '준비 중';
case 'baked':
$label = '픽업 가능';
break;
}
// $status 가 'received' 여도 $label 은 '픽업 가능'
고친 코드는 값을 고르는 일이므로 match 로 바꾼다. 폴스루 자체가 없다.
$label = match ($status) {
'received' => '준비 중',
'baked' => '픽업 가능',
default => '확인 필요',
};
2. match 에 default 를 두지 않는다
틀린 코드는 정해 둔 값만 들어온다고 믿고 default 를 뺐다. 외부에서 온 값이 목록에 없으면 UnhandledMatchError 로 프로그램이 멈춘다.
$label = match ($status) {
'received' => '준비 중',
'baked' => '픽업 가능',
};
// $status 가 'shipped' 이면 예외 발생
고친 코드는 처리할 수 없는 값의 응답을 default 로 정한다. 값이 정말 그 목록 안에만 있어야 하는 곳이라면 일부러 default 를 빼서 예외로 알아차리는 방법도 있다. 다만 사용자 입력이 닿는 곳에서는 default 를 두는 쪽이 낫다.
$label = match ($status) {
'received' => '준비 중',
'baked' => '픽업 가능',
default => '확인 필요',
};
3. if 조건에서 = 를 쓴다
= 는 대입이다. 조건 안에서 대입하면 대입한 값이 그대로 조건의 결과가 되어서, 5 는 참으로 취급되므로 항상 블록이 실행된다. 게다가 $quantity 값이 덮어써진다.
if ($quantity = 5) {
echo '5개 주문'; // 수량이 얼마든 실행된다
}
고친 코드는 비교 연산자를 쓴다. 엄격한 비교인 === 가 앞 장에서 배운 안전한 기본값이다.
if ($quantity === 5) {
echo '5개 주문';
}
4. 대체 문법에서 else if 를 두 단어로 쓴다
중괄호 문법에서는 else if 도 동작하지만, 콜론을 쓰는 대체 문법에서는 문법 오류(parse error)가 난다.
<?php if ($count > 0): ?>
<p>주문이 있다</p>
<?php else if ($count < 0): ?>
<p>수량 오류</p>
<?php endif; ?>
고친 코드는 한 단어인 elseif 를 쓴다. 습관적으로 항상 elseif 로 쓰면 두 문법 모두에서 안전하다.
<?php if ($count > 0): ?>
<p>주문이 있다</p>
<?php elseif ($count < 0): ?>
<p>수량 오류</p>
<?php endif; ?>
한눈에 보기
| 구조 | 한 줄 요약 | 주의할 점 |
|---|---|---|
if / elseif / else | 조건에 따라 코드 블록을 고른다 | 위에서부터 검사, 조건 순서가 결과를 바꾼다 |
switch | 값 하나를 여러 case 와 == 로 비교한다 | 느슨한 비교, break 를 빼면 아래로 흐른다 |
match | 값을 === 로 비교해 결과 값을 돌려주는 식 | 맞는 갈래가 없으면 예외, default 로 대비 |
for | 횟수나 범위가 정해진 반복 | 조건과 갱신을 헷갈리면 한 번 더 돌거나 덜 돈다 |
foreach | 목록의 원소를 차례로 훑는다 | 자리 번호는 0 부터 시작한다 |
while / do-while | 조건이 참인 동안 반복한다 | 조건에 쓰이는 값을 본문에서 바꿔야 끝난다 |
break / continue | 반복을 끝내거나 이번 회차를 건너뛴다 | 가장 안쪽 반복에만 적용된다 |
| 대체 문법 | 콜론과 end... 로 HTML 사이에서 읽기 쉽게 쓴다 | elseif 는 한 단어로 쓴다 |
연습 문제
- 시각(0 에서 23 사이의 정수)을 받아 10시 전이면 "오픈 전", 10시 이상 18시 미만이면 "영업 중", 그 밖에는 "마감" 을 돌려주는 함수
shopState(int $hour): string을match (true)로 작성하라. - 1 부터 10 까지 반복하면서 3 의 배수는 건너뛰고 나머지 수를 모두 더한 합계를 출력하는 코드를
for와continue로 작성하라. - 식빵 반죽이 1000g 있고 빵 한 개에 120g 이 든다.
while로 만들 수 있는 빵의 개수와 남는 반죽의 양을 구하라. 나눗셈 연산자는 쓰지 않는다. - 템플릿 파일에서
for의 대체 문법으로 1 부터 3 까지의 번호를<ol>목록의 항목으로 출력하는 코드를 작성하라. 항목 내용은 "예약 1번" 형태로 한다.
정답과 해설
1번
function shopState(int $hour): string
{
return match (true) {
$hour < 10 => '오픈 전',
$hour < 18 => '영업 중',
default => '마감',
};
}
match (true) 는 위에서부터 조건식의 결과가 true 인 첫 갈래를 고른다. 두 번째 갈래에 도달했다는 것은 이미 10시 이상이라는 뜻이므로 $hour >= 10 을 다시 쓰지 않아도 된다. 예를 들어 9 는 "오픈 전", 14 는 "영업 중", 20 은 "마감" 이다.
2번
$sum = 0;
for ($i = 1; $i <= 10; $i++) {
if ($i % 3 === 0) {
continue;
}
$sum += $i;
}
echo $sum, "\n";
% 는 나머지 연산자다. 3 의 배수(3, 6, 9)는 continue 로 건너뛰므로 1 + 2 + 4 + 5 + 7 + 8 + 10 이 더해져 37 이 출력된다.
3번
$dough = 1000;
$count = 0;
while ($dough >= 120) {
$dough -= 120;
$count++;
}
echo "{$count}개, 남은 반죽 {$dough}g\n";
반죽이 120g 이상인 동안 120 을 빼고 개수를 센다. 8번 반복하면 960g 을 쓰고 40g 이 남아서 조건이 거짓이 된다. 출력은 8개, 남은 반죽 40g 이다. 조건이 > 120 이면 반죽이 정확히 120g 남았을 때 하나를 만들지 못하므로, 경계값에서 >= 와 > 를 구분해야 한다.
4번
<ol>
<?php for ($n = 1; $n <= 3; $n++): ?>
<li>예약 <?= $n ?>번</li>
<?php endfor; ?>
</ol>
대체 문법에서는 for (...): 로 열고 endfor; 로 닫는다. 제어 줄이 ?> 로 끝나므로 빈 줄 없이 <li> 세 줄이 출력된다.