PHP / $_GET, $_POST - 양식(form)으로 전송된 데이터 받기

HTML 양식의 method가 get이면 URL의 쿼리 문자열을 PHP의 $_GET으로, post이면 일반적인 양식 본문을 $_POST로 받습니다. 입력 요소의 name이 배열의 키가 되며, 받은 값은 누락 여부와 형식을 검사한 뒤 사용해야 합니다. HTML 화면에 출력할 때는 htmlspecialchars()로 이스케이프합니다.

GET·POST를 선택하는 기준과 name의 역할

GET은 검색어·필터처럼 결과 주소를 공유하거나 다시 열어도 되는 조회에 적합합니다. POST는 등록·수정처럼 서버 상태를 바꾸는 요청 등에 사용합니다. POST로 보냈다는 사실만으로 데이터가 암호화되지는 않으므로 실제 서비스의 민감한 정보는 HTTPS와 적절한 접근 제어로 보호해야 합니다. 전송 방식은 MDN의 양식 전송 설명을 참고하세요.

항목 GET 양식 POST 양식
입력 값의 일반적인 위치 URL의 ? 뒤 쿼리 문자열 요청 본문
PHP에서 읽기 $_GET['color'] $_POST['color']
주소 공유 쿼리가 포함된 결과 주소를 공유 가능 주소만으로 요청 본문까지 재현되지 않음

form의 action은 받을 파일, method는 요청 방식을 지정합니다. input의 id는 label 연결 등에 사용하고, 서버로 전송할 키는 name으로 정합니다. name이 없거나 비활성화된 입력, 선택하지 않은 체크박스 등은 값이 오지 않을 수 있으므로 모든 키가 반드시 존재한다고 가정하면 안 됩니다. PHP의 양식 처리 안내에서 기본 연결 구조를 확인할 수 있습니다.

$_GET은 요청 방식의 이름만 보고 채워지는 배열이 아닙니다. POST 요청의 주소에도 ?page=2 같은 쿼리가 있으면 그 값은 $_GET에 들어갑니다. 입력 출처를 구분해야 할 때는 $_REQUEST로 합쳐 읽기보다 $_GET과 $_POST를 명확히 선택하세요.

GET 예제: 한 파일에서 양식과 결과 표시

PHP 8.x의 명령행 실행 파일이 설치되어 있고 터미널에서 php -v가 동작해야 합니다. 새 실습 폴더에 다음 전체 코드를 UTF-8의 get-form.php로 저장합니다. 별도 데이터베이스나 확장 모듈은 필요하지 않습니다. 아래 명령은 두 예제를 모두 저장한 뒤 해당 폴더에서 한 번 실행하세요.

php -S 127.0.0.1:8000

브라우저에서 http://127.0.0.1:8000/get-form.php를 엽니다. 파일을 더블 클릭해 file:// 주소로 열면 PHP가 실행되지 않습니다. 개발용 PHP 내장 웹 서버는 로컬 실습에 사용하고, 종료할 때는 터미널에서 Ctrl+C를 누릅니다. 포트가 사용 중이면 8001처럼 다른 번호로 바꾸고 주소에도 같은 번호를 적으세요.

<?php
declare(strict_types=1);
header('Content-Type: text/html; charset=UTF-8');
$raw = $_GET['color'] ?? '';
$error = '';
$color = '';
if (!is_string($raw)) {
    http_response_code(422);
    $error = '색상은 하나의 문자열로 입력하세요.';
} else {
    $color = trim($raw);
    if (strlen($color) > 120) {
        http_response_code(422);
        $error = '색상은 UTF-8 기준 120바이트 이내로 입력하세요.';
    }
}
function e(string $value): string {
    return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
?>
<!doctype html>
<html lang="ko">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>GET 색상 확인</title></head>
<body>
<form method="get" action="get-form.php">
  <label>좋아하는 색상 <input name="color" value="<?= e($color) ?>"></label>
  <button type="submit">확인</button>
</form>
<?php if ($error !== ''): ?>
<p><?= e($error) ?></p>
<?php elseif ($color === ''): ?>
<p>색상을 입력하세요.</p>
<?php else: ?>
<p>입력한 색상: <?= e($color) ?></p>
<?php endif; ?>
</body>
</html>

처음에는 “색상을 입력하세요.”가 표시됩니다. blue를 입력하고 확인하면 주소가 get-form.php?color=blue로 바뀌고 “입력한 색상: blue”가 나옵니다. 한글이나 공백은 주소에서 퍼센트 인코딩될 수 있지만 PHP는 쿼리를 해석해 문자열로 제공합니다. 이미 해석된 $_GET 값에 urldecode()를 다시 적용하지 마세요. 자세한 범위는 $_GET 문서에 설명되어 있습니다.

??는 color 키가 없을 때 빈 문자열을 쓰므로 직접 접속해도 키 누락 경고가 나지 않게 합니다. 다만 color[]=blue처럼 배열을 보내는 요청까지 문자열로 바꾸어 주지는 않습니다. is_string()으로 먼저 확인하고, 잘못된 형식은 HTTP 422로 거절합니다. 문자열 "0"도 유효한 입력이므로 empty()만으로 입력 여부를 판단하지 않았습니다.

POST 예제: 두 값을 읽고 검증한 뒤 표시

같은 폴더에 다음 코드를 post-form.php로 저장하고 http://127.0.0.1:8000/post-form.php로 접속합니다. 이 예제는 받은 값을 현재 응답에 보여 주기만 하며 파일·DB에 저장하거나 계정 정보를 변경하지 않습니다.

<?php
declare(strict_types=1);
header('Content-Type: text/html; charset=UTF-8');
$submitted = ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST';
$color = '';
$sport = '';
$error = '';
if ($submitted) {
    $rawColor = $_POST['color'] ?? '';
    $rawSport = $_POST['sport'] ?? '';
    if (!is_string($rawColor) || !is_string($rawSport)) {
        http_response_code(422);
        $error = '색상과 운동은 각각 하나의 문자열로 입력하세요.';
    } else {
        $color = trim($rawColor);
        $sport = trim($rawSport);
        if ($color === '' || $sport === '' || strlen($color) > 120 || strlen($sport) > 120) {
            http_response_code(422);
            $error = '두 항목을 모두 UTF-8 기준 120바이트 이내로 입력하세요.';
        }
    }
}
function e(string $value): string {
    return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
?>
<!doctype html>
<html lang="ko">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>POST 입력 확인</title></head>
<body>
<form method="post" action="post-form.php">
  <p><label>좋아하는 색상 <input name="color" value="<?= e($color) ?>" required></label></p>
  <p><label>좋아하는 운동 <input name="sport" value="<?= e($sport) ?>" required></label></p>
  <button type="submit">입력 확인</button>
</form>
<?php if ($error !== ''): ?>
<p><?= e($error) ?></p>
<?php elseif ($submitted): ?>
<p>색상: <?= e($color) ?> / 운동: <?= e($sport) ?></p>
<?php endif; ?>
</body>
</html>

색상에 파랑, 운동에 수영을 입력하면 결과 문단에 “색상: 파랑 / 운동: 수영”이 표시되고 주소에는 두 양식 값이 붙지 않습니다. 개발자 도구의 Network에서 해당 요청의 Method가 POST인지와 Payload의 Form Data에 color·sport가 있는지 확인할 수 있습니다. 같은 동작을 GET 예제와 비교하면 값이 실리는 위치의 차이가 보입니다.

required는 브라우저의 입력 도움 기능이므로 서버 검증을 대신하지 않습니다. 코드는 직접 보낸 요청도 고려해 두 항목의 존재·문자열 여부·빈 값·최대 바이트 수를 검사합니다. strlen()의 120은 문자 수가 아닌 바이트 수입니다. 한글 글자 수를 기준으로 제한하려면 mbstring 확장과 mb_strlen() 등을 사용해 정책을 바꿔야 합니다.

단순 입력 미리보기여서 새로고침하면 브라우저가 POST 재전송을 물을 수 있습니다. 실제 등록 기능을 만들 때는 검증과 저장에 더해 CSRF 방어, 권한 확인, 중복 처리 방지와 저장 후 리디렉션을 설계해야 합니다. 이 미리보기 코드에 저장 쿼리만 덧붙여 운영용 등록 양식으로 간주하지 마세요.

값이 없거나 예상한 배열에 들어오지 않을 때

  • 키 이름은 대소문자를 구분합니다. name="color"와 $_POST['Color']는 다르며, name을 id로 잘못 적지 않았는지 확인합니다.
  • POST는 요청 본문 형식도 확인합니다. 일반 양식의 application/x-www-form-urlencoded, multipart/form-data는 $_POST로 처리되지만 JSON 본문은 자동으로 들어가지 않습니다. JSON을 받는 별도 API는 php://input을 읽고 JSON 파싱과 검증을 수행해야 합니다.
  • 파일 업로드 내용은 $_POST의 일반 문자열이 아니라 $_FILES에서 처리합니다. multipart/form-data 양식 설정과 업로드 오류 검사도 별도로 필요합니다.
  • 입력 전체가 사라졌다면 post_max_size 같은 서버 제한과 실제 요청 크기를 확인합니다. 여러 입력이 일부만 들어오는 상황에서는 max_input_vars 등의 설정도 확인하되 제한값을 무조건 늘리지는 않습니다.

본문 형식의 범위는 $_POST 문서, 업로드 배열은 $_FILES 문서에서 확인할 수 있습니다. 브라우저 주소만 보지 말고 요청 방식·Content-Type·전송된 name을 함께 확인하면 원인을 좁힐 수 있습니다.

검증과 HTML 출력 이스케이프를 구분하기

검증은 값이 업무 규칙에 맞는지 판단하고, 이스케이프는 특정 출력 문맥에서 값이 코드로 해석되지 않게 표현합니다. 예제의 e()는 HTML 본문과 큰따옴표로 감싼 value 속성에 사용할 문자열을 htmlspecialchars()로 처리합니다. 원본 값을 HTML로 직접 echo하지 마세요.

예를 들어 색상에 <b>파랑</b>을 입력하면 굵은 글씨 태그로 적용되지 않고 해당 문자열이 화면과 입력 칸에 그대로 보여야 합니다. 따옴표도 속성 경계를 깨지 않아야 합니다. ENT_QUOTES와 UTF-8을 명시한 이유는 htmlspecialchars()의 변환 규칙을 참고하세요.

이 함수가 모든 문맥의 안전을 보장하지는 않습니다. SQL은 매개변수 바인딩, JavaScript·URL은 각 문맥에 맞는 처리가 필요합니다. HTML용으로 바꾼 문자열을 DB 저장 전의 범용 정제 값으로 삼지 말고, 검증한 원래 값을 목적에 맞게 처리한 뒤 실제 출력 지점에서 이스케이프하세요.