정규표현식으로 이메일이나 전화번호, URL을 검증하려고 preg_match()를 썼는데 자꾸 false가 나온다는 경험을 해본 개발자가 많을 것이다. 코드는 문법적으로 틀린 게 없는데 패턴이 매칭되지 않는 현상은 대부분 메타문자 이스케이프 누락이나 구분자 선택 오류 때문이다. 다만 많은 개발자들이 정규표현식의 문법과 PHP 문자열 이스케이프를 헷갈려서 문제를 제대로 진단하지 못한다. 이번에는 preg_match() 패턴이 왜 작동하지 않는지, 어떻게 올바르게 쓰는지 실제 사례로 완벽하게 정리하겠다.

 

1단계. preg_match() 기초 문법 이해하기

preg_match(pattern, subject, matches)는 정규표현식 패턴이 문자열에 맞는지 검사하는 함수다. 반환값은 매칭 성공 시 1, 실패 시 0, 오류 발생 시 false다. 세 번째 매개변수 matches는 생략 가능하며, 매칭된 부분 문자열을 배열로 담아준다.

정규표현식 패턴은 반드시 구분자(delimiter)로 감싸야 한다. 가장 흔한 구분자는 슬래시(/)인데, 패턴에 슬래시가 포함되면 이스케이프하거나 다른 구분자를 써야 한다.

 

PHP 문자열 이스케이프와 정규표현식 메타문자의 차이

흔한 실수는 PHP 문자열 이스케이프와 정규표현식 메타문자 이스케이프를 섞는 것이다. 예를 들어 \d는 정규표현식 문법인데, PHP 문자열에서 백슬래시는 특수문자 앞에 붙어야 PHP가 인식한다. 싱글쿼트(')는 PHP 이스케이프가 최소한이고, 더블쿼트(")는 변수 치환이 일어나므로 정규표현식에는 싱글쿼트를 쓰는 게 낫다.

 

2단계. 자주 실수하는 패턴 3가지와 해결법

 

잘못된 예 1: 메타문자 백슬래시 미이스케이프
✗ 잘못된 코드
$email = 'test@example.com';
if (preg_match('/^[\w+.-]+@[\w.-]+\.com$/', $email)) {
    echo '유효한 이메일';
}
// 결과: false (매칭 안 됨)

왜 안 될까? 더블쿼트에서 \w는 PHP가 w 앞의 백슬래시를 이스케이프하려고 시도해서 리터럴 w로 해석된다. 정규표현식이 받는 실제 패턴은 [w+.-] 같은 형태가 되어 '숫자나 알파벳이나 +나 . 또는 -'라는 뜻이 아니라 '문자 w나 +나 . 또는 -'가 된다.

✓ 올바른 코드 (싱글쿼트 사용)
$email = 'test@example.com';
if (preg_match('/^[\w+.-]+@[\w.-]+\.com$/', $email)) {
    echo '유효한 이메일';
}
// 결과: 1 (매칭 성공)

싱글쿼트를 쓰면 PHP가 문자열 내 백슬래시를 그대로 패스하므로 정규표현식이 \w(단어 문자), \.(점)를 올바르게 해석한다.

 

잘못된 예 2: URL 패턴에서 구분자 슬래시 미이스케이프
✗ 잘못된 코드
$url = 'https://example.com/path/to/page';
if (preg_match('/^https:\/\/.+$/', $url)) {
    echo '유효한 URL';
}
// 결과: Warning - Invalid regex 또는 false

문제는 구분자 슬래시 안의 슬래시를 \/로 이스케이프했는데, 싱글쿼트가 아닌 더블쿼트 안에서는 백슬래시 자체가 이스케이프되므로 정규표현식이 \/ 패턴을 제대로 받지 못한다. 또는 패턴이 너무 복잡해져 가독성이 떨어진다.

✓ 올바른 코드 1 (다른 구분자 사용)
$url = 'https://example.com/path/to/page';
if (preg_match('#^https://.+$#', $url)) {
    echo '유효한 URL';
}
// 결과: 1 (매칭 성공)

구분자를 #로 바꾸면 패턴 안의 슬래시를 이스케이프할 필요가 없다. 가독성도 훨씬 좋다.

✓ 올바른 코드 2 (구분자 이스케이프)
$url = 'https://example.com/path/to/page';
if (preg_match('/^https:\/\/.+$/', $url)) {
    echo '유효한 URL';
}
// 결과: 1 (매칭 성공)

슬래시 구분자를 써야 한다면 싱글쿼트와 \/를 조합하면 된다.

 

잘못된 예 3: 그룹과 수량자 문법 오류
✗ 잘못된 코드
$phone = '01012345678';
if (preg_match('/^0\d{2}-?\d{3,4}-?\d{4}$/', $phone)) {
    echo '유효한 전화번호';
}
// 결과: false (01012345678은 하이픈이 없으므로 매칭 안 됨)

-?는 하이픈이 있을 수도 있고 없을 수도 있다는 뜻인데, 01012345678처럼 하이픈이 전혀 없는 경우 -?-? 사이의 숫자가 정확히 맞지 않으면 매칭 실패한다. 원래는 전화번호가 010-1234-5678 형식이거나 01012345678 형식이거나 둘 중 하나여야 한다.

✓ 올바른 코드
$phone = '01012345678';
if (preg_match('/^0\d{2}-?\d{3,4}-?\d{4}$/', $phone) || 
    preg_match('/^01[0-9]\d{7,8}$/', $phone)) {
    echo '유효한 전화번호';
}
// 또는
if (preg_match('/^0\d{1,2}-?\d{3,4}-?\d{4}$|^01[0-9]\d{7,8}$/', $phone)) {
    echo '유효한 전화번호';
}
// 결과: 1 (매칭 성공)

패턴을 명확히 분리하거나 OR(|) 연산자로 여러 형식을 허용하면 된다.

 

3단계. 실전 예제: 이메일, URL, 숫자만 검증

 

이메일 검증
✓ 올바른 코드
$email = 'user+tag@subdomain.co.kr';
if (preg_match('/^[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/', $email)) {
    echo '유효한 이메일';
} else {
    echo '유효하지 않은 이메일';
}
// 결과: 유효한 이메일

// 더 간단한 방법: PHP 내장 검증
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
    echo '유효한 이메일';
}

 

URL 검증
✓ 올바른 코드
$url = 'https://example.com/path?param=value';
if (preg_match('#^https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(/.*)?$#', $url)) {
    echo '유효한 URL';
} else {
    echo '유효하지 않은 URL';
}
// 결과: 유효한 URL

// PHP 내장 검증
if (filter_var($url, FILTER_VALIDATE_URL)) {
    echo '유효한 URL';
}

 

숫자만 검증 (국제 전화번호)
✓ 올바른 코드
$phone = '+82-10-1234-5678';
// 숫자와 기호만 포함되었는지 확인
if (preg_match('/^[+0-9\-() ]+$/', $phone)) {
    // 숫자만 추출해서 길이 확인
    $digits = preg_replace('/[^0-9]/', '', $phone);
    if (strlen($digits) >= 10 && strlen($digits)