PHP / Session / 세션 값 출력하는 방법
PHP 세션에 저장한 값은 session_start()로 기존 세션을 연 뒤 $_SESSION 배열에서 읽습니다. 문자열을 HTML에 표시할 때는 htmlspecialchars()로 이스케이프하고, 아직 저장하지 않은 키에는 기본값을 적용합니다. 세션 ID나 전체 세션 내용을 공개 화면에 그대로 출력하지 말고 필요한 항목만 선택하세요.
session_start()와 $_SESSION의 관계
세션은 여러 HTTP 요청에 걸쳐 서버 쪽 상태를 이어 주는 기능입니다. 일반적인 쿠키 기반 구성에서는 브라우저가 세션 ID를 보내고 PHP가 그 ID에 대응하는 저장 데이터를 읽습니다. session_start()는 새 세션을 만들거나 기존 세션을 재개하며, 저장된 값을 $_SESSION에 채웁니다. 호출할 때마다 모든 값을 초기화하는 함수는 아닙니다.
쿠키와 응답 헤더를 보내야 하므로 HTML·echo·공백 출력 전에 호출해야 합니다. 파일은 UTF-8 BOM 없이 저장하고 <?php 앞에 빈 줄을 두지 마세요. 읽는 페이지에서도 같은 세션 설정으로 세션을 시작해야 합니다. 세션에 값을 저장하지 않은 새 브라우저에서는 해당 키가 없을 수 있습니다.
한 파일로 저장·출력·지우기를 실습하기
PHP 8.x와 세션 저장소에 쓸 수 있는 권한이 필요합니다. 별도 DB는 사용하지 않습니다. 새 실습 폴더에 아래 전체 코드를 session-demo.php로 저장합니다. 예제는 실습 이름 하나를 저장하는 기능이며 로그인·권한 부여 기능은 아닙니다.
<?php
declare(strict_types=1);
ini_set('session.use_strict_mode', '1');
ini_set('session.use_only_cookies', '1');
ini_set('session.use_trans_sid', '0');
session_name('CFSESSDEMO');
$https = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' && $_SERVER['HTTPS'] !== '';
session_set_cookie_params([
'lifetime' => 0, 'path' => '/', 'secure' => $https,
'httponly' => true, 'samesite' => 'Lax',
]);
if (!session_start()) {
http_response_code(500);
exit('세션을 시작하지 못했습니다.');
}
header('Content-Type: text/html; charset=UTF-8');
header('Cache-Control: no-store');
if (!isset($_SESSION['demo_csrf']) || !is_string($_SESSION['demo_csrf'])) {
$_SESSION['demo_csrf'] = bin2hex(random_bytes(32));
}
$error = '';
if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST') {
$token = $_POST['csrf'] ?? null;
if (!is_string($token) || !hash_equals($_SESSION['demo_csrf'], $token)) {
http_response_code(403);
exit('입력 양식을 다시 열어 주세요.');
}
$action = $_POST['action'] ?? '';
if ($action === 'clear') {
unset($_SESSION['demo_name']);
} elseif ($action === 'save') {
$raw = $_POST['name'] ?? null;
if (!is_string($raw) || trim($raw) === '' || strlen(trim($raw)) > 120) {
http_response_code(422);
$error = '이름은 UTF-8 기준 1~120바이트의 문자열로 입력하세요.';
} else {
$_SESSION['demo_name'] = trim($raw);
}
} else {
http_response_code(400);
exit('지원하지 않는 작업입니다.');
}
if ($error === '') {
session_write_close();
header('Location: session-demo.php', true, 303);
exit;
}
}
$name = $_SESSION['demo_name'] ?? null;
$name = is_string($name) ? $name : null;
$csrf = $_SESSION['demo_csrf'];
session_write_close();
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>세션 값 확인</title></head>
<body>
<p>저장된 이름: <?= $name === null ? '없음' : e($name) ?></p>
<?php if ($error !== ''): ?><p><?= e($error) ?></p><?php endif; ?>
<form method="post" action="session-demo.php">
<input type="hidden" name="csrf" value="<?= e($csrf) ?>">
<label>실습용 이름 <input name="name" value="<?= e($name ?? '') ?>"></label>
<button name="action" value="save">저장</button>
<button name="action" value="clear">실습 값 지우기</button>
</form>
<p>실습 항목의 배열 표현</p>
<pre><?= e(var_export(['demo_name' => $name], true)) ?></pre>
</body>
</html>
php -S 127.0.0.1:8000
해당 폴더에서 명령을 실행하고 브라우저로 http://127.0.0.1:8000/session-demo.php를 엽니다. 파일을 직접 더블 클릭하는 file:// 방식은 PHP를 실행하지 않습니다. 포트가 사용 중이면 다른 번호로 바꿔 주소에도 반영하고, 실습이 끝나면 터미널에서 Ctrl+C로 종료합니다.
- 처음에는 “저장된 이름: 없음”과 배열의 NULL 값이 표시됩니다.
- 실습용 이름에 민수를 입력하고 저장합니다. 응답이 303으로 같은 페이지에 이동한 뒤 “저장된 이름: 민수”와 배열의 demo_name 값이 표시됩니다.
- 같은 브라우저에서 새로고침합니다. 입력을 다시 저장하지 않아도 민수가 유지되어야 합니다. 일반적으로 같은 브라우저의 다른 탭도 같은 세션 쿠키를 공유합니다.
- 다른 쿠키 저장소를 사용하는 시크릿 창에서 같은 주소를 열면 별도 세션이므로 저장된 이름은 없음으로 시작합니다.
- 실습 값 지우기를 누르면 demo_name만 없어지고 화면은 처음 상태로 돌아옵니다.
저장·지우기는 POST로 받고, CSRF 토큰으로 이 세션에서 연 양식인지 확인합니다. 성공 후 리디렉션하므로 결과 페이지를 새로고침할 때 저장 요청을 반복하지 않습니다. 세션 이름 CFSESSDEMO는 다른 실습과 구분하기 위한 것이며 기존 애플리케이션에서 임의로 이름을 바꾸면 기존 쿠키와 연결되지 않습니다.
개별 값과 배열을 출력하는 부분 이해하기
$_SESSION['demo_name'] ?? null은 키가 없거나 값이 null이면 null을 사용합니다. 예제에서는 값이 실제 문자열인지도 검사하고, 없으면 “없음”으로 표시합니다. 빈 문자열·숫자 0·문자열 "0"을 모두 없음으로 취급하는 empty()와는 목적이 다릅니다. null 자체와 키 부재를 구분해야 하는 데이터라면 array_key_exists() 등의 기준도 선택해야 합니다.
e()는 HTML 본문과 따옴표로 감싼 입력 속성에 사용할 값을 htmlspecialchars()로 바꿉니다. 이름에 <b>민수</b>를 넣어도 태그가 적용되지 않고 문자열로 보여야 합니다. 세션에 저장한 값이라는 이유만으로 HTML에 직접 출력해도 안전한 것은 아닙니다.
하단의 배열 표시는 $_SESSION 전체가 아니라 demo_name만 복사한 작은 배열입니다. var_export(..., true)가 만든 문자열을 다시 e()로 처리해 태그·따옴표가 HTML로 해석되지 않게 합니다. var_dump($_SESSION)나 session_id()를 공개 페이지에 출력하면 다른 중요한 값이나 세션 식별자가 노출될 수 있으므로 진단에 필요한 항목만 선택하세요.
페이지를 이동하거나 새로고침하면 값이 사라질 때
- 두 요청의 주소가 같은 호스트인지 확인합니다. localhost와 127.0.0.1은 쿠키를 같은 호스트로 취급하지 않습니다. HTTP·HTTPS 전환과 쿠키 path·domain·이름 차이도 확인합니다.
- 개발자 도구에서 CFSESSDEMO 쿠키가 생성되고 다음 요청에도 전송되는지 확인합니다. 값을 답변·화면 캡처에 공개할 필요는 없습니다. 쿠키 차단 설정이나 별도 브라우저 프로필이면 다른 세션이 됩니다.
- session_start()가 성공하는지와 서버 오류 로그를 확인합니다. 파일 저장 방식이라면 session.save_path의 존재·권한·디스크 상태를 점검합니다. 저장소를 확인하지 않고 매번 기본값을 덮어쓰면 원인을 숨길 수 있습니다.
- PHP 파일 앞의 BOM·공백, include한 파일의 출력 때문에 headers already sent가 발생하지 않는지 확인합니다. 출력 버퍼를 켜서 경고만 숨기기보다 최초 출력 위치를 바로잡습니다.
- 여러 서버를 사용하는 환경에서는 요청마다 같은 세션 저장소에 접근하는지 확인합니다. 실습의 한 서버 파일 저장과 분산 운영 환경은 구성이 다를 수 있습니다.
예제의 session_set_cookie_params()는 세션 시작 전에 쿠키 속성을 정합니다. 로컬 HTTP에서는 Secure가 꺼지고, PHP가 HTTPS 요청으로 인식하면 켜집니다. 실제 서비스는 HTTPS를 사용하고 TLS 종료 프록시 환경에서는 신뢰할 수 있는 서버 설정으로 HTTPS 인식과 Secure 쿠키를 보장해야 합니다. 클라이언트가 보낸 임의 헤더를 그대로 믿어 판정하지 마세요. 관련 설정은 PHP 세션 보안 설정을 참고하세요.
값 삭제와 세션 쓰기 종료를 구분하기
unset($_SESSION['demo_name'])은 이름 항목만 제거합니다. 세션 전체를 없애는 로그아웃과 다르며, 이 실습에서는 CSRF 토큰 같은 다른 항목을 보존합니다. $_SESSION 변수 자체를 unset하지 마세요. 전체 로그아웃은 배열 정리, 세션 쿠키와 서버 저장 데이터 처리 등을 함께 고려해야 하며 session_destroy()만 호출하면 모든 쿠키와 현재 변수가 자동으로 사라지는 것은 아닙니다.
예제는 변경 후 session_write_close()로 데이터를 쓰고 세션 처리를 닫습니다. 화면에 사용할 이름과 CSRF 토큰도 닫기 전에 별도 변수로 읽어 둡니다. 쓰기를 닫은 뒤 $_SESSION 배열만 수정해도 그 변경이 자동 저장되지는 않습니다. 값 저장이 확인되었는지는 다음 요청에서 다시 읽어 비교해야 합니다.
파일 기반 세션은 같은 세션의 동시 요청을 잠그는 경우가 있으므로 세션 처리를 오래 잡고 있는 작업도 점검해야 합니다. 순수 조회만 할 때는 session_start()의 read_and_close 옵션을 검토할 수 있지만, 예제처럼 토큰이나 이름을 새로 저장하는 요청에는 그대로 적용하면 안 됩니다.




