코딩 스쿨 PHP

언어선택 : HTMLCSSJAVAJAVASCRIPTMYSQLSQL PHP

PHP RegEx

PHP 정규 표현식(RegEx): 완벽한 사용법과 예제 가이드


PHP 정규 표현식(RegEx)이란 무엇인가요?

PHP에서 **정규 표현식(Regular Expressions)**은 문자열 내에서 특정 패턴을 검색, 일치, 대체, 분할하는 강력한 도구입니다. 정규 표현식을 사용하면 복잡한 문자열 조작 작업을 효율적으로 수행할 수 있으며, 데이터 유효성 검사, 텍스트 파싱, 데이터 추출 등에 널리 활용됩니다. 이 가이드에서는 PHP에서 정규 표현식을 사용하는 방법부터 기본 개념, 주요 함수, 실용적인 예제, 최적화 및 보안 고려사항까지 상세히 설명하겠습니다.


1. 정규 표현식(RegEx)의 기본 개념

1.1 정규 표현식이란?

  • *정규 표현식(Regular Expression)**은 문자열에서 특정 패턴을 정의하고, 이를 기반으로 검색, 추출, 대체 등을 수행하는 일종의 언어입니다. 다양한 프로그래밍 언어에서 지원되며, PHP 역시 강력한 정규 표현식 기능을 제공합니다.

1.2 정규 표현식의 구성 요소

  • 리터럴(Literals): 일치해야 하는 정확한 문자 또는 문자열.
  • 메타문자(Metacharacters): 특별한 의미를 가지는 문자 (예: . `` + ? 등).
  • 수량자(Quantifiers): 반복 횟수를 지정 (``, +, ?, {n}, {n,}, {n,m}).
  • 그룹화(Grouping): 패턴을 그룹으로 묶어 캡처하거나, 수량자를 적용 (()).
  • 대안(Alternation): 여러 패턴 중 하나를 선택 (|).
  • 특수 시퀀스(Special Sequences): 미리 정의된 패턴 (\\\\d, \\\\w, \\\\s 등).
  • 주석(Comments): 패턴 내 주석 추가 ((?#comment)).

2. PHP에서 정규 표현식 사용하기

PHP는 두 가지 주요 방식으로 정규 표현식을 지원합니다:

  1. Perl 호환 정규 표현식 (PCRE): preg_로 시작하는 함수들 (예: preg_match, preg_replace).
  2. POSIX 정규 표현식: ereg_로 시작하는 함수들 (더 이상 권장되지 않음).

현재는 PCRE가 더 강력하고 널리 사용되므로, 이를 중심으로 설명하겠습니다.


3. PHP PCRE 함수

PHP에서 PCRE를 사용하기 위한 주요 함수들은 다음과 같습니다:

3.1 preg_match()

특정 패턴이 문자열 내에 존재하는지 검사하고, 일치 여부를 반환합니다.

구문:

int preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0)

예제:

<?php
$pattern = "/hello/";
$subject = "hello world";

if (preg_match($pattern, $subject, $matches)) {
    echo "매칭 성공: " . $matches[0]; // 출력: 매칭 성공: hello
} else {
    echo "매칭 실패";
}
?>

3.2 preg_match_all()

문자열 내에서 패턴과 일치하는 모든 부분을 찾고, 그 결과를 배열로 반환합니다.

구문:

int preg_match_all(string $pattern, string $subject, array &$matches, int $flags = PREG_PATTERN_ORDER, int $offset = 0)

예제:

<?php
$pattern = "/\\\\d+/";
$subject = "There are 15 apples and 30 oranges.";

preg_match_all($pattern, $subject, $matches);

print_r($matches);

/* 출력:
Array
(
    [0] => Array
        (
            [0] => 15
            [1] => 30
        )
)
*/
?>

3.3 preg_replace()

패턴과 일치하는 부분을 다른 문자열로 대체합니다.

구문:

string preg_replace(string|array $pattern, string|array $replacement, string|array $subject, int $limit = -1, int &$count = null)

예제:

<?php
$pattern = "/\\\\d+/";
$replacement = "#";
$subject = "User123 has 456 points.";

$result = preg_replace($pattern, $replacement, $subject);
echo $result; // 출력: User# has # points.
?>

3.4 preg_split()

패턴을 기준으로 문자열을 분할하여 배열로 반환합니다.

구문:

array preg_split(string $pattern, string $subject, int $limit = -1, int $flags = 0)

예제:

<?php
$pattern = "/[\\\\s,]+/";
$subject = "apple, banana orange";

$result = preg_split($pattern, $subject);
print_r($result);

/* 출력:
Array
(
    [0] => apple
    [1] => banana
    [2] => orange
)
*/
?>

3.5 preg_grep()

배열 내에서 패턴과 일치하는 요소만을 추출하여 새로운 배열로 반환합니다.

구문:

array preg_grep(string $pattern, array $input, int $flags = 0)

예제:

<?php
$input = ["apple", "banana123", "cherry", "date456"];
$pattern = "/\\\\d+/";

$result = preg_grep($pattern, $input);
print_r($result);

/* 출력:
Array
(
    [1] => banana123
    [3] => date456
)
*/
?>

3.6 preg_replace_callback()

패턴과 일치하는 부분을 콜백 함수의 반환값으로 대체합니다.

구문:

mixed preg_replace_callback(string|array $pattern, callable $callback, string|array $subject, int $limit = -1, int &$count = null)

예제:

<?php
$pattern = "/\\\\d+/";
$subject = "Item 1 costs $100, Item 2 costs $200.";

$result = preg_replace_callback($pattern, function($matches) {
    return $matches[0] * 2; // 숫자를 두 배로
}, $subject);

echo $result; // 출력: Item 2 costs $200, Item 4 costs $400.
?>


4. 정규 표현식 패턴과 메타문자

4.1 기본 메타문자

  • . (Dot): 임의의 단일 문자 (줄 바꿈 제외).
  • ^ (Caret): 문자열의 시작.
  • $ (Dollar): 문자열의 끝.
  • `` (Asterisk): 0회 이상 반복.
  • + (Plus): 1회 이상 반복.
  • ? (Question Mark): 0회 또는 1회 반복.
  • [] (Character Class): 특정 문자 집합 중 하나와 일치.
  • | (Alternation): 여러 패턴 중 하나와 일치.
  • () (Grouping): 패턴을 그룹으로 묶어 캡처.
  • \\\\ (Escape): 메타문자를 리터럴로 사용.

4.2 특수 시퀀스

  • \\\\d: 숫자와 일치 ([0-9]).
  • \\\\D: 숫자가 아닌 문자와 일치.
  • \\\\w: 단어 문자와 일치 ([A-Za-z0-9_]).
  • \\\\W: 단어 문자가 아닌 것과 일치.
  • \\\\s: 공백 문자와 일치 (스페이스, 탭, 줄 바꿈).
  • \\\\S: 공백 문자가 아닌 것과 일치.
  • \\\\b: 단어 경계.
  • \\\\B: 단어 경계가 아닌 것.

4.3 수량자(Quantifiers)

  • {n}: 정확히 n회 반복.
  • {n,}: n회 이상 반복.
  • {n,m}: n회 이상, m회 이하 반복.

4.4 그룹화 및 캡처

  • (pattern): 패턴을 그룹으로 묶고, 캡처.
  • (?:pattern): 캡처하지 않는 그룹.
  • (?P<name>pattern): 이름 있는 캡처 그룹.

5. 정규 표현식 수식과 패턴 예제

5.1 이메일 주소 검증

패턴 설명:

  • 시작과 끝을 ^$로 지정.
  • 사용자 이름 부분: 알파벳, 숫자, 점(.), 밑줄(_), 하이픈(``) 가능.
  • @ 기호.
  • 도메인 부분: 알파벳, 숫자, 하이픈(``), 점(.) 가능.

패턴:

/^[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$/

PHP 예제:

<?php
$email = "user@example.com";
$pattern = "/^[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$/";

if (preg_match($pattern, $email)) {
    echo "유효한 이메일 주소입니다.";
} else {
    echo "유효하지 않은 이메일 주소입니다.";
}
// 출력: 유효한 이메일 주소입니다.
?>

5.2 전화번호 검증 (예: 010-1234-5678)

패턴 설명:

  • 시작과 끝을 ^$로 지정.
  • 010-으로 시작.
  • 3자리 또는 4자리 숫자.
  • 하이픈(``).
  • 4자리 숫자.

패턴:

/^010-\\\\d{3,4}-\\\\d{4}$/

PHP 예제:

<?php
$phone = "010-1234-5678";
$pattern = "/^010-\\\\d{3,4}-\\\\d{4}$/";

if (preg_match($pattern, $phone)) {
    echo "유효한 전화번호입니다.";
} else {
    echo "유효하지 않은 전화번호입니다.";
}
// 출력: 유효한 전화번호입니다.
?>

5.3 URL 검증

패턴 설명:

  • http 또는 https로 시작.
  • :// 기호.
  • 도메인 이름.
  • 선택적으로 경로, 쿼리 문자열, 프래그먼트 포함.

패턴:

/^(https?:\\\\/\\\\/)?([\\\\w\\\\-]+)\\\\.([\\\\w\\\\-]+)(\\\\.[\\\\w\\\\-]+)*([\\\\/\\\\w\\\\-.?&=]*)?$/

PHP 예제:

<?php
$url = "<https://www.example.com/path?query=123>";
$pattern = "/^(https?:\\\\/\\\\/)?([\\\\w\\\\-]+)\\\\.([\\\\w\\\\-]+)(\\\\.[\\\\w\\\\-]+)*([\\\\/\\\\w\\\\-.?&=]*)?$/";

if (preg_match($pattern, $url)) {
    echo "유효한 URL입니다.";
} else {
    echo "유효하지 않은 URL입니다.";
}
// 출력: 유효한 URL입니다.
?>

5.4 비밀번호 강도 검증

패턴 설명:

  • 최소 8자 이상.
  • 최소 하나의 대문자.
  • 최소 하나의 소문자.
  • 최소 하나의 숫자.
  • 최소 하나의 특수 문자.

패턴:

/^(?=.*[A-Z])(?=.*[a-z])(?=.*\\\\d)(?=.*[\\\\W_]).{8,}$/

PHP 예제:

<?php
$password = "StrongP@ssw0rd";
$pattern = "/^(?=.*[A-Z])(?=.*[a-z])(?=.*\\\\d)(?=.*[\\\\W_]).{8,}$/";

if (preg_match($pattern, $password)) {
    echo "강력한 비밀번호입니다.";
} else {
    echo "비밀번호가 충분히 강력하지 않습니다.";
}
// 출력: 강력한 비밀번호입니다.
?>


6. 정규 표현식 수정자(Modifiers)

수정자는 패턴의 동작 방식을 변경하는 옵션입니다. 일반적으로 패턴의 마지막에 슬래시(/) 뒤에 추가됩니다.

6.1 i (Case Insensitive)

대소문자를 구분하지 않고 일치.

예제:

<?php
$pattern = "/hello/i";
$subject = "HELLO World";

if (preg_match($pattern, $subject)) {
    echo "매칭 성공!";
} else {
    echo "매칭 실패";
}
// 출력: 매칭 성공!
?>

6.2 m (Multiline)

문자열을 여러 줄로 처리, ^$가 각 줄의 시작과 끝에 일치.

예제:

<?php
$pattern = "/^Hello/m";
$subject = "Say Hello\\\\nHello World";

preg_match_all($pattern, $subject, $matches);
print_r($matches);

/* 출력:
Array
(
    [0] => Array
        (
            [0] => Hello
        )
)
*/
?>

6.3 s (Dot All)

.가 줄 바꿈 문자를 포함한 모든 문자와 일치.

예제:

<?php
$pattern = "/Hello.World/s";
$subject = "Hello\\\\nWorld";

if (preg_match($pattern, $subject)) {
    echo "매칭 성공!";
} else {
    echo "매칭 실패";
}
// 출력: 매칭 성공!
?>

6.4 x (Extended)

패턴 내 공백과 주석을 무시하여 가독성 향상.

예제:

<?php
$pattern = "/
    ^          # 문자열의 시작
    \\\\d{3}      # 정확히 3개의 숫자
    -          # 하이픈
    \\\\d{2,4}    # 2~4개의 숫자
    -          # 하이픈
    \\\\d{4}      # 정확히 4개의 숫자
    $
    /x";

$phone = "123-45-6789";

if (preg_match($pattern, $phone)) {
    echo "유효한 전화번호입니다.";
} else {
    echo "유효하지 않은 전화번호입니다.";
}
// 출력: 유효한 전화번호입니다.
?>


7. 고급 정규 표현식 기능

7.1 긍정적 전방탐색(Positive Lookahead)

특정 패턴 앞에 있는지 확인하지만, 해당 패턴은 결과에 포함하지 않음.

패턴:

/(?=pattern)/

예제:

<?php
$pattern = "/\\\\d+(?=%)/"; // 숫자 뒤에 %가 있는지 확인
$subject = "Discount is 50% and surcharge is 20%.";

preg_match_all($pattern, $subject, $matches);
print_r($matches);

/* 출력:
Array
(
    [0] => Array
        (
            [0] => 50
            [1] => 20
        )
)
*/
?>

7.2 부정적 전방탐색(Negative Lookahead)

특정 패턴 앞에 없는지 확인.

패턴:

/(?!pattern)/

예제:

<?php
$pattern = "/\\\\b(?!apple)\\\\w+\\\\b/i"; // 'apple'이 아닌 단어
$subject = "I like apple, banana, and cherry.";

preg_match_all($pattern, $subject, $matches);
print_r($matches);

/* 출력:
Array
(
    [0] => Array
        (
            [0] => I
            [1] => like
            [2] => banana
            [3] => and
            [4] => cherry
        )
)
*/
?>

7.3 긍정적 후방탐색(Positive Lookbehind)

특정 패턴 뒤에 있는지 확인하지만, 해당 패턴은 결과에 포함하지 않음.

패턴:

/(?<=pattern)/

예제:

<?php
$pattern = "/(?<=\\\\$)\\\\d+/";
$subject = "The price is $100 and the discount is $20.";

preg_match_all($pattern, $subject, $matches);
print_r($matches);

/* 출력:
Array
(
    [0] => Array
        (
            [0] => 100
            [1] => 20
        )
)
*/
?>

7.4 부정적 후방탐색(Negative Lookbehind)

특정 패턴 뒤에 없는지 확인.

패턴:

/(?<!pattern)/

예제:

<?php
$pattern = "/(?<!\\\\$)\\\\d+/";
$subject = "The price is $100 and the discount is 20.";

preg_match_all($pattern, $subject, $matches);
print_r($matches);

/* 출력:
Array
(
    [0] => Array
        (
            [0] => 20
        )
)
*/
?>

7.5 비캡처 그룹(Non-Capturing Groups)

패턴을 그룹화하지만 캡처하지 않음.

패턴:

/(?:pattern)/

예제:

<?php
$pattern = "/(?:Mr|Ms|Mrs)\\\\.?\\\\s\\\\w+/";
$subject = "Mr. Smith and Ms. Johnson are attending the meeting.";

preg_match_all($pattern, $subject, $matches);
print_r($matches);

/* 출력:
Array
(
    [0] => Array
        (
            [0] => Mr. Smith
            [1] => Ms. Johnson
        )
)
*/
?>

7.6 이름 있는 캡처 그룹(Named Captures)

캡처 그룹에 이름을 부여하여 결과를 더 명확하게 관리.

패턴:

/(?P<name>pattern)/

예제:

<?php
$pattern = "/(?P<first>\\\\w+)\\\\s(?P<last>\\\\w+)/";
$subject = "John Doe";

if (preg_match($pattern, $subject, $matches)) {
    echo "First Name: " . $matches['first'] . "<br>"; // 출력: First Name: John
    echo "Last Name: " . $matches['last'] . "<br>";   // 출력: Last Name: Doe
}
?>


8. 정규 표현식 성능 최적화

8.1 패턴 최적화

복잡한 패턴은 성능에 영향을 줄 수 있으므로, 가능한 한 단순하게 작성합니다.

비효율적인 패턴:

/(a|aa|aaa|aaaa)/

효율적인 패턴:

/a{1,4}/

8.2 캡처 최소화

필요하지 않은 캡처 그룹을 줄여 메모리 사용을 최적화합니다.

비효율적인 패턴:

/(a)(b)(c)/

효율적인 패턴:

/(?:a)(?:b)(?:c)/

8.3 불필요한 반복 피하기

과도한 반복은 성능 저하를 초래할 수 있습니다.

비효율적인 패턴:

/^(.*)+$/

효율적인 패턴:

/^(.*)$/

8.4 캐싱 사용

정규 표현식을 자주 사용하는 경우, 패턴을 변수에 저장하여 재사용합니다.

예제:

<?php
$pattern = "/\\\\d+/";
$subject = "Numbers: 123, 456, 789.";

for ($i = 0; $i < 1000; $i++) {
    preg_match($pattern, $subject, $matches);
}
?>


9. 정규 표현식 보안 고려사항

9.1 ReDoS (Regular Expression Denial of Service) 공격 방지

복잡한 패턴은 입력 데이터에 따라 무한 루프에 빠질 수 있으므로, 다음을 유의합니다:

  • 최소한의 메타문자 사용: 불필요한 메타문자나 과도한 반복을 피합니다.
  • 제한된 입력 크기: 정규 표현식에 전달되는 입력 데이터의 크기를 제한합니다.
  • 타임아웃 설정: PHP의 set_time_limit() 함수를 사용하여 스크립트 실행 시간을 제한합니다.

예제:

<?php
set_time_limit(1); // 스크립트 실행 시간을 1초로 제한

$pattern = "/^(a+)+$/"; // 취약한 패턴
$subject = "aaaaaaaaaaaaaaaaaaaaaab"; // ReDoS를 유발할 수 있는 입력

if (preg_match($pattern, $subject)) {
    echo "매칭 성공!";
} else {
    echo "매칭 실패";
}
?>

9.2 사용자 입력 정규화

사용자 입력을 처리할 때는 정규 표현식을 통해 필터링하고, 가능한 한 안전하게 처리합니다.

예제:

<?php
$input = $_POST['username'];
$pattern = "/^[a-zA-Z0-9_]{3,16}$/";

if (preg_match($pattern, $input)) {
    echo "유효한 사용자 이름입니다.";
} else {
    echo "유효하지 않은 사용자 이름입니다.";
}
?>

9.3 XSS 방지

정규 표현식을 사용하여 입력 데이터를 검증한 후, 출력 시 HTML 이스케이프를 적용하여 XSS 공격을 방지합니다.

예제:

<?php
$comments = [
    ["user" => "홍길동", "comment" => "<script>alert('XSS');</script>"],
    ["user" => "김철수", "comment" => "안녕하세요!"]
];

foreach ($comments as $comment) {
    $safe_user = htmlspecialchars($comment['user'], ENT_QUOTES, 'UTF-8');
    $safe_comment = htmlspecialchars($comment['comment'], ENT_QUOTES, 'UTF-8');
    echo "사용자: $safe_user, 댓글: $safe_comment<br>";
}
?>


10. PHP 8.0 이상의 정규 표현식 관련 새로운 기능

10.1 Named Arguments (명명된 인자)과 정규 표현식

PHP 8.0부터 함수 호출 시 매개변수의 이름을 명시하여 인자를 전달할 수 있습니다. 이는 정규 표현식을 사용하는 함수에서 매개변수의 순서를 무시하고, 원하는 인자만 전달할 수 있게 해줍니다.

예제:

<?php
function validatePattern(string $pattern, string $subject, int &$matchesCount = null): bool {
    return preg_match($pattern, $subject, $matches, PREG_OFFSET_CAPTURE, 0);
}

$pattern = "/hello/";
$subject = "Hello world! hello PHP.";

if (validatePattern(pattern: $pattern, subject: $subject, matchesCount: $count)) {
    echo "매칭 성공! 총 매칭 수: $count";
} else {
    echo "매칭 실패";
}
// 출력: 매칭 성공! 총 매칭 수: 1
?>

10.2 Union Types (유니온 타입)과 정규 표현식

PHP 8.0부터 함수의 매개변수나 반환값에 여러 타입을 지정할 수 있는 유니온 타입을 사용할 수 있습니다. 이는 정규 표현식을 사용하는 함수에서 보다 유연한 타입 관리를 가능하게 합니다.

예제:

<?php
function processInput(array|string $input): void {
    if (is_array($input)) {
        foreach ($input as $item) {
            if (preg_match("/\\\\d+/", $item)) {
                echo "숫자가 포함된 아이템: $item<br>";
            }
        }
    } else {
        if (preg_match("/\\\\d+/", $input)) {
            echo "숫자가 포함된 문자열: $input<br>";
        }
    }
}

processInput(["apple1", "banana", "cherry2"]); // 출력: 숫자가 포함된 아이템: apple1, 숫자가 포함된 아이템: cherry2
processInput("grape3");                          // 출력: 숫자가 포함된 문자열: grape3
?>

10.3 Array Unpacking (배열 펼치기)와 정규 표현식

PHP 8.0부터 배열 펼치기 연산자(...)를 사용하여 배열을 다른 배열에 쉽게 병합할 수 있습니다. 이를 통해 정규 표현식 패턴을 동적으로 구성할 수 있습니다.

예제:

<?php
$commonPatterns = [
    "/\\\\d+/",
    "/[a-zA-Z]+/"
];

$additionalPatterns = [
    "/\\\\s+/"
];

$mergedPatterns = [...$commonPatterns, ...$additionalPatterns];
$subject = "Sample 123 Text";

foreach ($mergedPatterns as $pattern) {
    if (preg_match($pattern, $subject, $matches)) {
        echo "매칭된 패턴: $pattern, 결과: " . $matches[0] . "<br>";
    }
}

/* 출력:
매칭된 패턴: /\\\\d+/, 결과: 123
매칭된 패턴: /[a-zA-Z]+/, 결과: Sample
매칭된 패턴: /\\\\s+/, 결과:
*/
?>


11. 정규 표현식 베스트 프랙티스

11.1 가독성 유지

  • 주석 추가: 복잡한 패턴에는 주석을 추가하여 이해를 돕습니다.

    /^(?P<username>[a-zA-Z0-9_]{3,16})@(?P<domain>[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,})$/
    
    
  • 공백 활용: x 수정자를 사용하여 패턴 내 공백을 허용하고, 패턴을 분할하여 가독성을 높입니다.

    /(?x)
        ^                           # 문자열 시작
        (?P<username>[a-zA-Z0-9_]{3,16}) # 사용자 이름
        @                           # @ 기호
        (?P<domain>[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}) # 도메인
        $                           # 문자열 끝
    /
    
    

11.2 패턴 재사용

자주 사용하는 패턴은 변수에 저장하여 재사용함으로써 코드의 중복을 줄입니다.

예제:

<?php
$emailPattern = "/^[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$/";

function validateEmail(string $email, string $pattern): bool {
    return preg_match($pattern, $email) === 1;
}

$email = "user@example.com";
if (validateEmail($email, $GLOBALS['emailPattern'])) {
    echo "유효한 이메일입니다.";
} else {
    echo "유효하지 않은 이메일입니다.";
}
?>

11.3 성능 최적화

  • 단순한 패턴 사용: 복잡한 패턴은 피하고, 가능한 한 단순하게 작성합니다.
  • 패턴을 컴파일하여 사용: 패턴을 여러 번 사용하는 경우, preg_match 대신 preg_match_all이나 preg_replace_callback을 활용하여 성능을 향상시킵니다.
  • 입력 데이터 제한: 정규 표현식을 적용할 입력 데이터의 크기를 제한하여 성능 저하를 방지합니다.

12. 정규 표현식 보안 고려사항

12.1 ReDoS 공격 방지

  • *ReDoS(Regular Expression Denial of Service)**는 정규 표현식의 취약점을 이용하여 서버의 자원을 고갈시키는 공격입니다. 이를 방지하기 위해 다음을 유의합니다:
  • 복잡한 패턴 피하기: 불필요하게 복잡한 패턴은 피하고, 가능한 한 단순하게 작성합니다.
  • 입력 데이터 제한: 정규 표현식에 전달되는 입력 데이터의 크기를 제한합니다.
  • 타임아웃 설정: PHP의 set_time_limit() 함수를 사용하여 스크립트의 최대 실행 시간을 제한합니다.

예제:

<?php
set_time_limit(1); // 스크립트 실행 시간을 1초로 제한

$pattern = "/^(a+)+$/"; // 취약한 패턴
$subject = "aaaaaaaaaaaaaaaaaaaaaab"; // ReDoS를 유발할 수 있는 입력

if (preg_match($pattern, $subject)) {
    echo "매칭 성공!";
} else {
    echo "매칭 실패";
}
// 출력: 매칭 실패
?>

12.2 사용자 입력 정규화

사용자 입력은 신뢰할 수 없으므로, 반드시 정규 표현식을 통해 검증하고 필터링해야 합니다.

예제:

<?php
$username = $_POST['username'];
$pattern = "/^[a-zA-Z0-9_]{3,16}$/";

if (preg_match($pattern, $username)) {
    echo "유효한 사용자 이름입니다.";
} else {
    echo "유효하지 않은 사용자 이름입니다.";
}
?>

12.3 XSS 방지

정규 표현식을 사용하여 입력 데이터를 검증한 후, 출력 시 HTML 이스케이프를 적용하여 XSS 공격을 방지합니다.

예제:

<?php
$comments = [
    ["user" => "홍길동", "comment" => "<script>alert('XSS');</script>"],
    ["user" => "김철수", "comment" => "안녕하세요!"]
];

foreach ($comments as $comment) {
    $safe_user = htmlspecialchars($comment['user'], ENT_QUOTES, 'UTF-8');
    $safe_comment = htmlspecialchars($comment['comment'], ENT_QUOTES, 'UTF-8');
    echo "사용자: $safe_user, 댓글: $safe_comment<br>";
}
?>


13. PHP 정규 표현식 Best Practices

13.1 가독성 유지

  • 주석과 공백 활용: 패턴이 복잡할 경우, 주석과 공백을 사용하여 가독성을 높입니다.

    /(?x)                                   # 확장 모드 활성화
        ^                                   # 문자열 시작
        (?P<username>[a-zA-Z0-9_]{3,16})    # 사용자 이름
        @                                   # @ 기호
        (?P<domain>[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}) # 도메인
        $                                   # 문자열 끝
    /
    
    
  • 패턴 단순화: 가능한 한 단순하고 명확한 패턴을 사용합니다.

13.2 패턴 재사용 및 모듈화

자주 사용하는 패턴은 함수나 변수로 모듈화하여 재사용성을 높입니다.

예제:

<?php
function isValidEmail(string $email): bool {
    $pattern = "/^[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$/";
    return preg_match($pattern, $email) === 1;
}

$email = "user@example.com";
if (isValidEmail($email)) {
    echo "유효한 이메일입니다.";
} else {
    echo "유효하지 않은 이메일입니다.";
}
?>

13.3 성능 최적화

  • 단일 패턴 사용: 여러 패턴을 사용할 때는 가능한 한 단일 패턴으로 통합하여 호출 횟수를 줄입니다.

  • 캡처 최소화: 필요하지 않은 캡처 그룹은 비캡처 그룹으로 변경합니다.

    /(?:pattern)/
    
    

13.4 테스트 및 디버깅

정규 표현식은 복잡할 수 있으므로, 다음과 같은 도구를 사용하여 테스트하고 디버깅합니다:

  • 온라인 정규 표현식 테스트기: Regex101, RegExr
  • PHP 코드 내 디버깅: print_r, var_dump 등을 사용하여 일치 결과를 확인합니다.

14. 실용적인 정규 표현식 예제

14.1 사용자 목록에서 이메일 추출

<?php
$text = "Contact us at support@example.com or sales@example.org.";

$pattern = "/[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}/";

preg_match_all($pattern, $text, $matches);

print_r($matches[0]);

/* 출력:
Array
(
    [0] => support@example.com
    [1] => sales@example.org
)
*/
?>

14.2 날짜 형식 변환 (YYYY-MM-DD -> DD/MM/YYYY)

<?php
$date = "2024-10-11";
$pattern = "/(\\\\d{4})-(\\\\d{2})-(\\\\d{2})/";
$replacement = "$3/$2/$1";

$newDate = preg_replace($pattern, $replacement, $date);
echo $newDate; // 출력: 11/10/2024
?>

14.3 텍스트에서 링크 추출

<?php
$text = "Visit our website at <https://www.example.com> or follow us on <http://twitter.com/example.">;

$pattern = "/https?:\\\\/\\\\/[^\\\\s]+/";

preg_match_all($pattern, $text, $matches);

print_r($matches[0]);

/* 출력:
Array
(
    [0] => <https://www.example.com>
    [1] => <http://twitter.com/example>
)
*/
?>

14.4 문자열에서 태그 제거

<?php
$html = "<p>This is <strong>bold</strong> text.</p>";

$pattern = "/<[^>]+>/";
$cleanText = preg_replace($pattern, "", $html);

echo $cleanText; // 출력: This is bold text.
?>

14.5 특정 단어 강조 표시

<?php
$text = "PHP is a popular scripting language. PHP is widely used.";

$pattern = "/\\\\b(PHP)\\\\b/i";
$replacement = "<strong>$1</strong>";

$highlighted = preg_replace($pattern, $replacement, $text);
echo $highlighted;

// 출력: <strong>PHP</strong> is a popular scripting language. <strong>PHP</strong> is widely used.
?>


15. 정규 표현식과 객체 지향 프로그래밍(OOP)

정규 표현식은 객체 지향 프로그래밍에서 데이터 처리와 검증에 유용하게 사용됩니다.

15.1 정규 표현식을 사용하는 클래스 메서드

<?php
class UserValidator {
    private $emailPattern = "/^[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$/";
    private $passwordPattern = "/^(?=.*[A-Z])(?=.*[a-z])(?=.*\\\\d)(?=.*[\\\\W_]).{8,}$/";

    public function validateEmail(string $email): bool {
        return preg_match($this->emailPattern, $email) === 1;
    }

    public function validatePassword(string $password): bool {
        return preg_match($this->passwordPattern, $password) === 1;
    }
}

$validator = new UserValidator();

$email = "user@example.com";
$password = "StrongP@ssw0rd";

if ($validator->validateEmail($email)) {
    echo "유효한 이메일입니다.<br>";
} else {
    echo "유효하지 않은 이메일입니다.<br>";
}

if ($validator->validatePassword($password)) {
    echo "강력한 비밀번호입니다.";
} else {
    echo "비밀번호가 충분히 강력하지 않습니다.";
}

/* 출력:
유효한 이메일입니다.
강력한 비밀번호입니다.
*/
?>

15.2 정규 표현식을 활용한 데이터 파싱 클래스

<?php
class LogParser {
    private $pattern = "/\\\\[(?P<date>[\\\\d\\\\-: ]+)\\\\] (?P<level>\\\\w+): (?P<message>.+)/";

    public function parse(string $logLine): ?array {
        if (preg_match($this->pattern, $logLine, $matches)) {
            return [
                'date' => $matches['date'],
                'level' => $matches['level'],
                'message' => $matches['message']
            ];
        }
        return null;
    }
}

$parser = new LogParser();
$logLine = "[2024-10-11 12:34:56] ERROR: An unexpected error occurred.";

$parsed = $parser->parse($logLine);
if ($parsed) {
    echo "날짜: {$parsed['date']}<br>";
    echo "레벨: {$parsed['level']}<br>";
    echo "메시지: {$parsed['message']}<br>";
} else {
    echo "로그 라인을 파싱할 수 없습니다.";
}

/* 출력:
날짜: 2024-10-11 12:34:56
레벨: ERROR
메시지: An unexpected error occurred.
*/
?>


16. 정규 표현식과 데이터베이스 연동

정규 표현식을 사용하여 데이터베이스와 상호작용할 때는 입력 데이터를 검증하고, 결과를 효율적으로 처리할 수 있습니다.

16.1 사용자 입력 데이터 검증 후 데이터베이스 삽입

<?php
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "testdb";

// 데이터베이스 연결
$conn = new mysqli($servername, $username, $password, $dbname);

// 연결 확인
if ($conn->connect_error) {
    die("연결 실패: " . $conn->connect_error);
}

// 사용자 입력 받기
$name = $_POST['name'];
$email = $_POST['email'];

// 정규 표현식을 사용한 검증
$emailPattern = "/^[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$/";
$namePattern = "/^[a-zA-Z\\\\s]{3,50}$/";

if (preg_match($namePattern, $name) && preg_match($emailPattern, $email)) {
    // 준비된 문장 사용
    $stmt = $conn->prepare("INSERT INTO users (name, email) VALUES (?, ?)");
    $stmt->bind_param("ss", $name, $email);

    if ($stmt->execute()) {
        echo "사용자가 성공적으로 등록되었습니다.";
    } else {
        echo "오류: " . $stmt->error;
    }

    $stmt->close();
} else {
    echo "입력 데이터가 유효하지 않습니다.";
}

$conn->close();
?>

16.2 데이터베이스에서 패턴 매칭을 사용한 데이터 검색

<?php
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "testdb";

// 데이터베이스 연결
$conn = new mysqli($servername, $username, $password, $dbname);

// 연결 확인
if ($conn->connect_error) {
    die("연결 실패: " . $conn->connect_error);
}

$searchPattern = ".*@example\\\\.com$"; // example.com 도메인 이메일 검색

// 정규 표현식을 사용한 검색 (MySQL REGEXP 사용)
$sql = "SELECT name, email FROM users WHERE email REGEXP ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $searchPattern);
$stmt->execute();
$result = $stmt->get_result();

while ($row = $result->fetch_assoc()) {
    echo "이름: {$row['name']}, 이메일: {$row['email']}<br>";
}

$stmt->close();
$conn->close();
?>


17. 정규 표현식과 메모리 관리

정규 표현식은 메모리를 많이 사용할 수 있으므로, 특히 대용량 데이터를 처리할 때는 다음을 유의합니다:

17.1 패턴 컴파일 재사용

자주 사용하는 패턴은 변수에 저장하여 재사용함으로써 메모리 사용을 줄입니다.

예제:

<?php
$pattern = "/\\\\d+/";
$subject = "Numbers: 123, 456, 789.";

for ($i = 0; $i < 1000; $i++) {
    preg_match($pattern, $subject, $matches);
}
?>

17.2 불필요한 캡처 피하기

필요하지 않은 캡처 그룹은 비캡처 그룹으로 변경하여 메모리 사용을 최적화합니다.

예제:

<?php
// 불필요한 캡처
$pattern = "/(a)(b)(c)/";

// 비캡처 그룹 사용
$pattern = "/(?:a)(?:b)(?:c)/";
?>


18. 정규 표현식과 성능 최적화

정규 표현식은 복잡할수록 성능에 영향을 줄 수 있으므로, 다음과 같은 최적화 기법을 사용합니다:

18.1 단순한 패턴 사용

가능한 한 단순하고 명확한 패턴을 사용하여 정규 표현식의 실행 속도를 향상시킵니다.

비효율적인 패턴:

/(a|aa|aaa|aaaa)/

효율적인 패턴:

/a{1,4}/

18.2 비캡처 그룹 사용

캡처 그룹은 추가적인 메모리와 성능 오버헤드를 유발할 수 있으므로, 필요하지 않은 경우 비캡처 그룹을 사용합니다.

예제:

/(?:pattern)/

18.3 입력 데이터 제한

정규 표현식에 전달되는 입력 데이터의 크기를 제한하여 성능 저하를 방지합니다.

예제:

<?php
$input = substr($input, 0, 100); // 최대 100자까지 제한
$pattern = "/your_pattern/";
preg_match($pattern, $input, $matches);
?>

18.4 Lazy Quantifiers 사용

필요한 만큼만 일치하도록 ?를 사용하여 탐욕적인 패턴을 완화합니다.

예제:

/".*?"/


19. 정규 표현식 관련 도구 및 리소스

19.1 온라인 정규 표현식 테스트기

  • **Regex101:** 다양한 언어와 플래그를 지원하는 강력한 테스트 도구.
  • **RegExr:** 실시간으로 패턴을 테스트하고 설명을 제공합니다.
  • **Regex Tester:** 간단한 인터페이스로 패턴을 테스트할 수 있습니다.

19.2 PHP 내장 함수 문서

19.3 정규 표현식 참고 자료


20. PHP 정규 표현식 요약

PHP 정규 표현식은 문자열 처리를 위한 강력한 도구로, 데이터 검증, 추출, 변환 등에 효과적으로 사용됩니다. 정규 표현식의 기본 개념부터 고급 기능까지 이해하고, PHP의 PCRE 함수를 적절히 활용하면 복잡한 문자열 작업을 효율적으로 처리할 수 있습니다. 또한, 성능 최적화와 보안 고려사항을 준수하여 안전하고 효율적인 코드를 작성하는 것이 중요합니다.

이 가이드를 통해 PHP 정규 표현식의 다양한 기능과 활용법을 익히고, 실용적인 예제를 통해 실제 프로젝트에 적용해보세요. 잘 설계된 정규 표현식은 웹 애플리케이션의 기능성과 보안성을 크게 향상시킬 것입니다.


21. 추가 자료 및 참고 링크


이 가이드를 통해 PHP 정규 표현식의 기본 개념부터 고급 기능까지 종합적으로 이해하고, 다양한 예제를 통해 실제 프로젝트에 효과적으로 적용할 수 있게 되셨기를 바랍니다. PHP 정규 표현식을 효과적으로 활용하여 더욱 안전하고 유연한 웹 애플리케이션을 개발해보세요.


copyright ⓒ 스타트코딩 all rights reserved.
이메일 : startcodingim@gamil.com