
1. [Top 1] isset(), empty(), is_null(): 변수 검증의 삼각 편대
변수의 존재와 상태를 확인하는 것은 에러 방지의 기초 중의 기초입니다. 주니어 시절 가장 많이 실수하는 것이 isset()과 empty()를 혼동하는 것인데, 실무에서는 is_null()까지 포함하여 정확히 구분해 사용해야 합니다.
변수 상태별 반환값 비교 (Markdown Table)
값 ($var)isset($var)empty($var)is_null($var)if ($var)
|
$var = 1;
|
TRUE
|
FALSE
|
FALSE
|
TRUE
|
|
$var = "";
|
TRUE
|
TRUE
|
FALSE
|
FALSE
|
|
$var = "0";
|
TRUE
|
TRUE
|
FALSE
|
FALSE
|
|
$var = 0;
|
TRUE
|
TRUE
|
FALSE
|
FALSE
|
|
$var = NULL;
|
FALSE
|
TRUE
|
TRUE
|
FALSE
|
|
$var = array();
|
TRUE
|
TRUE
|
FALSE
|
FALSE
|
|
정의되지 않음
|
FALSE
|
TRUE
|
TRUE (Warning)
|
FALSE
|
시니어의 팁: $_POST나 $_GET 데이터에 접근할 때 isset()으로 존재 여부를 먼저 확인하지 않으면 Undefined variable 노티스가 발생하며, 이는 보안상 시스템 구조를 노출하는 단초가 될 수 있습니다.
--------------------------------------------------------------------------------
2. [Top 2] htmlspecialchars(): XSS 방어의 최전선
사용자가 입력한 데이터를 브라우저에 출력할 때 이 함수를 생략하는 것은 "우리 서버를 해킹해 주세요"라고 광고하는 것과 같습니다. XSS(Cross-Site Scripting) 공격자는 댓글이나 폼 입력창에 <script> 태그를 심어 관리자의 세션을 탈취하려 합니다.
• 작동 원리: <는 <, >는 >로 변환됩니다. 이는 브라우저가 해당 문자열을 '실행해야 할 코드'가 아닌 '단순 텍스트'로 인식하게 만듭니다.
// 사용자 입력: <script>alert('Hacked')</script>
// ENT_QUOTES 옵션으로 작은따옴표와 큰따옴표까지 모두 안전하게 변환합니다.
$safe_output = htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
echo $safe_output;
// 결과: <script>alert('Hacked')</script>
--------------------------------------------------------------------------------
3. [Top 3] password_hash() & password_verify(): 인증 시스템의 표준
비밀번호는 절대 복호화 가능한 '암호화'가 아닌, 복구가 불가능한 '단방향 해싱'으로 저장해야 합니다. MD5나 SHA1은 현대의 연산 능력(GPU 공격 등) 앞에서는 무의미합니다.
시니어의 보안 설계:
• Argon2ID 사용: 현재 OWASP에서 권장하는 가장 강력한 알고리즘입니다.
• Timing Attack 방어: password_verify()는 두 해시를 비교할 때 '일정 시간 비교(Constant time comparison)' 방식을 사용하여, 응답 시간 차이로 비밀번호를 유추하는 공격을 원천 차단합니다.
• 자동 마이그레이션: password_needs_rehash()를 사용하면 보안 정책(Cost 증가 등)이 변경되었을 때 사용자가 로그인하는 시점에 자동으로 해시를 업데이트할 수 있습니다.
// Argon2ID 권장 설정 (2025 실무 기준)
$options = [
'memory_cost' => 65536, // 64MB
'time_cost' => 4,
'threads' => 3
];
$hash = password_hash($password, PASSWORD_ARGON2ID, $options);
// 검증 및 자동 재해싱
if (password_verify($userInput, $dbHash)) {
if (password_needs_rehash($dbHash, PASSWORD_ARGON2ID, $options)) {
$newHash = password_hash($userInput, PASSWORD_ARGON2ID, $options);
// DB 업데이트 로직 실행
}
}
--------------------------------------------------------------------------------
4. [Top 4] json_encode() & json_decode(): API 통신의 핵심
PHP 5.2 버전부터 내장된 JSON 파서는 이제 REST API의 표준 규격이 되었습니다. 주니어들은 수동으로 문자열을 조합하려 하지만, 시니어는 구조화된 배열을 바로 변환합니다.
• 한국어 처리: 한국어 깨짐(유니코드 변환)을 방지하려면 JSON_UNESCAPED_UNICODE 상수를 반드시 추가하십시오.
$data = ['status' => 'success', 'msg' => '처리 완료', 'id' => 101];
// 유니코드 변환 없이 한글 그대로 출력
$json = json_encode($data, JSON_UNESCAPED_UNICODE);
// JSON을 PHP 객체로 변환
$obj = json_decode($json);
--------------------------------------------------------------------------------
5. [Top 5] explode() & implode(): 데이터 구조의 유연한 전환
문자열과 배열 사이를 오가는 일은 실무에서 매우 빈번합니다.
• 실무 사례: 블로그 태그 시스템에서 "PHP,Laravel,MySQL" 문자열을 배열로 쪼개거나(explode), 관리자 페이지에서 권한 목록 배열을 콤마로 구분된 메타 태그나 CSV 형식의 문자열로 합칠 때(implode) 사용합니다.
// 문자열 -> 배열 (CSV 파싱 등)
$tags = "php,backend,dev";
$tagArray = explode(",", $tags);
// 배열 -> 문자열 (화면 노출용 권한 목록)
$roles = ['Admin', 'Editor', 'Author'];
echo implode(", ", $roles); // "Admin, Editor, Author"
--------------------------------------------------------------------------------
6. [Top 6] filter_var() & filter_var_array(): 검증의 효율화
복잡한 정규표현식을 매번 짤 필요가 없습니다. filter_var()는 이메일, URL, IP 주소 등의 유효성을 한 줄로 끝내줍니다.
시니어의 조언: 단일 값은 filter_var를 쓰지만, 폼 전체 데이터를 한꺼번에 처리할 때는 filter_var_array를 사용하는 것이 훨씬 구조적이고 효율적입니다.
// 이메일 및 정수 범위(1~100) 동시 검증
$data = [
'email' => 'user@example.com',
'age' => '25'
];
$filters = [
'email' => FILTER_VALIDATE_EMAIL,
'age' => ['filter' => FILTER_VALIDATE_INT, 'options' => ['min_range' => 1, 'max_range' => 100]]
];
$validated = filter_var_array($data, $filters);
--------------------------------------------------------------------------------
7. [Top 7] trim() & str_replace(): 데이터 정제와 표준화
사용자가 입력한 데이터는 항상 '지저분하다'고 가정해야 합니다.
• 데이터 표준화(Normalization): 단순히 공백만 제거하는 것이 아니라, trim() 후 strtolower()를 결합하여 이메일 주소 등을 표준화해야 DB 검색 시 대소문자 차이로 인한 오류를 막을 수 있습니다.
// 검색어 정제 및 금지어 필터링
$search = strtolower(trim($_GET['q']));
$clean_text = str_replace("badword", "***", $userInput);
--------------------------------------------------------------------------------
8. [Top 8] preg_match(): 정밀 패턴 매칭의 마법
더 정교한 검증(휴대폰 번호 형식 등)이 필요할 때 사용합니다.
• 반환값 주의사항: 이 함수는 일치 시 1, 미일치 시 0을 반환하지만, 에러 발생 시 false를 반환합니다. 시니어 개발자는 단순히 if(preg_match(...))라고 쓰지 않고 에러 상황까지 고려하여 정밀하게 체크합니다.
$pattern = "/^010-\d{4}-\d{4}$/";
$result = preg_match($pattern, $phone);
if ($result === false) {
// 정규식 에러 처리
} elseif ($result === 1) {
// 패턴 일치
}
--------------------------------------------------------------------------------
9. [Top 9] array_map() & array_filter(): 함수형 프로그래밍의 도입
foreach 루프는 명시적이지만 코드가 길어질 수 있습니다. array_map(변환)과 array_filter(필터링)를 사용하면 코드가 훨씬 간결해지고 의도가 명확해집니다.
• 성능 팁: PHP 8에서는 내부 엔진 최적화로 대용량 배열 처리 속도가 PHP 7 대비 약 1.6배 향상되었습니다. 가독성과 성능을 동시에 잡을 수 있는 선택입니다.
$numbers = [1, 2, 3, 4, 5];
// 짝수만 골라서 2배로 만들기
$evens = array_filter($numbers, fn($n) => $n % 2 === 0);
$doubled = array_map(fn($n) => $n * 2, $evens);
--------------------------------------------------------------------------------
10. [Top 10] file_exists(): 방어적 프로그래밍의 완성
파일을 읽거나 include 하기 전 존재 여부를 확인하는 것은 스크립트 중단(Fatal Error)을 막는 필수 과정입니다.
• 실무 사례: require는 파일이 없으면 즉시 스크립트를 중단시키므로, 사용자 프로필 이미지 로딩 시 파일이 없으면 placeholder 이미지를 보여주는 로직 등에 반드시 활용해야 합니다.
$path = "uploads/user_{$id}.jpg";
$displayImage = file_exists($path) ? $path : "img/default-user.png";
--------------------------------------------------------------------------------
보너스: PHP 8 성능 혁신과 Best Practice
PHP 8은 JIT(Just-In-Time) 컴파일러 도입으로 새로운 시대를 열었습니다. 벤치마크에 따르면 Fibonacci 계산 같은 CPU 집약적 작업은 최대 4배, 일반적인 문자열 및 배열 처리 성능은 약 1.6배 향상되었습니다.
시니어의 Best Practice (데이터 파이프라인): 여러 함수를 체이닝하여 입력값을 받는 즉시 정제하고 표준화하는 습관을 들이세요.
// 입력값 확인 -> 정제 -> 소문자화 (표준화 파이프라인)
if (isset($_POST['email']) && !empty(trim($_POST['email']))) {
$email = strtolower(trim($_POST['email']));
// 이후 유효성 검사 및 DB 처리
}
--------------------------------------------------------------------------------
결론 및 실무 체크리스트
코드를 작성한 후 배포하기 전, 다음 6가지를 반드시 체크하십시오.
1. 변수 존재성: isset()으로 전송 여부를 확인하고, empty()로 유효한 값인지 체크했는가?
2. 데이터 표준화: trim()과 strtolower()를 통해 DB 저장 데이터의 일관성을 확보했는가?
3. XSS 방어: 모든 echo 출력물에 htmlspecialchars()가 적용되어 있는가?
4. 비밀번호 보안: password_hash를 사용 중이며, password_needs_rehash로 미래의 보안 업데이트에 대비했는가?
5. 에러 방지: 파일 include나 접근 전 file_exists()로 확인하여 Fatal Error를 방지했는가?
6. 무차별 공격 방어: 로그인 시스템에 실패 횟수 제한(Lockout) 메커니즘을 구현했는가? (시니어라면 필수 고려 사항)
'Backend > Php' 카테고리의 다른 글
| 사용자의 목소리 듣기: PHP로 폼(Form) 데이터 안전하게 수집하고 출력하기 (0) | 2026.02.17 |
|---|---|
| 웹의 핵심, 데이터 전달: PHP GET과 POST 방식 한 번에 이해하기 (0) | 2026.02.17 |
| 변수가 사는 동네: PHP 전역 변수(global)와 지역 변수(local) 이해하기 (0) | 2026.02.17 |
| 함수(Function) 만들기: 나만의 재사용 가능한 도구 상자 구축하기 (0) | 2026.02.17 |
| PHP의 꽃, foreach: 배열 데이터를 가장 쉽고 빠르게 처리하는 방법 (0) | 2026.02.17 |
