정규 표현식은 강력하지만 PHP는 읽을 수 있는 것으로 알려져 있지 않습니다. 정규 표현식을 유지하는 것은 간단한 작업이 아닙니다.
PHP는 PCRE (PHP 7.3 이후 PCRE2) 정규식 플레이버를 사용하며 읽기 쉽고 설명이 필요하며 유지 관리하기 쉬운 정규식을 작성하는 데 도움이 되는 몇 가지 고급 함수가 함께 제공됩니다. PHP의 필터 및 ctype 함수는 URL, 이메일 및 영숫자 값과 같은 유효성 검사를 제공하므로 처음에는 정규식을 사용하지 않는 데 도움이 됩니다.
IDE는 더 좋은 구문 강조 표시를 제공하여 특정 정규 표현식을 더 읽기 쉽고 이해하기 쉽게 만들고 이를 개선하기 위한 빠른 수정을 제공 할 수도 있습니다. 그러나 처음부터 자명하고 읽기 쉬운 정규식을 작성하면 장기적으로 도움이 될 수 있습니다.
다음은 PHP에서 더 나은 정규 표현식을 개선하고 작성하기 위한 몇 가지 팁입니다. 이전 PHP 버전 (PHP 7.3 이전)에서는 작동하지 않을 수 있습니다. 또한 이러한 개선 사항을 사용하면 정규식이 다른 언어에 덜 이식 될 수 있습니다. 예를 들어, 명명 된 캡처는 이전 PHP 버전에서도 지원되지만 JavaScript에서는 명명 된 캡처 기능이 ECMAScript 2018에서만 추가되었습니다.
구분자 선택
각각의 모든 정규 표현식에는 표현식과 플래그라는 두 부분이 있습니다. 정규식은 두 문자 내에 포함되며 그 뒤에 선택적 플래그가 있습니다.
아래 정규식을 고려하십시오.
/(foo|bar)/i
모든 정규식에서 구분 기호 문자에는 표현식과 선택적 플래그가 포함됩니다. 위의 예에서 (foo | bar)는 표현식 자체이고 i는 플래그 / 수정 자입니다. / 문자는 구분 기호입니다.
슬래시 (/)는 자주 구분자로 사용되지만 ~,!, @, #, $ 등과 같은 모든 문자가 될 수 있습니다. 영숫자 문자 (AZ, az 및 0-9), 다중 바이트 문자 ( Emojis) 및 백 슬래시 (\)는 구분 기호로 사용할 수 없습니다.
또는 중괄호를 구분자로 사용할 수도 있습니다. {}, (), [] 및 <>가 포함 된 정규식도 허용되며 컨텍스트에 따라 더 읽기 쉬울 수 있습니다.
표현식 내에서 발생하는 모든 구분 문자를 이스케이프해야 하므로 구분 기호를 선택하는 것이 중요합니다. 정규식 내에서 이스케이프 된 문자가 적을수록 더 읽기 쉽습니다. 메타 문자 (예 : ^, $, 중괄호 및 정규 표현식에서 특별한 의미를 갖는 기타 문자)를 선택하지 않으면 이스케이프 되는 문자 수를 줄일 수 있습니다.
슬래시는 정규식 구분 기호로 일반적이지만 URI를 포함하는 정규식에는 적합하지 않은 경우가 많습니다.
preg_match('/^https:\/\/example.com\/path/i', $uri);
슬래시 (/)는 위의 예에서 구분 기호를 선택하는 데 적합하지 않습니다. 표현식 자체에도 슬래시가 포함되어 있으므로 이제 이스케이프해야하므로 스 니펫을 읽을 수 없습니다.
구분 기호를 /에서 #으로 바꾸는 것만으로도 이스케이프 문자가 더 이상 포함되지 않기 때문에 표현식을 더 쉽게 읽을 수 있습니다.
- /^https:\/\/example.com\/path/i
+ #^https://example.com/path#i
- preg_match('/^https:\/\/example.com\/path/i', $uri);
+ preg_match('#^https://example.com/path#i', $uri);
이스케이프 문자 줄이기
더 나은 구분 기호를 선택하는 것보다 한 단계 더 나아가 정규 표현식에 사용되는 이스케이프 문자 수를 줄이는 다른 방법이 있습니다.
정규식에서 특정 메타 문자는 대괄호 (문자 클래스) 안에 사용될 때 메타 문자로 간주되지 않습니다. 예를 들어,., *, + 및 $ 문자 (특히)는 정규 표현식에서 특수 기능을 수행하지만 대괄호 내부에는 없습니다.
/Username: @[a-z\.0-9]/
위의 식에서 도트 문자 (.)는 백 슬래시 (\.)로 이스케이프되지만. 문자는 대괄호 안에 사용될 때 메타 문자가 아닙니다.
또한 일부 문자는 범위의 일부가 아닌 경우 이스케이프 할 필요가 없습니다.
예를 들어 대시 문자 (-)는 두 문자 사이에 사용 된 경우 문자 범위를 나타내지 만 다른 곳에서 사용 된 경우 특별한 기능을 제공하지 않습니다. 정규식 / [AZ] /에서 대시 문자-는 A에서 Z까지의 일치 범위를 만드는 데 사용됩니다. 대시 문자가 이스케이프되면 (/ [A \ -Z] /) 정규식은 문자와 만 일치합니다. A, Z,-. 대시 문자 (\-)를 이스케이프하는 대신 단순히 대시 문자를 대괄호 끝으로 이동하면 이스케이프해야 하는 문자 수가 줄어 듭니다. 정규식 / [A \ -Z] /는 [AZ-]와 동일하지만 후자가 더 읽기 쉽습니다.
이스케이프 문자를 과도하게 사용한다고 해서 정규식이 실패하는 것은 아니지만 가독성을 크게 떨어 뜨릴 수 있습니다.
- /Price: [0-9\-\$\.\+]+/
+ /Price: [0-9$.+-]+/
특별한 의미가 없는 문자가 이스케이프 된 경우 정규 표현식에 오류가 발생하지만 상황에 따라 달라지지 않는 플래그 X가 있습니다 (예 : 중괄호에 따라 오류 발생 등).
preg_match('/x\yz/X', ''); // "y" is not a special character, but escaped.
Warning: preg_match(): Compilation failed: unrecognized character follows \ at offset 2 in ... on line ...
캡처되지 않은 그룹
정규식에서 () 중괄호는 캡처 그룹을 시작합니다. 일치하는 결과는 일치 목록에 전달됩니다.
Price : € 24 텍스트에서 주어진 텍스트에서 가격을 추출하는 예제 정규식을 고려하십시오.
$pattern = '/Price: (£|€)(\d+)/';
$text = 'Price: €24';
preg_match($pattern, $text, $matches);
위의 스니펫에는 두 개의 캡처 그룹이 있습니다. 첫 번째 그룹은 통화 ((£ | €))에 대한 그룹이고 그 뒤에 숫자 값이 있습니다.
$matches 변수는 두 캡처 그룹의 일치 결과를 저장합니다.
var_dump($matches);
array(3) {
[0]=> string(12) "Price: €24"
[1]=> string(3) "€"
[2]=> string(2) "24"
}
전혀 캡처 할 필요가 없거나 $matches 배열에 전달되는 일치 수를 제한 할 필요가 없는 정규식에서 비 캡처 그룹이 도움이 될 수 있습니다.
비 캡처 그룹의 구문은 (? :로 시작하고)로 끝나는 중괄호입니다. Regex 엔진은 중괄호 안의 표현식을 어설 션하지만 일치 항목으로 반환되지 않습니다. 즉, 캡처되지 않았습니다.
위의 표현식이 숫자 값에만 관심이 있는 경우 (£ | €) 캡처 링 그룹을 비 캡처 그룹 ((? : £ | €)으로 바꿀 수 있습니다.
$pattern = '/Price: (?:£|€)(\d+)/';
$text = 'Price: €24';
preg_match($pattern, $text, $matches);
var_dump($matches);
array(2) {
[0]=> string(12) "Price: €24"
[1]=> string(2) "24"
}
여러 그룹이 있는 정규식에서 사용되지 않는 그룹을 캡처 하지 않는 그룹으로 전환하면 $ matches 변수에 할당 된 데이터의 양을 줄일 수 있습니다.
명명 된 캡처
캡처 되지 않는 그룹과 유사하게 명명 된 캡처를 사용하면 특정 그룹을 캡처하고 이름을 지정할 수 있습니다. 반환 된 값의 이름을 지정하는 데 도움이 될뿐만 아니라 정규식 자체의 일부 이름도 지정할 수 있습니다.
위의 동일한 가격 일치 예제를 사용하여 명명 된 캡처 그룹은 각 캡처 그룹에 이름을 부여 할 수 있습니다.
/Price: (?<currency>£|€)(?<price>\d+)/
명명 된 캡처 그룹의 구문은 (? <, 그룹 이름,)으로 끝납니다.
위의 예에서 (? <currency> £ | €)는 이름이 currency 인 명명 된 캡처 그룹이고 (? <price> \ d +)는 이름이 price입니다. 이름은 정규식을 읽을 때 약간의 컨텍스트를 제공하지만 일치 값 배열의 값에 이름을 지정하는 방법도 제공합니다.
$pattern = '/Price: (?<currency>£|€)(?<price>\d+)/';
$text = 'Price: €24';
preg_match($pattern, $text, $matches);
var_dump($matches);
array(5) {
[0]=> string(12) "Price: €24"
+ ["currency"]=> string(3) "€"
[1]=> string(3) "€"
+ ["price"]=> string(2) "24"
[2]=> string(2) "24"
}
$matches 배열은 이제 일치 된 값의 이름과 위치 값을 포함합니다.
명명 된 캡처 그룹을 사용하면 $matches 값을 쉽게 사용하고 캡처 그룹의 이름을 유지하여 나중에 정규식을 쉽게 변경할 수 있습니다.
기본적으로 이름이 중복 된 캡처 그룹은 허용되지 않으며 오류가 발생합니다. PHP 경고 : preg_match() : 컴파일 실패 : 두 개의 명명 된 하위 패턴이 오프셋에서 동일한 이름 (PCRE2_DUPNAMES가 설정되지 않음)을 가짐 line .... J 수정자를 사용하여이 중복 명명 된 캡처 그룹을 명시 적으로 허용 할 수 있습니다.
/Price: (?<currency>£|€)?(?<price>\d+)(?<currency>£|€)?/J'
이 정규식을 사용하면 이름이 currency 인 두 개의 캡처 그룹이 있으며 J 플래그와 함께 명시 적으로 허용됩니다. 문자열과 일치하면 명명 된 캡처 값에 대한 마지막 일치 만 반환 되지만 위치 값 (0, 1, 2, ...)에는 모든 일치 항목이 포함됩니다.
$pattern = '/Price: (?<currency>£|€)?(?<price>\d+)(?<currency>£|€)?/J';
$text = 'Price: €24£';
preg_match($pattern, $text, $matches);
var_dump($matches);
array(6) {
[0]=> string(14) "Price: €24£"
["currency"]=> string(2) "£"
[1]=> string(3) "€"
["price"]=> string(2) "24"
[2]=> string(2) "24"
[3]=> string(2) "£"
}
주석 사용
일부 정규식은 매우 길고 여러 줄로 확장됩니다.
개별 하위 패턴 또는 어설 션을 주석 처리하는 동안 정규식을 연결하면 가독성을 높이고 커밋을 검토 할 때 더 작은 diff 출력을 제공 할 수 있습니다.
- $pattern = '/Price: (?<currency>£|€)(?<price>\d+)/i';
+ $pattern = '/Price: ';
+ $pattern .= '(?<currency>£|€)'; // Capture currency symbols £ or €
+ $pattern .= '(?<price>\d+)'; // Capture price without decimals.
+ $pattern .= '/i'; // Flags: Case-insensitive
또는 정규식 자체에 주석을 추가 할 수 있습니다.
엔진이 모든 공백 문자를 무시하도록 하는 정규 표현식 플래그 x가있어 표현식을 펼치거나 정렬하거나 여러 줄로 분할 할 수도 있습니다.
- /Price: (?<currency>£|€)(?<price>\d+)/i
+ /Price: \s (?<currency>£|€) (?<price>\d+) /ix
/Price : (? <currency> £ | €) (? <price> \ d +) / i에서 엔진은 Price : 문자열 바로 뒤의 공백 문자와 일치하지만 x 플래그를 사용하면 모든 공백이 무시됩니다. 공백을 일치 시키려면 \ s 특수 문자를 사용하십시오.
또한 x 플래그를 사용하면 # 문자는 PHP의 // 및 # 주석 구문과 유사하게 인라인 주석을 시작합니다.
하위 패턴의 논리적 그룹 주위에 더 많은 간격을 두면 패턴을 더 읽기 쉽게 만들 수 있습니다. 그러나 더 나은 방법은 표현식을 여러 줄로 나누고 주석을 추가하는 것입니다.
- /Price: (?<currency>£|€)(?<price>\d+)/i
+ /Price: # Check for the label "Price:"
+ \s # Ensure a white-space after.
+ (?<currency>£|€) # Capture currency symbols £ or €
+ (?<price>\d+) # Capture price without decimals.
+ /ix
PHP 변수에 저장할 때 Heredoc/Nowdoc을 사용하면 서식을 유지할 수 있습니다. PHP 7.3부터 heredoc/nowdoc 구문도 더 완화되었습니다.
$pattern = <<<PATTERN
/Price: # Check for the label "Price:"
\s # Ensure a white-space after.
(?<currency>£|€) # Capture currency symbols £ or €
(?<price>\d+) # Capture price without decimals.
/ix # Flags: Case-insensitive
PATTERN;
preg_match($pattern, 'Price: £42', $matches);
명명 된 문자 클래스
정규 표현식은 문자 클래스를 지원하며, 정규 표현식을 면밀히 살펴보면서 동시에 더 읽기 쉽게 만들 수 있습니다.
\d는 아마도 가장 자주 사용되는 문자 클래스 일 것입니다. \ d는 단일 숫자를 나타내며 [0-9] (비 유니 코드 모드)와 같습니다. 또한 \ D는 \ d의 역이며 [^ 0-9]와 같습니다.
숫자를 꼼꼼하게 찾는 정규 표현식과 숫자가 아닌 정규 표현식은 기능을 변경하지 않고도 단순화 할 수 있습니다.
- /Number: [0-9][^0-9]/
+ /Number: \d\D/
정규식은 몇 가지 더 많은 문자 클래스를 지원하므로 차이를 더욱 돋보이게 만들 수 있습니다.
- /[A-Za-z0-9_]/
+ /\w/
- /[A-Za-z0-9]/
+ /[[:xdigit:]]/
- / \t\r\n\v\f/
+ /\s/
유니 코드 지원 (/ u 플래그)과 함께 정규식을 사용하는 경우 여러 문자 클래스를 사용할 수 있습니다. 유니 코드 명명 된 문자 클래스에는 \ p {EXAMPLE} 패턴이 있습니다. 여기서 EXAMPLE은 문자 클래스의 이름입니다. 대문자 P (예 : \ P {FOO})를 사용하는 것은 해당 문자 클래스의 역입니다.
예를 들어, \ p {Currency_Symbol}은 현재 및 미래의 모든 통화 기호에 대해 명명 된 문자 클래스입니다. 또한 \ p {Sc} (symbol-currency)와 같은 더 짧은 형식을 가집니다.
$pattern = '/Price: \p{Sc}\d+/u';
$text = 'Price: ¥42';
캐릭터 클래스는 캐릭터에 대한 사전 지식 없이도 클래스 캡처 / 매칭을 허용합니다. 향후에 도입 될 새로운 통화 기호는 해당 정보가 다음 유니 코드 데이터베이스 업데이트에 포함되는 즉시 일치하기 시작할 것입니다.
유니 코드 문자 클래스에는 모든 유니 코드 스크립트에 대한 매우 유용한 스크립트 클래스 목록도 포함되어 있습니다. 예를 들어 \ p {Sinhala}는 신 할라 어의 모든 문자를 나타내며 \ x {0D80}-\ x {0DFF}와 같습니다.
- $pattern = '/[\x{0D80}-\x{0DFF}]/u';
+ $pattern = '/\p{Sinhala}]/u';
$text = 'පීඑච්පී.වොච්`;
$contains_sinhala = preg_match($pattern, $text);
https://php.watch/articles/php-regex-readability
등록된 댓글이 없습니다.