Menu

PHP 폼 처리: 전송, 검증, 다시 표시하기

PHP 폼은 <form method="post">로 자기 자신에게 전송되고, 스크립트는 $_SERVER['REQUEST_METHOD']가 'POST'일 때 $_POST['name']에서 필드를 읽습니다. 검증, 필드 옆 에러 메시지, 입력값 유지, 체크박스와 select, 출력 이스케이프를 알아봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

PHP 폼은 자신을 출력한 스크립트로 다시 전송됩니다. 이름 있는 입력과 함께 <form method="post">를 쓰고, $_SERVER['REQUEST_METHOD']가 'POST'일 때 각 필드를 $_POST['name']으로 읽으세요. 다시 출력하는 것은 모두 htmlspecialchars()로 이스케이프하세요.

이 블록을 실행하고 폼에 이름을 입력한 뒤 Send를 누르세요. 이번에는 $_POST가 채워진 채로 같은 스크립트가 다시 실행됩니다.

여기서 모든 폼 핸들러에 필요한 세 가지가 일어납니다.

  • 메서드 확인. 첫 방문에서 요청은 GET이고 처리할 것이 없습니다. ?? 'GET'은 REQUEST_METHOD가 설정되지 않는 명령줄에서 파일을 실행할 때만 의미가 있으며, 웹 서버에서는 항상 설정됩니다.
  • 모든 필드에 ?? ''. 필드는 없을 수 있고(이름이 바뀐 입력, 스크립트가 보낸 요청), 없는 키를 읽으면 경고가 출력됩니다.
  • 출력 이스케이프. 이름이 페이지에 두 번 다시 들어가며, htmlspecialchars()는 <b>Ada</b> 같은 값이 마크업이 되지 않고 텍스트로 보이게 합니다. 자세한 내용은 htmlspecialchars 페이지에 있습니다.

폼을 검증하고 필드 옆에 에러 보여 주기

필드 이름을 키로 하는 배열에 에러를 모으세요. 배열이 비어 있으면 폼이 유효하고, 아니면 각 메시지를 해당 필드 아래에 두고 방문자의 입력을 그대로 유지한 채 폼을 다시 출력합니다. 빈 채로, 잘못된 이메일로, 유효한 값으로 차례로 전송해 보세요.

novalidate는 브라우저 자체 검사를 꺼서 PHP 검사가 동작하는 것을 볼 수 있게 합니다. 실제 폼에서는 둘 다 유지하세요. 브라우저 검증은 방문자를 위한 편의이고, 누구나 폼 없이 요청을 보낼 수 있으므로 중요한 것은 PHP 검증입니다. filter_var()는 이메일과 정수 범위를 각각 한 번의 호출로 처리합니다. 다른 필터는 filter_var 페이지에서 다룹니다.

체크박스, 라디오 버튼, select

텍스트 필드는 빈 값이라도 항상 값을 보냅니다. 다른 컨트롤은 그렇지 않습니다.

  • 체크되지 않은 체크박스는 아무것도 보내지 않으므로 그 키가 $_POST에 없습니다.
  • topics[]라는 이름의 체크박스는 배열로 도착합니다: $_POST['topics']는 ['php', 'sql']입니다.
  • 라디오 그룹이나 <select>는 선택한 옵션의 value를 보냅니다.

전송 후에도 선택을 유지하려면 일치하는 옵션에 checked나 selected를 출력하세요. 상자 몇 개를 체크하고, 수준을 고르고, Save를 누르세요.

입력을 읽은 뒤의 두 확인은 보기보다 중요합니다. array_intersect()는 목록에 없던 주제를 버리고, is_string()과 array_key_exists() 확인은 제공한 적 없는 값(또는 $levels[$level]이 예외를 던지게 만드는 배열)을 누군가 보내면 수준을 기본값으로 되돌립니다. select는 브라우저가 보낼 수 있는 것을 제한할 뿐, 요청에 담길 수 있는 것을 제한하지 않습니다.

hidden 필드로 여러 단계 폼 만들기

hidden 입력은 값을 보여 주지 않고 한 단계에서 다음 단계로 전달합니다. 여기서 1단계는 이름을 묻고, 2단계는 언어를 묻으면서 이름을 hidden 필드로 함께 보내며, 3단계는 둘 다 보여 줍니다. 세 단계를 모두 진행해 보세요.

hidden 필드는 페이지 소스에서 보이고 다른 필드처럼 수정할 수 있으므로, 방문자 자신의 답을 단계 사이에 전달하는 데는 괜찮지만 가격, 사용자 id, 역할에는 절대 쓰면 안 됩니다. 그런 것은 서버의 세션에 두세요. 세션과 쿠키를 참고하세요.

Post/Redirect/Get: 새로고침 시 재전송 막기

POST가 성공한 뒤 스크립트가 "감사합니다" 페이지를 바로 출력하면, 새로고침을 누를 때 브라우저가 POST를 다시 보내 주문이나 댓글이 두 번 저장됩니다. 해결 방법은 저장하고, 리다이렉트하고, 결과를 GET으로 보여 주는 것입니다.

<?php
session_start();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $errors = validate($_POST);
    if (!$errors) {
        save_comment($_POST);
        $_SESSION['flash'] = 'Comment posted.';
        header('Location: /comments.php', true, 303);
        exit;
    }
    // On errors, fall through and print the form with the messages.
}

$flash = $_SESSION['flash'] ?? null;
unset($_SESSION['flash']);

303 See Other는 브라우저가 리다이렉트를 GET으로 따라가게 합니다. header() 뒤의 exit이 중요합니다. 없으면 스크립트의 나머지가 계속 실행됩니다. 리다이렉트는 출력보다 먼저 일어나야 하며, 그렇지 않으면 PHP가 "headers already sent"와 함께 거부합니다.

실제 폼은 CSRF 토큰도 추가합니다. 세션과 hidden 필드에 저장한 무작위 값을 전송 시 hash_equals()로 비교해, 다른 사이트가 로그인한 방문자를 대신해 폼을 전송하지 못하게 합니다.

브라우저 없이 검증 테스트하기

규칙을 배열을 받아 에러를 반환하는 함수에 넣으세요. 페이지는 $_POST로 그것을 호출하고, 여러분은 원하는 어떤 배열로든 호출할 수 있으므로 경계 사례를 쉽게 시도할 수 있습니다. 목록에 직접 사례를 추가해 보세요.

네 번째 사례는 사람들이 잊는 것입니다. 요청은 name[]=x를 보낼 수 있고, 그러면 $_POST['name']이 배열이 되며, 배열을 trim()에 넘기면 TypeError가 나서 스크립트가 멈춥니다. 먼저 is_string()을 확인하면 악의적인 입력이 평범한 검증 에러가 됩니다. 앞의 블록들의 (string) 형 변환도 TypeError를 피하지만, 배열을 Array라는 단어로 바꾸고 경고를 출력하므로 is_string()이 더 깔끔한 확인입니다.

흔한 실수: 폼이 전송되기 전에 $_POST 읽기

가장 흔한 첫 폼 스크립트는 파일 맨 위에서 필드를 읽습니다.

<?php
$name = $_POST['name'];
echo "Hello, $name";

첫 방문에는 아무것도 전송되지 않았으므로 PHP가 다음을 출력합니다.

Warning: Undefined array key "name" in /var/www/form.php on line 2

해결 방법은 첫 블록의 패턴입니다. 요청이 POST일 때만 폼을 처리하고, 모든 필드를 기본값과 함께 읽으세요: $_POST['name'] ?? ''. 전송 후에 경고가 나타난다면 HTML의 name 속성과 PHP의 키를 비교하세요. name="Name"과 $_POST['name']은 서로 다른 키입니다.

자주 묻는 질문

PHP 폼을 같은 페이지로 전송하려면 어떻게 하나요?

action 없이(또는 action="") <form method="post">를 쓰세요. 브라우저가 현재 URL로 다시 전송하므로 같은 스크립트가 폼을 보여 주고 처리합니다. $_SERVER['REQUEST_METHOD'] === 'POST'를 확인하고 $_POST에서 필드를 읽으세요.

PHP에서 전송 후에도 폼 값을 유지하려면 어떻게 하나요?

전송된 값을 이스케이프해서 필드에 다시 출력하세요: <input name="email" value="<?= htmlspecialchars($_POST['email'] ?? '') ?>">. 체크박스는 checked를, select의 옵션은 selected를 전송된 값이 일치할 때 출력하세요.

PHP에서 여러 체크박스의 값을 얻으려면 어떻게 하나요?

체크박스들에 대괄호로 끝나는 같은 이름을 주세요: name="topics[]". 그러면 PHP가 체크된 값을 배열 $_POST['topics']에 넣습니다. 체크되지 않은 상자는 아무것도 보내지 않으므로 $_POST['topics'] ?? []를 쓰고, 모든 값을 제공한 목록과 대조하세요.

페이지를 새로고침하면 PHP 폼이 다시 전송되는 이유는 무엇인가요?

마지막 요청이 POST였으므로 브라우저가 새로고침 때 그것을 반복합니다. 전송이 성공한 뒤 header('Location: /thanks.php'); exit;로 리다이렉트하세요(Post/Redirect/Get 패턴). 그러면 새로고침은 무해한 GET을 반복합니다.

PHP에서 $_POST를 그대로 써도 안전한가요?

아니요. $_POST의 모든 값은 방문자에게서 오며 HTML, SQL, 문자열을 기대한 곳의 배열 등 무엇이든 될 수 있습니다. 들어올 때 검증하고(filter_var, 길이와 허용 목록 확인), 나갈 때 이스케이프하세요(HTML에는 htmlspecialchars, SQL에는 prepared statement).

Coddy 프로그래밍 언어 일러스트

Coddy로 코딩 배우기

시작하기