1. 2007.12.17 정규식 예제 [jsscript]
  2. 2007.12.17 재사용성을 높인 PHP 프레임워크

정규식 예제 [jsscript]

없어서 올립니당...영문정규식은 어느분이 링크했던뎅..ㅋ....나도공부해야징;;
출처:
©2000 Microsoft Corporation. All rights reserved.
퍼온곳: 도움말 파일이라 다운로드해서 봐도 됨;;;; ㅡㅡ;
http://venture.inchon.ac.kr/ius_bbs/view.php?id=909&code=develop

다음 내용은 정규식에 대한 일반적인 사항을 소개하기 위한 것입니다.

각 항목을 읽기만 해도 어느 정도 이해할 수는 있지만, 이러한 항목에 들어 있는 대부분의 정보는 이전에 소개된 기능이나 개념을 이해하고 있어야 그 내용을 파악할 수 있습니다. 그러므로 전체적으로 잘 이해하려면 다음 항목을 순서대로 읽는 것이 좋습니다.

정규식 가이드는 다음과 같은 항목으로 구성됩니다.
정규식

정규식의 기원

정규식 사용

정규식 구문

정규식 만들기

우선 순위

일반 문자

특수 문자

인쇄할 수 없는 문자

문자 검색

한정 기호

앵커

대체 및 그룹화

역참조
--------------------------------------------------------------------------
정규식
이전에 정규식을 다뤄 본 경험이 없으면 용어와 개념이 익숙하지 않겠지만 생각처럼 생소하지는 않을 것입니다.

하드 디스크에서 파일을 검색하는 방법에 대해 생각해 보십시오. 대부분의 경우 ? 및 * 문자를 사용하여 파일을 찾습니다. ? 문자는 파일 이름에서 문자 하나를 찾는 반면 *는 0개 이상의 문자를 찾습니다. 예를 들어, 'data?.dat'와 같은 패턴은 다음과 같은 파일을 찾습니다.

data1.dat

data2.dat

datax.dat

dataN.dat

? 문자 대신 * 문자를 사용하면 좀 더 많은 파일을 찾게 됩니다. 'data*.dat'는 다음과 같은 파일을 찾습니다.

data.dat

data1.dat

data2.dat

data12.dat

datax.dat

dataXYZ.dat

이러한 파일 검색 방법은 매우 유용하지만 제한되어 있습니다. ? 및 * 와일드카드 문자의 제한된 기능으로 정규식의 기능을 짐작할 수 있지만 정규식은 훨씬 더 강력하고 융통성이 있습니다.

----------------------------------------------------------------------
정규식의 기원
정규식의 기원을 추적해 보면 인간 신경계의 작용 방법에 대한 초기 연구로까지 올라갑니다. 신경 생리학자인 Warren McCulloch와 Walter Pitts는 이러한 신경계를 설명할 수 있는 수학적 방법을 개발했습니다.

1956년 미국 수학자인 Stephen Kleene은 McCulloch와 Pitts의 초기 연구를 기반으로 정규식의 개념을 소개한 Representation of Events in Nerve Nets라는 논문을 발표했습니다. 그가 "정규 집합의 대수학"이라고 표현한 것을 설명하는 용어가 "정규식"입니다.

그 이후로 그의 연구는 Unix의 핵심 개발자인 Ken Thompson이 만든 초기 계산 검색 알고리즘에 적용되었습니다. 그러나 정규식이 실제로 적용된 최초의 응용 프로그램은 qed라는 Unix 편집기였습니다.

그리고 그 나머지는 말 그대로 옛날 이야기입니다. 정규식은 그 후 텍스트 기반 편집기 및 검색 도구에서 중요한 부분을 차지하게 되었습니다.

----------------------------------------------------------------------------
정규식 사용
일반적인 찾기 및 바꾸기 작업에서 찾을 문자열을 정확하게 입력해야 합니다. 이 기술은 정적 텍스트에서 이루어지는 간단한 찾기 및 바꾸기 작업에는 적합하지만, 융통성이 부족하며 동적 텍스트에서는 찾기 작업을 수행하기가 어렵다는 단점이 있습니다.

정규식을 통해 다음을 수행할 수 있습니다.

문자열에 있는 패턴을 테스트합니다. 예를 들어, 입력 문자열을 테스트하여 전화 번호 패턴 또는 신용 카드 번호 패턴이 문자열에 있는지 볼 수 있습니다. 이를 데이터 유효성이라고 합니다.
텍스트를 바꿉니다. 정규식을 사용하여 문서에서 특정 텍스트를 식별하고 이를 완전히 제거하거나 다른 텍스트로 바꿀 수 있습니다.
패턴 일치를 기반으로 문자열에서 부분 검색 문자열을 추출합니다. 문서 또는 입력 필드에서 특정 텍스트를 찾을 수 있습니다.
예를 들어, 전체 웹 사이트를 검색하여 오래된 자료를 제거하고 일부 HTML 서식 태그를 바꿔야 할 경우 정규식을 사용하면 찾는 자료 또는 HTML 서식 태그가 특정 파일에 있는지 여부를 테스트할 수 있습니다. 이 방법을 사용하면 영향을 받는 파일의 범위를 제거되었거나 변경된 내용이 있는 파일로 좁힐 수 있습니다. 그런 다음 정규식을 사용하여 오래된 자료를 제거하고 대체할 태그를 찾아서 바꿀 수 있습니다.

문자열 처리 기능이 지원되지 않는 언어에서도 정규식을 유용하게 사용할 수 있습니다. Visual Basic에 포함된 VBScript에는 다양한 문자열 처리 기능이 있는 반면 JScript에는 C와 마찬가지로 이러한 기능이 없습니다. 정규식은 JScript의 문자열 처리 기능을 상당히 향상시켰을 뿐만 아니라, 단일 식에서 여러 문자열을 처리할 수 있으므로 VBScript에서도 효율적으로 사용할 수 있습니다.
-------------------------------------------------------------------------------
정규식 구문
정규식은 일반 문자(예: a에서 z)와 메타문자로 알려진 특수 문자로 구성된 텍스트 패턴입니다. 패턴은 텍스트 본문을 검색할 때 일치하는 문자열을 하나 이상 설명합니다. 정규식은 검색되는 문자열과 일치하는 문자 패턴을 찾는 템플릿의 역할을 합니다.

일반적으로 볼 수 있는 몇 가지 정규식 예는 다음과 같습니다.

JScript VBScript 검색
/^\[ \t]*$/ "^\[ \t]*$" 빈 줄을 찾습니다.
/\d{2}-\d{5}/ "\d{2}-\d{5}" 2자리, 하이픈 및 5자리로 구성된 ID 번호를 찾습니다.
/<(.*)>.*<\/\1>/ "<(.*)>.*<\/\1>" HTML 태그를 찾습니다.


아래 표는 정규식 컨텍스트에 사용되는 모든 메타문자와 메타문자의 동작을 보여줍니다.

문자 설명
\ 그 다음 문자를 특수 문자, 리터럴, 역참조, 또는 8진수 이스케이프로 표시합니다. 예를 들어, 'n'은 문자 "n"을 찾고 '\n'은 줄 바꿈 문자를 찾습니다. '\\' 시퀀스는 "\"를 찾고 '\('는 "("를 찾습니다.
^ 입력 문자열의 시작 위치를 찾습니다. Multiline 속성이 설정되어 있으면 ^는 '\n' 또는 '\r'앞의 위치를 찾습니다.
$ 입력 문자열의 끝 위치를 찾습니다. Multiline 속성이 설정되어 있으면 $는 '\n' 또는 '\r'뒤의 위치를 찾습니다.
* 부분식의 선행 문자를 0개 이상 찾습니다. 예를 들어, 'zo*'는 "z", "zoo" 등입니다. *는 {0,}와 같습니다.
+ 부분식의 선행 문자를 한 개 이상 찾습니다. 예를 들어, 'zo+'는 "zo", "zoo" 등이지만 "z"는 아닙니다. +는 {1,}와 같습니다.
? 부분식의 선행 문자를 0개 또는 한 개 찾습니다. 예를 들어, "do(es)?"는 "do" 또는 "does"의 "do"를 찾습니다. ?는 {0,1}과 같습니다.
{n} n은 음이 아닌 정수입니다. 정확히 n개 찾습니다. 예를 들어, 'o{2}'는 "Bob"의 "o"는 찾지 않지만 "food"의 o 두 개는 찾습니다.
{n,} n은 음이 아닌 정수입니다. 정확히 n개 찾습니다. 예를 들어, 'o{2}'는 "Bob"의 "o"는 찾지 않지만 "foooood"의 모든 o는 찾습니다. 'o{1,}'는 "o+"와 같고, 'o{0,}'는 "o*"와 같습니다.
{n,m} m과 n은 음이 아닌 정수입니다. 여기서 m은 n보다 크거나 같습니다. 최소 n개, 최대 m개 찾습니다. 예를 들어, "o{1,3}"은 "fooooood"의 처음 세 개의 o를 찾습니다. "o{0,1}"은 "o?"와 같습니다. 쉼표와 숫자 사이에는 공백을 넣을 수 없습니다.
? 이 문자가 다른 한정 부호(*, +, ?, {n}, {n,}, {n,m})의 바로 뒤에 나올 경우 일치 패턴은 제한적입니다. 기본값인 무제한 패턴은 가능한 많은 문자열을 찾는 데 반해 제한적인 패턴은 가능한 적은 문자열을 찾습니다. 예를 들어, "oooo" 문자열에서 "o+?"는 "o" 한 개만 찾고, "o+"는 모든 "o"를 찾습니다.
. "\n"을 제외한 모든 단일 문자를 찾습니다. "\n"을 포함한 모든 문자를 찾으려면 '[.\n]' 패턴을 사용하십시오.
(pattern) pattern을 찾아 검색한 문자열을 캡처합니다. 캡처한 문자열은 VBScript의 경우 SubMatches 컬렉션, Jscript의 경우 $0...$9 속성을 이용하여 결과로 나오는 Matches 컬렉션에서 추출할 수 있습니다. 괄호 문자인 ( )를 찾으려면 "\(" 또는 "\)"를 사용하십시오.
(?:pattern) pattern을 찾지만 검색한 문자열을 캡처하지 않습니다. 즉, 검색한 문자열을 나중에 사용할 수 있도록 저장하지 않는 비캡처 검색입니다. 이것은 패턴의 일부를 "or" 문자(|)로 묶을 때 유용합니다. 예를 들어, 'industr(?:y|ies)는 'industry|industries'보다 더 경제적인 식입니다.
(?=pattern) 포함 예상 검색은 pattern과 일치하는 문자열이 시작하는 위치에서 검색할 문자열을 찾습니다. 이것은 검색한 문자열을 나중에 사용할 수 있도록 캡처하지 않는 비캡처 검색입니다. 예를 들어, "Windows(?=95|98|NT|2000)"는 "Windows 2000"의 "Windows"는 찾지만 "Windows 3.1"의 "Windows"는 찾지 않습니다. 예상 검색은 검색할 문자열을 찾은 후 예상 검색 문자열을 구성하는 문자 다음부터가 아니라 마지막으로 검색한 문자열 바로 다음부터 찾기 시작합니다.  
(?!pattern) 제외 예상 검색은 pattern과 일치하지 않는 문자열이 시작하는 위치에서 검색할 문자열을 찾습니다. 이것은 검색한 문자열을 나중에 사용할 수 있도록 캡처하지 않는 비캡처 검색입니다. 예를 들어, "Windows(?!95|98|NT|2000)"는 "Windows 3.1"의 "Windows"는 찾지만 "Windows 2000"의 "Windows"는 찾지 않습니다. 예상 검색은 검색할 문자열을 찾은 후 예상 검색 문자열을 구성하는 문자 다음부터가 아니라 마지막으로 검색한 문자열 바로 다음부터 찾기 시작합니다.  
x|y x 또는 y를 찾습니다. 예를 들어, "z|food"는 "z" 또는 "food"를 찾습니다. "(z|f)ood"는 "zood" 또는 "food"를 찾습니다.  
[xyz] 문자 집합입니다. 괄호 안의 문자 중 하나를 찾습니다. 예를 들어, "[abc]"는 "plain"의 "a"를 찾습니다.  
[^xyz] 제외 문자 집합입니다. 괄호 밖의 문자 중 하나를 찾습니다. 예를 들어, "[^abc]"는 "plain"의 "p"를 찾습니다.  
[a-z] 문자 범위입니다. 지정한 범위 안의 문자를 찾습니다. 예를 들어, "[a-z]"는 "a"부터 "z" 사이의 모든 소문자를 찾습니다.  
[^a-z] 제외 문자 범위입니다. 지정된 범위 밖의 문자를 찾습니다. 예를 들어, "[^a-z]"는 "a"부터 "z" 사이에 없는 모든 문자를 찾습니다.  
\b 단어의 경계, 즉 단어와 공백 사이의 위치를 찾습니다. 예를 들어, "er\b"는 "never"의 "er"는 찾지만 "verb"의 "er"는 찾지 않습니다.
\B 단어의 비경계를 찾습니다. "er\B"는 "verb"의 "er"는 찾지만 "never"의 "er"는 찾지 않습니다.
\cx X 가 나타내는 제어 문자를 찾습니다. 예를 들어, \cM은 Control-M 즉, 캐리지 리턴 문자를 찾습니다. x 값은 A-Z 또는 a-z의 범위 안에 있어야 합니다. 그렇지 않으면 c는 리터럴 "c" 문자로 간주됩니다.  
\d 숫자 문자를 찾습니다. [0-9]와 같습니다.
\D 비숫자 문자를 찾습니다. [^0-9]와 같습니다.  
\f 폼피드 문자를 찾습니다. \x0c와 \cL과 같습니다.
\n 줄 바꿈 문자를 찾습니다. \x0a와 \cJ와 같습니다.
\r 캐리지 리턴 문자를 찾습니다. \x0d와 \cM과 같습니다.
\s 공백, 탭, 폼피드 등의 공백을 찾습니다. "[ \f\n\r\t\v]"와 같습니다.
\S 공백이 아닌 문자를 찾습니다. "[^ \f\n\r\t\v]"와 같습니다.  
\t 탭 문자를 찾습니다. \x09와 \cI와 같습니다.
\v 수직 탭 문자를 찾습니다. \x0b와 \cK와 같습니다.
\w 밑줄을 포함한 모든 단어 문자를 찾습니다. "[A-Za-z0-9_]"와 같습니다.  
\W 모든 비단어 문자를 찾습니다. "[^A-Za-z0-9_]"와 같습니다.  
\xn n을 찾습니다. 여기서 n은 16진수 이스케이프 값입니다. 16진수 이스케이프 값은 정확히 두 자리여야 합니다. 예를 들어, '\x41'은 "A"를 찾고 '\x041'은 '\x04'와 "1"과 같습니다. 정규식에서 ASCII 코드를 사용할 수 있습니다.
\num num을 찾습니다. 여기서 num은 양의 정수입니다. 캡처한 문자열에 대한 역참조입니다. 예를 들어, '(.)\1'은 연속적으로 나오는 동일한 문자 두 개를 찾습니다.
\n 8진수 이스케이프 값이나 역참조를 나타냅니다. \n 앞에 최소한 n개의 캡처된 부분식이 나왔다면 n은 역참조입니다. 그렇지 않은 경우 n이 0에서 7 사이의 8진수이면 n은 8진수 이스케이프 값입니다.
\nm 8진수 이스케이프 값이나 역참조를 나타냅니다. \nm 앞에 최소한 nm개의 캡처된 부분식이 나왔다면 nm은 역참조입니다. \nm 앞에 최소한 n개의 캡처가 나왔다면 n은 역참조이고 뒤에는 리터럴 m이 옵니다. 이 두 경우가 아닐 때 n과 m이 0에서 7 사이의 8진수이면 \nm은 8진수 이스케이프 값 nm을 찾습니다.
\nml n이 0에서 3 사이의 8진수이고 m과 l이 0에서 7 사이의 8진수면 8진수 이스케이프 값 nml을 찾습니다.
\un n은 4 자리의 16진수로 표현된 유니코드 문자입니다. 예를 들어, \u00A9는 저작권 기호(&copy;)를 찾습니다.


-------------------------------------------------------------------------------
정규식 만들기
정규식은 산술식을 만드는 것과 같은 방법으로 구성됩니다. 즉, 작은 식은 다양한 메타문자 및 연산자를 사용하여 결합되며 이는 더 큰 식을 만듭니다.

한 쌍의 구분 기호 안에 식 패턴의 다양한 구성 요소를 넣어서 정규식을 구성합니다. JScript에서 구분 기호는 한 쌍의 슬래시(/) 문자입니다. 예를 들어 다음과 같습니다.

/expression/
VBScript에서는 한 쌍의 따옴표("")로 정규식을 구분합니다. 예를 들어 다음과 같습니다.

"expression"
위의 두 예제에서 정규식 패턴(expression)은 RegExp 개체의 Pattern 속성에 저장됩니다.

정규식의 구성 요소는 개별 문자, 문자 집합, 문자 범위, 문자 간 선택 또는 이러한 모든 구성 요소의 조합일 수 있습니다.


-------------------------------------------------------------------------------
우선 순위
일단 정규식을 구성하면 산술식처럼 평가됩니다. 즉, 왼쪽에서 오른쪽으로 평가되고 우선 순위에 따라 이루어집니다.

아래 표는 다양한 정규식 연산자의 우선 순위를 가장 높은 것에서 가장 낮은 것까지 보여줍니다.

연산자 설명
\ 이스케이프
(), (?:), (?=), [] 괄호 및 대괄호
*, +, ?, {n}, {n,}, {n,m} 한정 기호
^, $, \anymetacharacter 앵커 및 시퀀스
| 대체


-----------------------------------------------------------------------------
일반 문자
일반 문자는 명시적으로 메타문자로 지정되지 않은 인쇄할 수 있는 문자 및 인쇄할 수 없는 문자로 구성됩니다. 여기에는 모든 대소문자, 모든 숫자, 모든 구두점 및 일부 기호가 포함됩니다.

가장 간단한 정규식 양식은 검색된 문자열에서 문자 자체를 찾는 단일 일반 문자입니다. 예를 들어, 단일 문자 패턴 'A'는 검색된 문자열에 나타날 때마다 'A' 문자를 찾습니다. 단일 문자 정규식 패턴의 몇 가지 예는 다음과 같습니다.

/a/
/7/
/M/
이와 동일한 VBScript 단일 문자 정규식은 다음과 같습니다.

"a"
"7"
"M"
여러 개의 단일 문자를 결합하여 좀 더 큰 식을 만들 수 있습니다. 예를 들어, 다음 JScript 정규식은 단일 문자 식인 'a', '7' 및 'M'을 결합하여 만들어진 식입니다.

/a7M/
이와 동일한 VBScript 식은 다음과 같습니다.

"a7M"
여기에는 연결 연산자가 필요하지 않으며 문자를 하나씩 나열하기만 하면 됩니다.

-------------------------------------------------------------------------------
특수 문자
문자열을 찾을 때 특별히 처리해야 하는 여러 가지 메타문자가 있습니다. 이러한 특수 문자를 찾으려면 먼저 이를 이스케이프해야 합니다. 즉, 특수 문자 앞에 백슬래시 문자(\)를 입력해야 합니다. 다음 표는 특수 문자와 그 의미를 보여줍니다.

특수 문자 설명
$ 입력 문자열의 끝 위치를 찾습니다. RegExp 개체의 Multiline 속성이 설정되어 있으면 $는 '\n' 또는 '\r' 앞의 위치를 찾습니다. $ 문자 자체를 찾으려면 \$를 사용하십시오.
( ) 부분식의 시작과 끝을 찾습니다. 부분식은 나중에 사용할 수 있도록 캡처할 수 있습니다. 이 문자를 찾으려면 \( 및 \)를 사용하십시오.
* 부분식의 선행 문자를 0개 이상 찾습니다. * 문자를 찾으려면 \*를 사용하십시오.
+ 부분식의 선행 문자를 한 개 이상 찾습니다. + 문자를 찾으려면 \+를 사용하십시오.
. 줄 바꿈 문자 \n을 제외한 모든 단일 문자를 찾습니다. .를 찾으려면 \.를 사용하십시오.
[  대괄호 식의 시작 위치를 찾습니다. [를 찾으려면 \[를 사용하십시오.  
? 부분식의 선행 문자를 0개 또는 한 개 찾거나 제한적인 한정 기호를 나타냅니다. ?를 찾으려면 \?를 사용하십시오.
\ 그 다음 문자를 특수 문자, 리터럴, 역참조 또는 8진수 이스케이프로 표시합니다. 예를 들어, 'n'은 문자 'n'을 찾고 '\n'은 줄 바꿈 문자를 찾습니다. '\\' 시퀀스는 "\"를 찾고 '\('는 "("를 찾습니다.
^ 입력 문자열의 시작 위치를 찾습니다. 대괄호 식에서 사용될 때는 대괄호에 포함된 문자 집합을 제외한 문자열을 찾습니다. ^ 문자 자체를 찾으려면 \^를 사용하십시오.
{ 한정 기호 식의 시작 위치를 찾습니다. {를 찾으려면 \{를 사용하십시오.
| 두 항목 간의 선택을 나타냅니다. |를 찾으려면 \|를 사용하십시오.

-----------------------------------------------------------------------------

인쇄할 수 없는 문자
가끔 사용하는 문자 중 인쇄할 수 없는 유용한 문자가 여러 개 있습니다. 아래 표에서는 인쇄할 수 없는 이들 문자를 나타낼 때 사용되는 이스케이프 시퀀스를 보여줍니다.

문자 의미
\cx x가 나타내는 제어 문자를 찾습니다. 예를 들어, \cM은 Control-M 즉, 캐리지 리턴 문자를 찾습니다. x 값은 A-Z 또는 a-z의 범위 안에 있어야 합니다. 그렇지 않으면 c는 리터럴 "c" 문자로 간주됩니다.
\f 폼피드 문자를 찾습니다. \x0c와 \cL과 같습니다.
\n 줄 바꿈 문자를 찾습니다. \x0a와 \cJ와 같습니다.
\r 캐리지 리턴 문자를 찾습니다. \x0d와 \cM과 같습니다.
\s 공백, 탭, 폼피드 등의 공백을 찾습니다. "[ \f\n\r\t\v]"와 같습니다.
\S 공백이 아닌 문자를 찾습니다. "[^ \f\n\r\t\v]"와 같습니다.  
\t 탭 문자를 찾습니다. \x09와 \cI와 같습니다.
\v 수직 탭 문자를 찾습니다. \x0b와 \cK와 같습니다.


-------------------------------------------------------------------------------
JScript  언어 참조

--------------------------------------------------------------------------------

문자 일치
마침표(.)는 문자열에서 줄 바꿈 문자(\n)를 제외한 단일 인쇄 또는 비인쇄 문자를 찾습니다. 다음 JScript 정규식은 'aac', 'abc', 'acc', 'adc' 뿐만 아니라 'a1c', 'a2c', a-c' 및 a#c'를 나타내기도 합니다.

/a.c/
이와 동일한 VBScript 정규식은 다음과 같습니다.

"a.c"
입력 문자열의 일부에 마침표(.)가 있는 파일 이름이 포함된 문자열을 찾을 경우 정규식에서 마침표 앞에 백슬래시(\) 문자를 입력하십시오. 예를 들어 다음 JScript 정규식은 'filename.ext'를 찾습니다.

/filename\.ext/
이와 동일한 VBScript 식은 다음과 같습니다.

"filename\.ext"
이러한 식은 제한적이어서 여기서는 임의의 단일 문자만 찾을 수 있습니다. 그러나 목록에서 지정된 문자를 찾을 경우 유용합니다. 예를 들어, Chapter 1, Chapter 2 등과 같이 숫자로 표시된 장 제목이 들어 있는 입력 텍스트가 있을 때 이러한 장 제목을 찾을 수 있습니다.

대괄호 식
하나 이상의 개별 문자를 대괄호([ ]) 안에 입력하여 찾을 문자 목록을 만들 수 있습니다. 문자를 대괄호로 묶으면 목록은 대괄호 식이 됩니다. 다른 곳에서와 마찬가지로 대괄호 안에서 일반 문자는 문자 자체를 나타내므로 입력 텍스트에서 문자 자체를 찾지만 대괄호 식에서 대부분의 특수 문자는 문자 자체를 찾지 않습니다. 다음은 몇 가지 예외입니다.

']' 문자는 목록의 첫째 항목이 아닌 경우 이 목록을 종료합니다. 목록에서 ']' 문자 자체를 찾으려면 이를 '[' 바로 다음에 놓으십시오.
'\' 문자는 계속 이스케이프 문자로 사용됩니다. '\' 문자를 찾으려면 '\\'를 사용하십시오.
대괄호 식으로 묶인 문자는 대괄호 식이 나타나는 정규식의 위치에 해당하는 단일 문자만을 찾습니다. 다음 JScript 정규식은 'Chapter 1', 'Chapter 2', 'Chapter 3', 'Chapter 4' 및 'Chapter 5' 등을 검색합니다.

/Chapter [12345]/
VBScript에서 위와 같은 장 제목을 찾으려면 다음을 사용하십시오.

"Chapter [12345]"
단어 'Chapter' 및 이어지는 공백은 대괄호 내에 있는 문자와 관련된 위치에 고정됩니다. 그러면 대괄호 식은 단어 'Chapter' 및 공백 바로 다음에 오는 단일 문자 위치에 해당하는 문자 집합만 지정하므로 여기서는 아홉 번째 문자 위치입니다.

문자 자체 대신 범위를 사용하여 검색 문자를 표현하려면 하이픈 문자(-)를 사용하여 시작하는 문자와 끝나는 문자를 구분합니다. 개별 문자의 문자 값은 범위에서의 상대적 순서를 결정합니다. 다음 JScript 정규식에는 위의 대괄호로 묶인 목록과 같은 범위식이 들어 있습니다.

/Chapter [1-5]/
이와 동일한 VBScript의 식은 다음과 같습니다.

"Chapter [1-5]"
범위가 이러한 방식으로 지정되면 시작하는 값과 끝나는 값이 범위에 포함됩니다. 시작하는 값은 유니코드 정렬 순서에서 끝나는 값 앞에 와야 합니다.

대괄호 식에 하이픈 문자를 포함시켜려면 다음 중 하나를 수행해야 합니다.

백슬래시로 이스케이프합니다.
[\-]
대괄호로 묶은 목록의 시작 또는 끝에 하이픈 문자를 놓습니다. 다음 식은 모든 소문자 및 하이픈을 찾습니다.
[-a-z]
[a-z-]
시작하는 문자 값이 하이픈보다 먼저 오고, 끝나는 문자 값이 하이픈과 같거나 하이픈보다 나중에 오도록 범위를 만듭니다. 다음 두 정규식은 모두 이 요구 사항을 만족합니다.
[!--]
[!-~]
목록 시작에 캐럿(^) 문자를 놓으면 목록이나 범위에 없는 모든 문자를 찾을 수도 있습니다. 목록에서 캐럿 문자가 다른 위치에 나타나면 이는 특별한 의미 없이 캐럿 문자 자체를 찾습니다. 다음 JScript 정규식은 장 제목의 숫자가 5보다 큰 경우를 찾습니다.

/Chapter [^12345]/
VBScript에서 사용하는 경우는 다음과 같습니다.

"Chapter [^12345]"
위의 예제에서 식은 1, 2, 3, 4, 5를 제외하고 9번째 위치에 있는 모든 숫자 문자를 찾습니다. 예를 들어, 'Chapter 7'과 'Chapter 9'를 찾습니다.

하이픈 문자(-)를 사용하여 위와 동일한 식을 나타낼 수 있습니다. JScript의 경우는 다음과 같습니다.

/Chapter [^1-5]/
VBScript의 경우는 다음과 같습니다.

"Chapter [^1-5]"
일반적으로 대괄호 식은 대소문자 또는 숫자 검색을 지정하기 위해 사용됩니다. 다음 JScript 식은 이러한 검색을 지정합니다.

/[A-Za-z0-9]/
이와 동일한 VBScript의 식은 다음과 같습니다.

"[A-Za-z0-9]"


-------------------------------------------------------------------------------
한정 기호
검색 문자가 몇 개인지 모를 때도 있습니다. 이러한 불확실성을 해결하기 위해 정규식에서는 한정 기호의 개념을 지원합니다. 이러한 한정 기호를 사용하면 정규식에서 지정된 구성 요소가 몇 개 나타나야 검색이 참이 되는지를 지정할 수 있습니다.

아래 표에서는 다양한 한정 기호 및 그 의미를 설명합니다.

문자 설명
*  부분식의 선행 문자를 0개 이상 찾습니다. 예를 들어, 'zo*'는 "z" 와 "zoo"를 찾습니다. *는 {0,}와 같습니다.
+  부분식의 선행 문자를 한 개 이상 찾습니다. 예를 들어, 'zo+'는 "zo" 및 "zoo"는 찾지만 "z"는 검색하지는 않습니다. +는 {1,}와 같습니다.
?  부분식의 선행 문자를 0개 이상 찾습니다. 예를 들어, 'do(es)?'는 "do" 또는 "does"의 "do"를 찾습니다. ?는 {0,1}과 같습니다.
{n} n은 음이 아닌 정수입니다. 정확하게 n 개 찾습니다. 예를 들어, 'o{2}'는 "Bob"의 'o'는 찾지 않지만 "food"의 o 두 개는 찾습니다.
{n,}  n은 음이 아닌 정수입니다. 최소한 n 개 찾습니다. 예를 들어, 'o{2,}'는 "Bob"의 'o'는 찾지 않고 "foooood"의 o는 모두 찾습니다. 'o{1,}'는 'o+'와 같고 'o{0,}'는 'o*'와 같습니다.
{n,m}  m 및 n은 음이 아닌 정수며 여기서 n <= m입니다. 최소한 n 개, 최대한 m개 찾습니다. 예를 들어, 'o{1,3}'은 "fooooood"의 처음 o 세 개를 찾습니다. 'o{0,1}'은 'o?'와 같습니다. 쉼표와 숫자 사이에는 공백을 넣을 수 없습니다.


입력 문서가 큰 경우 장 번호는 9를 넘기 쉬우므로 두 자리 또는 세 자리 수의 장 번호를 처리할 수 있는 방법이 필요합니다. 한정 기호는 이러한 기능을 제공합니다. 다음 JScript 정규식은 모든 자리 수의 장 제목을 찾습니다.

/Chapter [1-9][0-9]*/
이와 동일한 VBScript 정규식은 다음과 같습니다.

"Chapter [1-9][0-9]*"
한정 기호는 범위식 다음에 나타납니다. 그러므로 이 경우 한정 기호는 0과 9를 포함한 자리 수를 지정하는 전체 범위식에 적용됩니다.

'+' 한정 기호는 두 번째 위치 또는 그 다음에 꼭 숫자가 올 필요가 없으므로 여기서 사용되지 않습니다. '?' 문자도 장 번호를 두 자리 수로 제한하므로 사용되지 않습니다. 'Chapter'와 공백 문자 다음에 한 자리 수 이상을 찾을 수도 있습니다.

장 번호는 99장으로만 제한되므로 다음 JScript 식을 사용하여 적어도 한 자리 수를 지정할 수 있지만 두 자리 수 이상은 지정할 수 없습니다.

/Chapter [0-9]{1,2}/
VBScript의 경우 다음 정규식을 사용하십시오.

"Chapter [0-9]{1,2}"
위 식의 단점은 99보다 큰 장 번호가 있어도 처음 두 자리 수에 해당하는 장 번호만을 찾는다는 것입니다. 또한 누군가가 Chapter 0을 만들어도 그것까지 찾습니다. 두 자리 수만을 찾는 JScript 식은 아래 예를 사용하는 것이 좋습니다.

/Chapter [1-9][0-9]?/
또는 다음을 사용합니다.

/Chapter [1-9][0-9]{0,1}/
이와 동일한 VBScript 식은 다음과 같습니다.

"Chapter [1-9][0-9]?"
또는 다음과 같습니다.

"Chapter [1-9][0-9]{0,1}"
'*', '+' 및 '?' 한정 기호는 모두 무제한적이라고 하며 가능한 한 텍스트를 많이 찾습니다. 그러나 이렇게 많이 찾을 필요가 없는 경우도 있습니다. 즉, 최소한으로 찾아야 하는 경우도 있습니다.

예를 들어, HTML 문서에 다음과 같은 텍스트가 있고, H1 태그로 묶인 장 제목을 찾는다고 가정합니다.

<H1>Chapter 1 &#8211; Introduction to Regular Expressions</H1>
다음 식은 여는 H1 태그에 있는 보다 작음 기호(<)에서 닫는 H1 태그 끝에 있는 보다 큼 기호(>) 사이에 있는 모든 문자열을 찾습니다.

/<.*>/
VBScript 정규식은 다음과 같습니다.

"<.*>"
실제 검색하려는 문자열이 여는 H1 태그뿐인 경우에 다음 제한적인 식은 <H1>만 찾습니다.

/<.*?>/
또는 다음과 같이 나타냅니다.

"<.*?>"
'?'를 '*', '+' 또는 '?' 한정 기호 다음에 놓으면 식은 무제한적인 검색에서 제한적인 검색 또는 최소 검색으로 변환됩니다.


---------------------------------------------------------------------------------

앵커
지금까지 예는 장 제목이 나타나는 위치를 찾는 작업에 관한 것이었습니다. 공백, 숫자가 이어지는 'Chapter' 문자열의 위치는 실제 장 제목이거나 다른 장에 대한 상호 참조일 수 있습니다. 실제로 장 제목은 항상 줄의 시작 위치에 있으므로 제목만 찾고 상호 참조는 찾지 않는 방법을 만들 수 있습니다.

앵커가 이러한 기능을 제공합니다. 앵커를 사용하면 정규식을 줄의 시작이나 끝으로 고정시킬 수 있습니다. 단어 내부 또는 단어 시작이나 끝에 나타나는 정규식을 만들 수도 있습니다. 다음 표는 정규식 앵커 및 그 의미를 보여줍니다.

문자 설명
^ 입력 문자열의 시작 위치를 찾습니다. Multiline 속성이 설정되어 있으면 ^는 '\n' 또는 '\r'앞의 위치를 찾습니다.
$ 입력 문자열의 끝 위치를 찾습니다. Multiline 속성이 설정되어 있으면 $는 '\n' 또는 '\r'뒤의 위치를 찾습니다.
\b 단어의 경계, 즉 단어와 공백 사이의 위치를 찾습니다. 예를 들어, 'er\b'는 "never"의 "er"는 찾지만 "verb"의 "er"는 찾지 않습니다.
\B 단어의 비경계를 찾습니다. 'er\B'는 "verb"의 "er"는 찾지만 "never"의 "er"는 찾지 않습니다.


앵커와 한정 기호는 함께 사용할 수 없습니다. 줄 바꿈 또는 단어 경계 전후 바로 다음에는 한 자리만 올 수 있으므로 '^*'와 같은 식은 허용되지 않습니다.

텍스트 줄의 시작 위치에 있는 텍스트를 찾으려면 정규식 시작에 '^' 문자를 사용합니다. 이것은 대괄호 식에서 사용하는 '^'와는 다릅니다.

텍스트 줄 끝에 있는 텍스트를 찾으려면 정규식 끝에 '$' 문자를 사용하십시오.

장 제목을 찾을 때 다음 JScript 정규식에서처럼 앵커를 사용할 수 있습니다. 그러면 줄의 시작 위치에서 두 자리까지 장 제목을 찾습니다.

/^Chapter [1-9][0-9]{0,1}/
이와 동일한 VBScript의 정규식은 다음과 같습니다.

"^Chapter [1-9][0-9]{0,1}"
실제로 장 제목이 있는 줄에는 장 제목만 있으므로 장 제목은 줄의 시작 및 끝에 있어야 합니다. 다음 식에서 지정된 검색 문자열은 장만 찾고 상호 참조는 찾지 않습니다. 따라서 이 정규식은 텍스트 줄의 시작과 끝에서만 찾습니다.

/^Chapter [1-9][0-9]{0,1}$/
VBScript 를 사용하는 경우는 다음과 같습니다.

"^Chapter [1-9][0-9]{0,1}$"
단어 경계를 찾는 방법은 약간 다르지만 정규식에 매우 중요한 기능을 추가합니다. 단어 경계는 단어와 공백 사이의 위치입니다. 비단어 경계는 그외 다른 위치입니다. 다음 JScript 식은 단어 'Chapter'의 처음 세 문자가 단어 경계 다음에 나타나므로 이 세 문자를 찾습니다.

/\bCha/
또는 VBScript의 경우는 다음과 같습니다.

"\bCha"
여기서 '\b' 연산자의 위치는 매우 중요합니다. 이 연산자가 검색할 문자열 앞에 올 경우 단어의 시작 부분에서 문자열을 찾고 검색할 문자열 뒤에 올 경우에는 단어의 끝 부분에서 문자열을 찾습니다. 예를 들어, 다음 식은 'ter'가 단어 경계 앞에 오므로 'Chapter'라는 단어에서 'ter'를 찾습니다.

/ter\b/
또는 다음과 같습니다.

"ter\b"
다음 식은 'Chapter'의 'apt'를 찾지만 'aptitude'의 'apt는 찾지 않습니다.

/\Bapt/
또는 다음과 같습니다.

"\Bapt"
왜냐하면 'apt'는 'Chapter'라는 단어에서 비단어 경계에 있지만 'aptitude'라는 단어에서는 단어 경계에 있기 때문입니다. 비단어 경계 연산자의 경우 검색할 문자열이 단어 시작이나 끝과 관계가 없으므로 위치는 중요하지 않습니다.

----------------------------------------------------------------------------------
대체 및 그룹화
대체 정규식은 '|' 문자를 사용하여 두 개 이상의 대안 중 하나를 선택할 수 있습니다. 또한 장 제목 정규식을 확장하여 장 제목 이상의 것을 다루도록 할 수도 있으나 생각처럼 그리 간단하지는 않습니다. 대체 정규식을 사용하면 '|' 문자 양쪽의 식 중 가능한 가장 큰 식을 찾습니다. 다음 JScript 및 VBScript의 식이 줄의 시작과 끝에서 'Chapter' 또는 'Section' 다음에 한 자리 또는 두 자리 수가 나오는 문자열을 찾는다고 생각할 수 있습니다.

/^Chapter|Section [1-9][0-9]{0,1}$/
"^Chapter|Section [1-9][0-9]{0,1}$"
하지만 위의 정규식은 줄 첫부분에 나오는 'Chapter' 또는 줄 끝에 나오는 'Section'과 다음에 모든 수가 오는 경우를 찾습니다. 입력 문자열이 'Chapter 22'일 경우 위의 식은 단어 'Chapter'만 찾고 입력 문자열이 'Section 22'일 경우 식은 'Section 22'를 찾습니다.  그러나 이 정규식은 의도했던 것과는 다르므로 사용자의 의도에 맞는 결과가 나올 수 있는 정규식을 만들어 보겠습니다.

괄호를 사용하여 대체 범위를 제한할 수 있습니다. 즉, 두 단어 'Chapter'와 'Section'만 적용되도록 제한합니다. 그러나 나중에 부분식에 대한 절에서 설명하겠지만, 괄호는 부분식을 만들 때도 사용되므로 그 사용 방법이 까다롭습니다. 위의 정규식에서 적합한 위치에 괄호를 추가하면 'Chapter 1' 또는 'Section 3'과 같은 문자열을 찾는 정규식을 만들 수 있습니다.

다음 정규식은 괄호를 사용하여 'Chapter' 및 'Section'을 그룹화했으므로 식이 제대로 작동하게 됩니다. JScript의 경우는 다음과 같습니다.

/^(Chapter|Section) [1-9][0-9]{0,1}$/
VBScript의 경우는 다음과 같습니다.

"^(Chapter|Section) [1-9][0-9]{0,1}$"
이 식은 제대로 작동하지만 예기치 않은 결과가 발생하기도 합니다. 'Chapter|Section' 앞뒤로 괄호를 넣으면 적절하게 그룹화되고, 이 두 단어 중 하나는 나중에 사용할 수 있도록 캡처됩니다. 위의 식에는 괄호 쌍이 하나뿐이므로 부분 검색 문자열이 하나만 캡처됩니다. 이 부분 검색 문자열은 VBScript의 Submatches 컬렉션을 사용하거나 JScript에 있는 RegExp 개체의 $1-$9 속성을 사용하여 참조할 수 있습니다.

부분 검색 문자열을 캡처하는 것이 바람직할 때도 있고 그렇지 않을 때도 있습니다. 위의 예제에서 실제로 하려는 작업은 괄호를 사용하여 단어 'Chapter' 또는 'Section' 중 선택한 것을 그룹화하는 것입니다. 검색한 문자열을 나중에 참조할 필요는 없습니다. 사실, 부분 검색 문자열을 캡처할 필요가 없으면 이를 사용하지 마십시오. 그러면 정규식에서 이런 문자열을 저장하기 위해 시간과 메모리를 사용하지 않아도 되므로 효율성이 높아집니다.

괄호 안에서 정규식 패턴 앞에 ''?:'를 사용하면 나중에 사용하도록 문자열을 저장하지 않습니다. 다음 예는 위의 정규식을 수정한 것으로, 부분 검색 문자열을 저장하지 않으면서 위의 식과 동일한 기능을 합니다. JScript의 경우는 다음과 같습니다.

/^(?:Chapter|Section) [1-9][0-9]{0,1}$/
VBScript의 경우는 다음과 같습니다.

"^(?:Chapter|Section) [1-9][0-9]{0,1}$"
'?:' 메타문자 외에도 예상 검색에 사용하는 기타 비캡처 메타문자가 두 가지 있습니다. ?=로 지정된 포함 예상 검색은 괄호 안의 정규식 패턴과 일치하는 문자열이 시작되는 곳에서 문자열을 검색합니다. '?!'로 지정된 제외 예상 검색은 정규식과 일치하지 않는 문자열이 시작되는 곳에서 문자열을 검색합니다.

예를 들어, Windows 3.1, Windows 95, Windows 98 및 Windows NT에 대한 참조가 들어 있는 문서가 있습니다. Windows 95, Windows 98 및 Windows NT에 대한 모든 참조를 찾고 이러한 참조를 Windows 2000으로 변경하여 문서를 업데이트해야 한다고 가정합니다. 다음 JScript 정규식은 포함 예상 검색의 예를 보여주고 있으며 Windows 95, Windows 98 및Windows NT를 찾습니다.

/Windows(?=95 |98 |NT )/
이와 동일한 VBScript 식은 다음과 같습니다.

"Windows(?=95 |98 |NT )"
일단 문자열을 찾으면 예상 문자열에 포함된 문자를 제외하고 검색된 텍스트 바로 다음부터 찾기 시작합니다. 예를 들어, 위의 정규식이 'Windows 98'을 찾으면 '98' 다음이 아닌 'Windows' 다음부터 다시 찾습니다.


--------------------------------------------------------------------------------
역참조
정규식의 가장 중요한 기능 중 하나는 나중에 사용하도록 검색된 패턴의 일부를 저장하는 기능입니다. 앞에서 설명했듯이 정규식 패턴 또는 패턴의 일부를 괄호로 묶으면 그 식의 일부를 임시 버퍼에 저장할 수 있습니다. 비캡처 메타문자인 '?:', '?=' 또는 '?!'를 사용하여 저장된 정규식 일부를 무시할 수 있습니다.

캡처된 각 부분 검색 문자열은 정규식 패턴에서 왼쪽에서 오른쪽으로 저장됩니다. 부분 검색 문자열이 저장되는 버퍼 번호는 1부터 시작해서 최대 99개의 부분식까지 저장할 수 있습니다. 서로 다른 버퍼는 '\n'을 사용하여 액세스할 수 있는데 여기서 n은 특정 버퍼를 식별하는 한 자리 또는 두 자리의 십진수입니다.

가장 간단하고 가장 유용한 역참조 응용 프로그램 중 하나는 텍스트에서 동일한 두 단어를 찾는 기능을 제공합니다. 다음 문장을 살펴 보십시오.

Is is the cost of of gasoline going up up?
위 문장의 문제는 단어가 중복되어 있다는 것입니다. 모든 단어마다 일일이 중복 여부를 검사하지 않고 그 문장을 수정하는 방법을 개발하면 좋을 것입니다. 다음 JScript 정규식은 단일 부분식을 사용하여 이 문제를 해결합니다.

/\b([a-z]+) \1\b/gi
이와 동일한 VBScript 식은 다음과 같습니다.

"\b([a-z]+) \1\b"
이 경우 괄호 안의 모든 요소가 부분식이 됩니다. 캡처된 식에는 '[a-z]+'에 지정된 대로 영문자를 하나 이상 포함하고 있습니다. 정규식의 두 번째 부분은 이전에 캡처된 부분 검색 문자열에 대한 참조입니다. 즉, 대괄호 식에서 검색된 단어의 두 번째 경우입니다. '\1'을 사용하여 지정된 첫 번째 부분 검색 문자열을 지정합니다. 단어 경계 메타문자는 구분된 단어만 검색합니다. 그렇게 하지 않으면 이 식에서 "is issued" 또는 "this is"와 같은 구가 제대로 식별되지 않습니다.

JScript 식에서 정규식 다음에 오는 전역 플래그('g')는 입력 문자열에서 찾을 수 있는 가능한 한 많은 검색할 문자열에 식이 적용됨을 나타냅니다. 식 끝의 대소문자 구분 안함('i') 플래그는 대소문자를 구분하지 않도록 지정합니다. 여러 줄 플래그는 줄 바꿈 문자 양쪽에 잠재적으로 검색할 문자열이 있을 수 있음을 지정합니다. VBScript에서는 식에서 여러 가지 플래그를 설정할 수는 없지만 RegExp 개체의 속성을 사용하여 명시적으로 설정할 수 있습니다.

위의 정규식을 사용하면 다음 JScript 코드로 부분 검색 문자열 정보를 사용하여 텍스트의 문자열에 연속으로 나오는 동일한 두 단어를 한 단어로 바꿀 수 있습니다.

var ss = "Is is the cost of of gasoline going up up?.\n";
var re = /\b([a-z]+) \1\b/gim;       //정규식 패턴을 만듭니다.
var rv = ss.replace(re,"$1");   //두 단어를 한 단어로 바꿉니다.
이와 거의 동일한 VBScript 코드는 다음과 같습니다.

Dim ss, re, rv
ss = "Is is the cost of of gasoline going up up?." & vbNewLine
Set re = New RegExp
re.Pattern = "\b([a-z]+) \1\b"
re.Global = True
re.IgnoreCase = True
re.MultiLine = True
rv = re.Replace(ss,"$1")
VBScript 코드에서 전역 플래그, 대소문자 구분 안함 플래그 및 여러 행 플래그는 적절히 명명된 RegExp 개체의 속성을 사용하여 설정됩니다.

replace 메서드에서 $1을 사용하면 처음 저장된 부분 검색 문자열을 참조합니다. 부분 검색 문자열이 하나 이상 있으면 이를 $2, $3 등을 사용하여 계속 참조할 수 있습니다.

역참조를 사용하는 또 다른 방법은 URI(Universal Resource Indicator)를 각 구성 요소로 나누는 것입니다. 다음 URI를 프로토콜(ftp, http 등), 도메인 주소 및 페이지/경로로 나눌 수 있습니다.

http://msdn.microsoft.com:80/scripting/default.htm
다음 정규식은 그 기능을 제공합니다. JScript의 경우는 다음과 같습니다.

/(\w+):\/\/([^/:]+)(:\d*)?([^# ]*)/
VBScript의 경우는 다음과 같습니다.

"(\w+):\/\/([^/:]+)(:\d*)?([^# ]*)"
괄호로 묶은 첫 번째 부분식은 웹 주소의 프로토콜 부분을 캡처하도록 설계되었습니다. 이 부분식은 콜론과 두 개의 슬래시 앞에 오는 단어를 모두 찾습니다. 괄호로 묶은 두 번째 부분식은 주소 중 도메인 주소 부분을 캡처합니다. 이 부분식은 '^', '/' 또는 ':' 문자를 포함하지 않는 문자 시퀀스를 찾습니다. 괄호로 묶은 세 번째 부분식은 웹 사이트 포트 번호가 지정되어 있으면 이를 캡처합니다. 이 부분식은 콜론 다음에 오는 0 이상의 자리 수를 찾습니다. 그리고 마지막으로 괄호로 묶은 네 번째 부분식은 웹 주소로 지정된 경로 및/또는 페이지 정보를 캡처합니다. 이 부분식은 '#' 또는 공백 문자를 제외한 하나 이상의 문자를 찾습니다.

정규식을 위의 URI에 적용하면 부분 검색 문자열에 다음이 포함됩니다.

RegExp.$1은 "http"를 포함합니다.

RegExp.$2는 "msdn.microsoft.com"을 포함합니다.

RegExp.$3은 ":80"을 포함합니다.

RegExp.$4는 "/scripting/default.htm"을 포함합니다.


재사용성을 높인 PHP 프레임워크

재사용성을 높인 PHP 프레임워크

PHP에서 무르익어가는 열매, PEAR

한기철

PEAR는 'PHP Extension and Application Repository'의 약자다. 직역하자면 PHP의 확장과 애플리케이션의 저장소 정도가 될 것이다. 과일의 배를 의미하는 영어 'pear'와 같이 읽으면 된다. 간략하게 PEAR를 정의하자면 재사용성을 높인 PHP 컴포넌트 또는 라이브러리들에 기반을 둔 프레임워크와 이의 배포 방식이라 이야기할 수 있겠다.

소프트웨어를 개발하면서 재사용이 가능한 결과물을 만들고 이러한 결과물들을 재사용하는 것이 미덕으로 여겨지던 시대는 이미 지나간 것 같다. 오히려 전체 결과물에서부터 이를 구성하는 개개의 요소까지 재사용을 고려하지 않는다면 해야 할 일을 하지 않았다는 지적을 받아도 변명할 틈이 없는 것이 현재의 개발자의 입장일 것이다.
이런 재사용을 위해 개인 라이브러리를 구축하기도 하고, 컴포넌트를 만드는 등 노력을 하지만 이런 경우 또한 개인의 재사용 여부를 넘어선 범용성이라는 문제가 다시 남게 된다. 특정 집단 내부에서만 소통되는 방식으로 만들어진 컴포넌트, API, 라이브러리, 프레임워크들은 대중이 알아듣기에는 다소 무리가 있는 사투리일 수 있기 때문이다. 스스로 PHP는 자신 있다고 생각하는 개발자라도 본인이 접하지 못했던 사투리를 알아듣는다는 것은 쉬운 일도 아니고 아울러 적잖은 시간을 필요로 하게 된다.
실세계의 언어에는 '표준어'라는 것이 존재하지만 소프트웨어 개발에 있어서는 그저 대세를 따르는 것이 가장 현명한 접근이 되어줄 뿐이라는 조언 이상이 힘들 것이다. 최소한 스스로 '대세'가 무엇인지를 판단해야 하는 숙제는 여전히 남게 된다. 다행히도 PHP의 경우 PEAR와 PECL이라는 자매 프로젝트가 대세로 인정받고 있다는 사실에 의심의 여지가 없으니 다른 개발 환경보다는 쉽게 앞서의 숙제에 대한 해답을 얻을 수 있는 셈이라고 이야기하고 싶다.
PEAR를 구성하는 라이브러리들을 유기적으로 모아둔 프레임워크뿐 아니라 이의 배포가 이루어지는 홈페이지와 사용자들의 의견 교환 공간 모두를 합한 것이 PEAR를 의미한다고 표방하고 있다. PEAR 사이트의 PEAR 소개를 적어보면 다음과 같다.

◆ PHP 사용자를 위한 오픈소스 기반 구조적 라이브러리
◆ 코드 배포와 패키지 관리 시스템
◆ PHP 표준 코딩 스타일
◆ PFC(PHP Foundation Class)
◆ PECL(PHP Extension Community Library)
◆ PEAR 프로젝트 지원을 위한 웹 사이트 및 메일링 리스트

프레임워크의 내용이 방대하다 보니 글에서 일일이 설명을 하지 못하고 상세한 내용은 매뉴얼 등 문서를 참조하길 바란다는 이야기를 미리 해야겠다. 모든 프레임워크가 그러하듯이 PEAR 또한 선행 학습이 없다면 프레임워크의 강력함을 제대로 맛보기 어려울 것이라는 이야기로 매뉴얼의 중요함을 다시 한 번 강조하면서 PEAR와의 만남을 시작하겠다.

go-pear를 이용한 PEAR 설치

먼저 PEAR의 설치부터 살펴보자. 앞의 목록에 포함되어 있는 '코드 배포와 패키지 관리 시스템'이 PEAR의 기본 설치와 추가 패키지 설치, 업그레이드 및 환경 설정을 해주는 자체 기능을 제공하고 있다는 이야기라고 바꾸어 말할 수 있겠다. PHP가 지원되는 수많은 환경의 수많은 경우를 다 언급하기는 힘드니 이 글은 윈도우 환경에서 PHP 4.3.9와 아파치 1.3.33 그리고 데이터베이스는 MySQL 4.1을 기준으로 진행하겠다. PHP, 아파치, MySQL 설치와 환경 설정은 별도의 설치 문서를 참조하길 바란다.
PEAR 설치는 PHP 4.3.× 이상의 버전에 포함되어 있는 매니저 프로그램을 이용해 진행한다. 먼저 PHP를 설치한 디렉토리를 윈도우의 PATH 환경 변수에 추가했다. PHP를 설치한 디렉터리를 살펴보면 go-pear.bat 파일이 있을 것이다. PEAR의 골격을 설치하고 기본적인 설정을 수행하는 프로그램이 go-pear다. 윈도우에서 커맨드 창을 실행한 후 go-pear.bat 파일을 실행하면 <화면 1>과 같이 환영 메시지가 나오고 계속 진행할 것인지를 묻는 것으로 설치가 시작된다.

<화면 1> go-pear의 환영 메세지


환영 메시지가 나타난 후에는 엔터 키를 눌러 진행을 계속한다. <화면 2>와 같이 설치와 관련된 디렉터리와 파일들의 설정을 보여준다. go-pear 실행 이전에 별도로 PHP 관련 디렉터리 구조를 바꾼 경우가 아니라면 엔터 키를 눌러 설정 전체에 대한 확인을 하고 설치를 계속 진행한다. <화면 3>과 같이 각종 패키지가 설치됐음을 알려주는 진행 상황이 화면에 나타날 것이다. 패키지 설치가 끝나면 <화면 4>의 중간 부분에서 보이는 것과 같이 PHP에 대한 설정을 담고 있는 php.ini 파일을 자동으로 수정할 것인지를 묻는다. 'Y'를 입력해 php.ini 파일을 자동으로 수정한다. go-pear에서 수정한 부분은 php.ini 파일을 텍스트 편집기에서 열어보면 주석으로 go-pear 실행에 의해 바뀌었음이 적혀 있을 것이다.

<화면 2> PHP 관련 환경 설정 확인

<화면 3> 설치 진행 화면, 어떤 패키지들이 설치되고 있는가를 보여준다. 

<화면 4> php.ini 자동 수정 확인 

설치가 종료되면 설치 디렉터리 안에 윈도우 레지스트리 등록을 위한 PEAR_ENV.reg 파일이 생성되어 있을 것이다. 이 파일을 더블클릭해 레지스트리 정보 등록을 수행한다. <화면 5>의 내용이 PEAR_ENV.reg의 내용이다.

<화면 5> PEAR_ENV.reg 파일의 내용


pear 실행 파일을 이용한 환경 설정과 추가 패키지 설치

go-pear를 이용해 PEAR를 이루는 기본 골격을 설치한 후에는 pear.bat 파일을 이용해 패키지(설치 결과에 따라서는 pear.bat이라는 파일 이름 뒤에 old가 붙는 경우도 있을 것이다. old를 지워 pear.bat 파일로 파일명을 변경한다)를 추가하거나 환경 설정 등의 작업을 수행할 수 있게 된다. 먼저 자주 사용되는 명령어 몇 가지를 살펴보자. 전체 명령행 옵션은 매뉴얼 등을 참조하기 바란다.

패키지 목록 조회 관련 옵션

커맨드 창에서 <화면 6>과 같이 'pear list'라고 입력하면 현재 설치되어 있는 패키지 이름과 버전 목록이 조회된다. 'pear list-remote'라고 입력하면 <화면 7>과 같이 현재 PEAR 프로젝트에서 다운로드할 수 있는 패키지 목록이 나타난다. 설치되어 있는 패키지와 다운로드 가능한 패키지를 동시에 보고 싶다면 'pear list-all'이라고 입력한다.

<화면 6> 현재 설치되어 있는 패키지 목록

<화면 7> 다운로드 가능한 패키지 목록 


PEAR 관련 환경 설정 관련 옵션

커맨드 창에서 'pear config-show'라고 설정 보기 명령을 입력하면 <화면 8>과 같이 전체 환경 설정을 보여준다. 좌측부터 간략한 설명, 환경 변수 이름, 환경 변수의 값을 의미한다. 'pear config-help'라고 입력하면 앞의 명령보다는 약간 더 상세한 설명을 볼 수 있을 것이다. 특정 환경 변수의 값을 보고 싶은 경우라면 'pear config-get 환경 변수 이름'을 입력하면 해당 환경 변수의 값을 조회할 수 있다. 반대로 특정 환경 변수의 값을 변경하고자 하는 경우에는 'pear config-set 환경 변수 이름'을 입력한다. 예를 들어 배포되는 패키지의 상태를 담고 있는 preferred_state의 기본값인 안정(stable) 상태의 패키지뿐 아니라 개발 중인 베타 버전의 패키지까지 설치하고 싶다면 preferred_state의 값을 stable에서 beta로 변경해야 한다. 'pear config-set preferred_state beta'라고 입력하면 이후 패키지 설치시 안정 상태의 패키지뿐 아니라 베타 상태의 패키지도 설치할 수 있다. 역으로 preferred_state가 stable인 상태라면 베타 상태의 패키지는 설치되지 않는다. stable, beta, alpha, devel, snapshot이 상태를 변경할 수 있는 값이다.

<화면 8> PEAR 환경 설정 내용

패키지 설치 관련 옵션

앞서 설명한 list 등의 명령으로 추가하거나 업그레이드하고자 하는 패키지 이름을 확인한 후 install과 upgrade 명령을 수행해 설치와 업그레이드를 수행할 수 있다. 'install 패키지명', 'upgrade 패키지명'과 같이 버전 번호를 포함한 패키지 이름을 입력해 설치와 업그레이드를 수행한다. 특정 패키지를 제거하고자 하는 경우라면 uninstall 명령어를 사용한다. upgrade-all 명령을 실행해 설치된 패키지 전체에 대한 일괄 업그레이드를 수행할 수도 있다. 이외에 CVS, FTP, 직접 다운로드를 통한 PEAR와 패키지 설치가 가능하다.
또 특정 패키지의 경우 상위 패키지가 설치되어야만 설치 가능한 하위 패키지인 경우가 있다. <화면 9>에서 보는 것처럼 Auth 패키지의 설치 없이 하위 패키지인 Auth_HTTP 패키지를 설치하려고 하는 경우 1.2.0 이상 버전의 Auth 패키지가 설치되어야 한다는 메시지가 나오고 Auth_HTTP 패키지 설치는 진행되지 않는다. 다시 Auth 패키지를 설치하고 Auth_HTTP 패키지의 설치 명령을 실행하면 이상 없이 패키지가 설치된다(각각의 진행 내용은 가로 선으로 화면상에서 구분했음).

<화면 9> 패키지간의 의존 관계가 있는 경우의 패키지 설치


PEAR와 MySQL을 이용한 사용자 인증

앞서 설치한 Auth 패키지를 이용해 사용자 인증 프로그램을 작성해 보자. 사용자 이름과 패스워드는 MySQL을 이용해 저장하고 이를 불러들여 인증이 이루어지는 방식으로 Auth 패키지의 사용 방법과 PEAR의 동작 방식을 살펴보자.
먼저 MySQL에서 사용자 인증에 사용할 auth라는 테이블을 작성하자. 다음과 같은 SQL문을 MySQL에서 실행해 auth 데이터베이스를 생성하고 다시 auth 데이터베이스 안에 auth 테이블을 작성한다. auth 테이블에 테스트에 사용할 test1, test2라는 사용자와 사용자의 패스워드를 입력한다. 우선 사용자 이름과 패스워드는 동일하게 입력해 보겠다.

1. auth 데이터베이스를 생성한다.
    CREATE DATABASE auth;

2. auth 데이터베이스를 선택한다.
    USE auth

3. auth 테이블을 생성한다.
    CREATE TABLE auth (username VARCHAR(50) default '' NOT NULL, password VARCHAR(32) default '' NOT NULL, PRIMARY KEY (username), KEY (password));

4. 사용자 이름과 비밀번호(텍스트 형태)를 두 건 입력한다. 두 번째 입력 값의 패스워드는 MD5() 함수를 적용한다.
    INSERT INTO auth values('test1','test1'),('test2',MD5('test2'));

다음으로 <리스트 1>과 같이 간단한 인증 소스를 작성해 auth_test. php로 저장해 보자.

<리스트 1> Auth 패키지를 이용한 인증 예
<?php
require_once "Auth.php";

function loginFunction() {
    echo "<form method=\"post\" action=\"" . $_SERVER['PHP_SELF'] . "?login=1\">";
    echo "사용자명<input type=\"text\" name=\"username\" /><br />";
    echo "패스워드<input type=\"password\" name=\"password\" /><br />";
    echo "<input type=\"submit\">";
    echo "</form>";
}

if (isset($_GET['login']) && $_GET['login'] == 1) {
    $optional = true;
} else {
    $optional = false;
}

$dsn = "mysql://mySQL계정:mySQL패스워드@서버IP:포트/auth";
$a = new Auth("DB", $dsn, "loginFunction", $optional);

$a->start();

if (isset($_GET['logout']) && $_GET['logout'] == 1 && $a->checkAuth()) {
    $a->logout();
    $a->start();
}

echo "로그인 인증 성공 여부에 관계없이 나타나는 메시지입니다.<br />";

if (!isset($_GET['login'])) {
    echo "<a href=\"" . $_SERVER['PHP_SELF'] . "?login=1\">로그인하려면 클릭해 주세요.</a><br />\n";
}

if ($a->getAuth()) {
    echo "로그인 인증 성공!<br />";
    echo "<a href=\"auth_test.php?logout=1\">로그아웃을 원하면 클릭해 주세요.</a>\n";
}
?>

$dsn = "mysql://MySQL계정:MySQL패스워드@127.0.0.1/auth";의 MySQL 계정과 MySQL 패스워드, 서버 IP와 포트(기본값인 3306을 사용하는 경우는 적지 않아도 된다)를 각자의 환경에 맞춰 적어준다. 주석 한 줄 들어가 있지 않은 코드이니 먼저 실행해 그 결과를 확인해 보자. 웹 브라우저에서 auth_test.php를 호출하면 <화면 10>과 같은 초기 메시지가 나타날 것이다. 아래 줄의 링크를 클릭해 로그인 입력 화면으로 이동하면 <화면 11>과 같이 입력 양식이 나타날 것이다. 사용자 이름과 패스워드 입력란에 앞서 auth 테이블에 저장했던 'test1'을 입력하고 로그인을 수행해 보자. 로그인에 실패하고 다시 <화면 11>과 같은 상태로 되돌아올 것이다. 이번에는 'test2'를 입력하고 로그인을 수행해 보자. 정상적인 로그인 인증이 일어나고 <화면 12>와 같은 로그인 성공 메시지와 로그아웃 링크가 나타날 것이다.

<화면 10> 초기 메시지


<화면 11> 입력 양식


<화면 12> 로그인 성공 메시지와 로그아웃 링크


앞서 auth 테이블에 자료를 입력할 때 'test1'의 경우 패스워드를 텍스트 상태로 'test1'이라고 입력했고, 'test2'의 경우는 MySQL의 MD5() 함수를 사용해 패스워드를 저장했음을 기억해 내자. PEAR의 Auth 패키지는 기본적으로 패스워드 필드를 MD5가 적용된 것으로 인식한다. 다음으로 이어지는 loginFunction()의 내용은 HTML 입력 양식을 화면에 출력하라는 내용으로 구성된 함수다.

사용자 인증 프로그램의 동작 설명

이제 <리스트 1>의 PHP 소스를 차근히 살펴보자. 본격적인 코드의 첫줄인 require_once "Auth.php";는 Auth 패키지를 사용하겠다는 의미임을 쉽게 알 수 있을 것이다(require와 require_once, 그리고 include의 차이점은 독자의 몫이라 생각하겠다). PEAR를 사용하는 경우 Auth와 같은 패키지 이름을 php 파일명으로 사용하지 않도록 주의한다.
이어지는 부분은 GET 파라미터로 login=1과 같이 값을 전달받은 경우 $optional 값을 true로, 그렇지 않은 경우에는 $optional 값을 false로 만드는 기능을 수행한다.
다음에 이어지는 부분이 $dsn 값에 URL 형식으로 데이터베이스 정보를 적는 내용이다. MySQL 이외의 데이터 연결의 경우도 형식에는 그리 크게 차이가 없다. 주요 데이터베이스별 연결은 이후 DB 패키지를 설명하면서 상세히 살펴보겠다.
그 다음에 나오는 $a = new Auth("DB", $dsn, "loginFunction", $optional); 한 줄에 사실상 인증과 관련해 프로그래머가 신경 써 주어야 할 내용이 모두 담겨 있는 셈이다. 첫 번째 값 "DB"는 DB 패키지의 기능을 이용해 인증을 수행하겠다는 의미다. DB 외에 파일, LDAP, SMB, RADIUS, SOAP, IMAP 등을 인증 방법으로 사용할 수 있고 Auth 패키지의 하부 패키지를 확장하면 더 다양한 인증 방법을 사용할 수 있다.

두 번째로 입력받은 $dsn 값이 데이터베이스 연결 정보를 갖는 DSN(Data Source Name) 값이다. 세 번째 값인 "loginFunction"이 인증을 수행하기 위해 실행되어야 하는 함수의 이름이고, 마지막으로 앞서 true인지 false인지가 결정된 $optional의 값이 세 번째 값에 해당하는 함수의 실행 여부를 나타낸다. 초기에 php 확장자까지만 브라우저 주소 창에 입력한 경우라면 $optional의 값이 false가 되므로 "로그인 인증 성공 여부에 관계없이 나타나는 메시지입니다."라는 문장과 "로그인하려면 클릭해 주세요"라는 링크 문장만 화면에 나타나게 된다. 이 링크를 클릭하는 경우 ?login=1이라는 GET 파라미터가 전달되고 $optional이 true로 변경되며 loginfuction()이 수행된 결과로 화면에 사용자 이름과 패스워드 입력 폼이 나타나게 된다.
'$a->start()'를 호출하면 Auth 객체(PEAR는 객체지향이 아닌 구조적 라이브러리임을 표방하고 있으나 생성자에 의한 생성 결과물 등의 호출 방법을 표현하기 위해 객체라는 용어를 사용하겠다)가 생성되어 인증이 시작되고, '$a->getAuth()'로 인증 여부를 확인해 인증이 성공한 경우 "로그인 인증 성공!"이라는 메시지와 로그아웃할 수 있는 링크가 화면에 나타나게 된다. 로그아웃은 GET 파라미터로 logout=1이라는 값을 전달받은 경우에 수행되고 '$a->logout()'과 같이 호출하면 로그아웃이 이루어진다. 당연히 로그아웃할 대상으로 Auth 객체가 필요하므로 '$a->logout()'이 호출되기 이전에 '$a->start()'가 먼저 호출되어야 하며 로그아웃 이후에 다시 한 번 '$a->start()'를 호출해 로그인 초기 정보를 화면에 출력한다.

Auth의 진실 혹은 비밀

아직 PEAR에는 익숙하지 않지만 PHP에 익숙한 독자라면 앞의 코드를 보고 몇 가지 의문을 갖게 될 것이다. $dsn에 데이터베이스 연결에 필요한 정보가 담겨 있는 것은 알겠으나 데이터베이스를 연결하는 구문도 없고 어떤 테이블에서 정보를 읽어와야 하는지, 테이블의 어떤 컬럼이 사용자 이름이고, 어떤 컬럼이 MD5 패스워드인지 등의 정보가 전혀 없이 데이터베이스를 연결한 사용자 인증이 이루어진다는 점이 당연히 의문일 것이다.
먼저 MySQL_connect() 등의 함수 호출을 직접 수행하지 않는 것은 Auth 패키지의 New Auth() 생성자에 의해 앞서 $a에 해당하는 Auth 객체가 생성될 경우 PEAR::DB 패키지를 알아서 사용하게 되기 때문이다. 이 점에서 PEAR를 컴포넌트 또는 라이브러리라고 부르지 않고 프레임워크라 부를 수 있는 근거를 확인한 셈이다. 단순히 인증을 위한 컴포넌트 또는 라이브러리라면 인증과 데이터베이스 연결이라는 명확히 구분되는 독립적인 기능들이 서로를 호출해 사용한다는 것은 이의 독립성을 해치는 결과일 뿐이지만 명확히 프레임워크 내의 동작을 전제로 하는 PEAR의 패키지들은 기능적으로는 독립되어 있더라도 프레임워크 내의 다른 패키지들에 의존 관계를 갖는 것이 당연한 일이며 오히려 이러한 의존 관계들이 더 견고한 프레임워크를 개발자에게 전달하는 기반이 된다 하겠다.
이제 남은 의문은 테이블과 사용자 이름, 패스워드 컬럼을 어떻게 알았을까 하는 것이다. 한마디로 이야기하면 앞서 Auth 패키지의 기본 인증 방법인 MD5() 함수를 사용했듯이 예제를 위해 생성한 auth 테이블과 username, password 컬럼 또한 Auth 패키지의 기본값이라는 점이다. 별도의 테이블 이름 컬럼을 지정하지 않는 한 Auth 객체는 DB 인증을 위해 주어진 데이터베이스 DSN 내에서 auth라는 테이블을 찾고, auth 테이블의 username 컬럼을 사용자 이름으로, password 컬럼을 패스워드로 인식하여 동작하도록 기본값이 지정되어 있는 것이다. 실제로 사용되는 사용자 정보를 담고 있는 테이블의 경우라면 앞의 예처럼 사용자 이름과 패스워드 두 컬럼으로 이루어진 경우는 거의 없을 것이다. 그렇다고 auth의 기본값을 사용하기 위해 기존 테이블의 테이블 이름과 컬럼 이름을 바꾸고 프로그램을 수정할 필요는 없다. 기본값은 어디까지나 기본값일 뿐 기본값이라는 단어 자체가 변경할 수 있다는 의미를 담고 있는 셈이다. 테이블 이름과 컬럼 이름을 입맛에 맞게 바꾸고 싶다면 <리스트 2>와 같이 Array에 DSN, 테이블 이름, 사용자 이름, 컬럼 이름, 패스워드 컬럼 이름을 담아 Auth()의 두 번째 값으로 전달하면 된다.

<리스트 2> Auth의 기본값 변경
$params = array(
    "dsn" => "mysql://martin:test@localhost/auth",
    "table" => "myAuth",
    "usernamecol" => "myUserColumn",
    "passwordcol" => "myPasswordColumn"
);

$a = new Auth("DB", $params, "loginFunction");
$a->start();

PEAR::DB 데이터베이스 패키지

업무 관련 프로그램의 30~40%를 차지하는 코드가 데이터 소스를 연결하고 데이터를 처리하는 코드라고 한다. 간단하게는 데이터가 담긴 파일을 읽어들이거나 각종 설정이 담긴 ini 파일 등을 읽는 일에서부터 데이터베이스를 연결해 사용하는 경우 분산 환경에서 소켓, 웹 서비스 등을 통해 스트림 형태의 XML 파일을 처리하는 경우까지, 이런 데이터 소스 연결과 데이터 처리를 위한 코드를 작성하는 수고를 조금이나마 덜 수 있다면 바로 개발 생산성 향상으로 이어질 것이라는 점은 쉽게 상상할 수 있을 것이다. 게다가 데이터 소스 형태가 바뀐다거나 데이터베이스 서버의 IP 설정값이 바뀌었다거나 하는 경우 두 종류 이상의 데이터 소스를 처리해야 한다면 계획적인 교통 정리가 선행된 이후에야 원하는 처리를 수행하는 것이 정답이 될 것이다. 이런 와중에 잘 정의되어 있는, 그리고 데이터 소스에 대해 비교적 독립적인 기능을 구할 수 있다면 사용하지 않을 이유가 없을 것이다.
PEAR의 DB 패키지는 이러한 역할을 수행하는 잘 정의되어 있는 패키지임에 틀림없다. 앞서의 다양한 데이터 소스에 모두를 지원하지는 않지만 적어도 관계형 데이터베이스의 연결과 데이터 처리에 대해서는 일관된 접근 방법을 제공하고 있는 패키지다. PEAR의 DB 패키지는 MySQL 관련 함수를 사용하는 경우와, OCI8 함수를 사용해 오라클을 연결하는 경우에서 나타나는 차이 없이 일관된 방법으로 데이터베이스 연결과 데이터 처리를 가능하게 해준다(DB 패키지가 제공하는 기능을 일일이 설명하기에는 지면이 부족할 것이다. 중요한 기능 중 유용한 기능을 개념적으로 살펴보는 것으로 PEAR::DB를 만나보자).
앞에서 PEAR::DB 패키지를 이용해 데이터베이스에 접근하기 위해서는 DSN이 필요하다는 것을 이야기했다. DSN의 형태는 다음과 같다.

phptype(dbsyntax)://username:password@protocol+hostspec/database?option=value

또는 다음과 같다.

phptype(syntax)://user:pass@protocol(proto_opts)/database(최근 버전의 경우에 지원하는 형태)

여기서 phptype에 해당하는 값이 다양한 데이터 소스의 이름이다. 주요 데이터베이스별로 phptype을 살펴보면 오라클의 경우 oci8, MS SQL·MySQL의 경우는 데이터베이스 이름과 같은 mssql과 mysql, PostgreSQL의 경우는 pgsql, MySQL4.1 이상의 버전과 PHP 5.×를 사용하는 경우라면 mysqli라는 phptype을 사용해 확장된 보안 기능 등을 사용할 수 있다. 사용하는 데이터베이스와 사용하는 방식에 따라 DSN 또한 수없이 많은 형태를 갖게 된다. 필요로 하는 데이터 소스와 접근 방법에 따른 DSN의 형태는 매뉴얼을 참조하길 바란다. MySQL과 관련된 DSN을 생성하는 방법 몇 가지를 적어 봤다.

◆ DSN 변수에 계정, 패스워드, 서버의 IP, 포트와 사용할 데이터베이스를 지정하는 일반적인 방법
    $dsn = "mysql://mySQL계정:mySQL패스워드@서버IP:포트/데이터베이스명";

◆ MySQL 소켓 경로를 지정해 직접 연결하는 방법
    $dsn=mysql://user@unix(/path/to/socket)/데이터베이스명

◆ DSN의 값들을 Array에 저장해 사용하는 방법(PHP 5.0, MySQL 4.1 이상을 사용한 SSL 연결)

$dsn = array(
    'phptype' => 'mysqli',
    'username' => '사용자명',
    'password' => '패스워드',
    'hostspec' => '서버IP:포트',
    'database' => '데이터베이스명',
    /* SSL 인증에 필요한 값과 인증 방법 */
    'key' => 'client-key.pem',
    'cert' => 'client-cert.pem',
    'ca' => 'cacert.pem',
    'capath' => '/path/to/ca/dir',
    'cipher' => 'AES',
);

데이터베이스의 연결과 질의 전달, 그리고 페치

이제 DSN이 준비됐으니 데이터베이스를 연결하자. 데이터베이스 연결은 $db =& DB::connect($dsn, $options);와 같이 & DB::connect()에 DSN과 옵션 값을 인자로 전달해 연결을 수행한다('&'를 사용해 assign by reference에 의해 참조한다). DSN과 옵션 값 모두 Array 형태로 전달할 수 있다. MySQL을 이용하는 경우라면 데이터베이스 연결 소스와 연결이 실패한 경우의 에러 처리는 <리스트 3>과 같다.

<리스트 3> MySQL의 연결 예(일부 들여쓰기는 PEAR 코딩 표준과 다를 수 있다)
<?php
include("DB.php");

$dsn = array(
    'phptype' => "mysql",
    'username' => "test2", // 사용자명
    'password' => "test2", // 패스워드
    'hostspec' => "서버IP:포트",
    'database' => "auth" // 데이터베이스명
);
$db = DB::connect($dsn);

if (DB::isError($db)) { // 데이터베이스 연결시 발생하는 오류를 처리한다.
    echo "데이터베이스 연결 과정에서 오류가 발생했습니다.<br />\n";
    echo "오류 메시지: " . $db->getMessage() . "<br />\n";
    echo "오류 상세 설명: " . $db->getDebugInfo() . "<br />\n";
}
?>

DB 연결은 '$db = DB::connect($dsn);' 구문에 별도의 옵션 없이 처리된다. 데이터베이스 관련 오류의 처리 과정을 살펴보자. DB::iserror()에 참이 전달된 경우 오류가 발생한 것으로 인식하고 오류 메시지 출력을 처리하는 과정을 거치게 된다. DB::iserror()는 네 가지 형태의 메시지를 처리해 준다. 호출 방법은 '$db->get Message()'와 같이 호출하며 DB_Error::getMessage(), DB_Error::getCode()를 통해 데이터베이스 자체의 오류 메시지와 무관한 PEAR::DB의 에러 메시지와 에러 코드를 처리할 수 있으며 DB_Error::getDebugInfo(), DB_Error::getUserInfo()를 통해 오라클의 ORA-××××와 같은 데이터베이스 관리 시스템의 오류 메시지를 처리할 수 있다. DB_Error의 구성은 Pear::DB의 구성과 같다.
데이터베이스 연결 외의 경우에도 오류 처리를 하는 경우라면 PEAR_Error를 이용해 앞서의 getMessage() 등의 함수를 호출하면 된다. PEAR 프레임워크 내부에 오류 처리에 대해 일관된 표준과 접근 방법이 제공되고 있는 것이다. 오류 발생시 die 구문을 사용하고 싶다면 isError()로 시작하는 조건문 안에 die 구문을 사용하면 될 것이다.
다음은 SQL 질의를 전달하고 결과를 페치(fetch)하는 방법을 살펴보겠다. 데이터베이스를 연결하고 SQL 구문을 전달한 뒤 그 결과를 담고 있는 무엇인가로부터 데이터를 처리하는 과정이라 이야기할 수 있을 것이다. <리스트 3>의 ?> 윗줄에 <리스트 4>의 내용을 추가하면 $rows에 네스트 연관 배열(nest associated array) 형태로 결과 값이 저장된다.

<리스트 4> SQL 구문의 전달과 결과 값 처리
$sql = "SELECT username, password FROM auth";
$row = $db->getAll($sql, DB_FETCHMODE_ASSOC);

if (DB::isError($row)) { // SQL 실행시에 발생하는 오류를 처리한다.
    echo "조회 결과 처리 과정에서 오류가 발생했습니다.<br />\n";
    echo "오류 메시지: " . $db->getMessage() . "<br />\n";
    echo "오류 상세 설명: " . $db->getDebugInfo() . "<br />\n";
}

<리스트 4>의 경우 getALL()과 DB_FETCHMODE_ASSOC라는 페치 방법을 사용했다. get*()이나 fetch*()와 같이 페치를 수행하는 구문에서는 fetch mode 옵션을 지정할 수 있다. 크게 세 가지 형태로 기본값인 DB_FETCHMODE_ORDERED는 $row[0], $row[1]과 같이 질의 결과의 순서에 따라 접근할 수 있는 array 형태의 결과가 생성된다. DB_FETCHMODE_ASSOC는 $row[username], $row[password]와 같이 컬럼 이름에 의해 접근이 가능한 연관 배열(associated array) 형태의 결과가 생성된다. DB_FETCHMODE_OBJECT의 경우는 $row->username, $row->password와 같이 접근할 수 있는 객체가 결과로 생성된다. fetch mode의 값은 setFetchMode()를 이용해 지정할 수도 있다.
SQL 구문의 전달과 결과 생성을 동시에 하는 get*()에 해당하는 함수 몇가지를 추가로 살펴보면 앞서 사용한 getAll()은 질의의 결과 전체를, getOne()은 질의 결과의 첫 번째 로우의 첫 번째 컬럼을 결과로 생성하며, getRow()의 경우는 결과의 첫 번째 한 로우를 결과로 생성하며, getAssoc()는 입력받는 인자에 따라 연관 배열 또는 네스트 연관 배열 형태로 결과를 생성한다. SQL 구문의 전달과 페치를 별도로 구분하는 경우는 <리스트 5>와 같이 query()를 사용하고 fetchRow()나 fetchIntp()를 사용한다. 1로우 이상의 결과를 포함하는 경우는 루프 구문에서 반복 처리를 수행한다.

<리스트 5> SQL 전달과 페치를 구분한 경우
$res =& $db->query('SELECT * FROM Table');
while ($row =& $res->fetchRow()) {
    echo $row[0] . "\n";
}

데이터 소스 연결과 결과 생성에 대한 이용이 끝난 경우에는 $res->free()와 같이 free()를 사용해 결과(result set)에 대한 릴리즈를 수행하고, 데이터 소스 연결에 대해서는 $db->disconnect()와 같이 disconnect()를 사용해 연결을 끊어준다. 이외에 DB 패키지에는 prepared statement의 사용, 시퀀스, 트랜잭션 처리 기능 등이 포함되어 있으니 데이터베이스와 응용 프로그램의 설계에 따라 필요한 기능 등을 살펴보기 바란다.

SQL 없이 데이터를 처리하는 DB_DataObject

지금까지 간략하게 데이터베이스와 관련된 DB 패키지를 살펴봤다. 데이터베이스와 관련해서는 여러 PHP 자료에서 사용하는 방식대로 SQL문을 작성하고 이를 전달하는 예제를 보았으나 이번에는 굳이 SQL문을 코딩하지 않아도 데이터 처리가 가능한 기능을 제공하는 DB_DataObject 패키지를 만나보겠다.
J2EE 프로그래밍을 하는 경우에 데이터베이스의 테이블 또는 뷰 전체에 대한 접근과 데이터 처리를 하는 엔티티 빈을 생성하거나 GUI 기반의 통합 개발 환경을 제공하는 환경에서는 데이터 소스와 연결되는 데이터 소스 객체 혹은 컴포넌트들을 사용해 테이블에 대한 질의와 Insert, Update 처리를 하기도 한다. 이러한 방식들이 제공하는 장점은 CRUD(Create, Read, Update, Delete)로 대변되는 데이터에 대한 주요 조작이 일정한 형태를 따르는 경우가 대부분이고, 특정 테이블의 이름과 키 값을 알고 Select를 수행한다면 변경된 값을 저장하기 위한 Update 문장이나 신규 입력 값에 따른 Insert 문장은 충분히 자동으로 생성할 수 있다는 점에서 우선적으로 SQL 문을 작성하는 수고를 덜어준다는 점이고, 동일한 테이블에 대한 처리를 수행하는 여러 개의 프로그램을 작성하면서 동일한 작업을 반복하거나 복사와 붙여넣기(copy and paste)를 되풀이하는 수고를 덜어준다는 점, 그리고 이미 검증된 결과물을 사용하는 경우라면 이에 대한 추가적인 테스트 등의 작업이 필요 없다는 점 등을 들 수 있을 것이다.
DB_DataObject 패키지는 DB 패키지의 하부 패키지로, 앞서 설명한 것과 같이 데이터베이스 연결과 테이블의 스키마(schema)와 관련된 정보를 파일 또는 XML 등으로 저장해 두고 필요한 경우 이를 불러다 사용할 수 있는 클래스를 제공해 준다.
J2EE 프로그래밍을 해본 독자라면 XML로 정보를 저장해 두고 이에 근거해 웹 애플리케이션 서버가 엔티티 빈을 생성하게 되는 CMP 빈의 생성 과정과 비슷하다고 생각해도 큰 무리는 없을 것이다. 수작업으로 이러한 스키마를 입맛에 맞게 생성할 수도 있으나 DB_DataObject에 포함된 자동 생성 기능을 이용해 앞서의 auth 데이터베이스에 포함된 테이블들에 대해 스키마와 클래스를 자동 생성하는 과정을 살펴보자.
먼저 <리스트 6>과 같이 데이터베이스와 자동 생성된 스키마와 클래스들이 저장될 경로 등을 지정한 ini 파일을 작성해 PHP 설치 디렉토리 아래의 Pear/DB/DataObject 디렉토리에 저장한다. 앞서의 예제에서 데이터베이스 이름을 auth로 했으므로 auth_DO.ini 파일로 저장하겠다.

리스트 6> auth_DO.ini 파일의 내용
[DB_DataObject]
database = mysql://mySQL계정:mySQL패스워드@서버IP:포트/auth
schema_location = C:\php4\pear\DataObjects
class_location  = C:\php4\pear\DataObjects
require_prefix  = DataObjects/
class_prefix = DataObjects_

먼저 database의 값으로 DSN을 적어두고 스키마 정보를 담고 있는 ini 파일과 클래스 php 파일이 저장될 경로를 지정한다. require_prefix는 클래스 파일들이 저장될 경로명이고, Class_prefix는 클래스 이름에 사용될 접두어다. 클래스 이름은 '접두어+테이블명' 형태로 생성된다. 위의 네 가지 필수 값 외에 시퀀스, 키 처리, 자동 생성 등과 관련된 추가 옵션이 존재한다.
이제 본격적으로 자동 생성 도구를 이용해 스키마와 클래스를 자동으로 생성하자. 커맨드 창에서 PHP 설치 디렉토리 아래의 Pear/DB/DataObject 디렉토리로 이동하면 createTables.php라는 파일을 찾을 수 있을 것이다. 이 파일을 셸 또는 커맨드 창에서 실행 가능한 php 파일(윈도우의 경우 PHP 설치 디렉토리의 php.exe 파일)을 이용해 실행한다. 다음과 같이 입력하면 <화면 13>처럼 MySQL의 auth 데이터베이스의 모든 테이블에 대해 스키마를 담고 있는 ini 파일과 클래스 php 파일을 생성하게 된다.

C:\PHP설치디렉토리\php.exe createTables.php authDO.ini

<화면 13> create Tables.php를 이용한 스키마와 클래스 생성


자동 생성된 auth.php 클래스 파일의 내용은 <리스트 7>과 같다. DataObjects_라는 접두어와 Auth라는 테이블 이름이 합쳐진 클래스 이름과 테이블의 컬럼 정보 등이 포함되어 있다. 자동 생성된 스키마 파일을 사용하지 않고 클래스 파일 내에 스키마와 관련된 정보를 담을 수도 있다. ''의 종료 태그가 없음에 유의한다.

<리스트 7> auth.php 클래스 파일의 내용
<?php
/*
 * Table Definition for auth
 */
require_once 'DB/DataObject.php';

class DataObjects_Auth extends DB_DataObject {
    ###START_AUTOCODE
    /* the code below is auto generated do not remove the above tag */
    var $__table = 'auth';

    // table name
    var $username;

    // string(50) not_null primary_key
    var $password;

    // string(32) not_null multiple_key
    /* ZE2 compatibility trick*/
    function __clone() { return $this;}

    /* Static get */
    function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('DataObjects_Auth',$k,$v); }

    /* the code above is auto generated do not remove the tag below */
    ###END_AUTOCODE
}

자동 생성된 스키마 파일의 내용은 <리스트 8>과 같으며 테이블 이름과 '테이블 이름+keys'라는 두 개 영역의 정보를 담고 있다. 테이블 정보의 경우 컬럼 이름을 담고 있으며 컬럼 이름 뒤에 따라오는 숫자 값은 숫자 형태의 컬럼인 경우 1, 문자 형태의 컬럼인 경우는 2, null을 허용하는 컬럼인 경우는 0, not null 컬럼인 경우는 128이라는 값을 합쳐 컬럼의 형태 정보를 가지고 있게 된다. 스키마 파일에 저장된 해당 컬럼의 속성 값이 숫자 129라면 숫자의 1과 not null의 128이 합쳐진 것이며 컬럼의 값이 2이라면 null을 허용한다는 0과 문자형의 2가 합쳐진 것이다.

<리스트 8> auth.ini 스키마 파일의 내용
[auth]
username = 130
password = 130

[auth__keys]
username = K

이제 웹 서버에 앞서 작성한 auth_DO.ini 파일을 복사한 후 <리스트 9>와 같이 DO_test.php 파일을 만들어 SELECT문 입력 없이 조회가 이루어지는 것을 확인해 보자.

<리스트 9> DO_test.php 파일의 내용
<?
require 'DB/DataObject.php'; // DB_DataObject 패키지를 사용한다.
// 웹 서버의 DataObjects 디렉토리에 저장된 auth_DO.ini 파일을 읽어온다.
$config = parse_ini_file('DataObjects/auth_DO.ini',TRUE);
// auth_DO.ini 파일 정보에 따른 작업을 수행한다.
foreach($config as $class=>$values) {
    $options = &PEAR::getStaticProperty($class,'options');
    $options = $values;
}
// staticGet을 이용해 클래스에서 키 값(username)이 test2인 로우를 조회해 온다.
$auth_DO = DB_DataObject::staticGet('DataObjects_auth','test2');
print_r($auth->password);  // 결과 로우에서 username 컬럼의 값을 출력한다.

?>

웹 브라우저에서 DO_test.php를 호출하면 앞서 테이블에 입력한 'test2'라는 텍스트에 MD5 함수를 적용한 결과를 확인할 수 있을 것이다.

DB_DataObject의 다양한 기능

<리스트 9>의 내용은 SELECT * FROM auth WHERE username = 'test2'라는 SQL문을 실행하고, 그 결과 중 password 컬럼을 출력한 것과 같다. 이외에 복수의 결과 처리를 위한 fetch(), 데이터 입력을 위한 Insert(), 변경을 위한 update(), 삭제를 위한 Delete() 등 데이터 관련 기본 작업을 SQL의 직접 입력 없이 처리할 수 있는 기능이 제공되며, selectAdd()를 이용해 조회해 올 컬럼을 지정하거나 whereAdd()를 통해 조회 조건을 추가할 수도 있다. DB_DataObject의 기본값들을 변경할 수 있는 함수들과 SQL Escape 문자를 처리하는 함수, selectAS(), groupBy(), orderBy() 함수도 제공되니 어지간한 데이터 처리 작업은 따로 SQL 구문을 작성하지 않아도 가능하다고 할 수 있을 것이다.
또 자동 생성되는 클래스를 변경하거나 클래스를 직접 작성한다면 별도의 스키마 없이 클래스를 생성하는 것뿐 아니라 데이터베이스에 저장된 컬럼 형태를 변경하거나 합쳐서 클래스가 생성되게 할 수도 있다. auth_DO.ini 파일에 해당되는 설정 파일에는 두 개 이상의 데이터베이스를 지정할 수 있으며, 동일한 테이블을 다양한 이름으로 별칭을 주어 인식하게 할 수도 있다. 스키마 파일의 경우에도 단순히 테이블 하나에 해당하는 정보뿐 아니라 여러 테이블을 조인한 결과에 해당하는 클래스를 처리할 수 있는 스키마를 생성할 수도 있다. 예제에서는 parse_ini_file()을 이용해 ini 형태의 기본 설정을 사용했지만 XML 패키지를 이용해 이를 대체하는 것도 그리 어려운 일은 아니다.

기능 그 이상의 프레임워크

간략하게나마 Auth, DB, DB_DataObject 패키지를 통해 PEAR를 살펴봤다. 이외에 어떤 패키지들이 있으며 어떤 기능을 하는지를 일일이 설명하는 것은 단순한 나열조차 쉽지 않은 일이고 'pear upgrade-all' 명령어를 입력하면 늘 실망시키지 않고 두세 개 이상의 패키지가 업데이트되는 활발한 프로젝트가 PEAR 프로젝트다.
혹자는 PEAR가 제공하는 기능의 일부분을 보고 이 기능은 내가 자주 사용하는 다른 기능보다 복잡하고 성능도 떨어진다는 이야기를 하기도 한다. 실례로 온라인에서 PEAR::DB와 ADODB를 사용하는 개발자간에 논쟁이 벌어지는 일도 적지 않게 접했던 기억이 있다.
주지해야 할 것은 PEAR가 제공하는 기능의 일부분만을 보고 PEAR를 논한다면 그것은 프레임워크로서의 PEAR에 대한 실례가 될 것이라는 점이다. PEAR::DB의 기능은 데이터베이스 관련 작업을 처리하는 본연의 기능 외에도 확인한 것과 같이 Auth 패키지 등에서도 이를 사용하고 있으며 Auth 패키지 또한 다른 패키지들이 동작하기 위한 필수 조건인 경우가 있다. 이처럼 유기적인 모습으로 패키지와 패키지들이 관계를 가지고 있는 것이 단순 라이브러리나 컴포넌트가 아닌 프레임워크의 모습일 것이다.
프레임워크를 사용하기 위해서는 이를 학습하는 노력을 선행해야 한다는 점을 다시 한 번 강조해 본다. 때로는 프레임워크의 환경을 이해하는 것이 프로그래밍 언어를 하나 익히는 것보다 더 많은 노력을 필요로 할 수 있다는 점을 기억해 두자.

[ PECL 그리고 Imagick ]

PEAR를 이야기하면서 빼놓을 수 없는 것이 PECL이다. PEAR와 PECL 홈페이지를 방문하면 모두 서로를 자매(sister)라고 소개하고 있다(누가 언니이고 누가 동생일지 궁금하다). 본문의 DataObject의 경우도 DB_oo라는 이름으로 PECL 프로젝트에서 PEAR로 이사한 경우다.
PHP 소스를 컴파일할 때 '--with-imagick'처럼 옵션을 주거나 PHP 설치 디렉터리의 extensions 디렉터리에 포함되어 있어 php.ini 파일을 수정해 php.ini 파일의 extension 부분에 추가해 PHP의 기능을 확장할 수 있게 해주는 라이브러리들이 PECL의 결과물들이다.
PECL과 비교했을 때 PEAR의 버전이 상대적으로 많이 낮은 패키지가 이미지 처리와 관련된 패키지들이다. PECL에 포함된 Imagick의 경우 버전 1.0을 앞두고 있으나 PEAR의 경우는 범용적인 이미지 처리 기능을 제공하는 안정된 상태의 패키지를 찾기 어려운 상황이다. Imagick의 경우 imagemagick의 라이브러리들을 사용하므로 GD 등으로 직접 작성한 프로그램보다 속도가 늦는 경우는 있으나 이미지와 관련된 기능을 다양하고 체계적으로 제공하고 있다. 매뉴얼에 상세한 설명이 없이 예제를 제공하는 정도이고 한글 지원이 되지 않는다는 단점이 있으나(이 점은 필자가 확인하지 못한 것일 수도 있다. Imagick을 사용해 한글 처리를 성공한 독자가 있다면 정보 공유를 부탁하고 싶다) 막강한 기능과 손쉬운 사용법, 그리고 오픈소스라는 분명 놓치기 아까운 매력을 가지고 있는 터라 간단하게 설치와 사용법을 살펴보고자 한다. 설치 환경은 본문의 환경과 동일하다. 먼저 Imagemagick을 다운로드해 설치한다. Imagemagick은 http://www. imagemagick.org에서 다운로드할 수 있다. 현재 6.1.× 버전이 배포되고 있으나 PHP에서 동작하지 않는 경우가 많으니 이전 버전인 5.5.7 버전의 윈도우 dll 버전을 설치할 것을 권장한다.
Imagemagick이 설치된 디렉터리를 PATH에 추가하고 MAGICK_HOME 환경 변수에도 동일한 디렉터리를 지정한다. 윈도우용으로 컴파일된 PECL 라이브러리들을 배포하는 kromann.info/pecl.php에서 php_imagick.dll 파일을 다운로드해 PHP 설치 디렉터리의 extensions 디렉터리에 저장한다. 추가 extension을 사용하지 않았던 경우라면 php.ini의 extension_dir의 값이 정확히 지정됐는지 확인한다. php.ini 파일의 extension 부분에 'extension = php_imagick.dll'이라고 추가한다. 아파치 웹 서버를 다시 시작한다. 이 때 imagick extension 관련 에러가 발생하지 않는다면 정상적인 설치가 이루어진 것이다. <리스트 1, 2>와 같이 imagick_test. html 파일과 iconize.php를 작성한다.
<리스트 1> imagick_test.html
<html>
 <head>
  <title>이미지 테스트 페이지</title>
 </head>
 <body>

<p>
<div align="CENTER">

<img src="./image.jpg" alt="[원본 JPEG 포맷]">
</div>
<div align="CENTER">[원본 JPEG 포맷]</div>
<br />
<div align="CENTER">

<img src="./iconize.php" alt="[축소 GIF 포맷, EMBOSS 적용]">
</div>
<div align="CENTER">[축소 GIF 포맷, EMBOSS 적용]</div>
</body>
</html>
<리스트 2> iconize.php
<?
function ShowError($handle) {
    // 에러 처리 함수
    $reason = imagick_failedreason($handle) ;
    $description = imagick_faileddescription($handle) ;

    print "handle failed!<br />\nReason: $reason<br />\nDescription: $description<br />\n" ;
    exit ;
}

// 이미지를 읽어들인다.
$handle = imagick_readimage(getcwd() . "/image.jpg") ;
if (imagick_iserror($handle)) { ShowError($handle); }

// 이미지 크기를 200x200으로 축소한다.
if (!imagick_scale($handle, 200, 200, "!")) { ShowError($handle); }

// 이미지에 emboss 필터를 적용한다.
if (!imagick_emboss( $handle, 1, 1 )) { ShowError($handle); }

// JPEG 이미지를 GIF 이미지로 변환한다.
imagick_convert($handle, "GIF");
// 이미지를 브라우저에 전달하기 위한 형태로 변경한다.
if (!( $image_data = imagick_image2blob($handle))) { ShowError($handle); }

// GIF mime 헤더를 출력한다.
header("Content-type: " . imagick_getmimetype($handle));
// 이미지 출력(브라우저로 전송)한다.
print $image_data ;
?>


출처: http://cug.kr/index.php?mid=basic_news&page=7&document_srl=1173

'WebDevelop > PHP' 카테고리의 다른 글

php 소스 보여주기  (0) 2008.02.15
FPDF를 이용한 PHP로 PDF 만들기  (0) 2007.12.05
register_globals on 과 off 의 차이점  (0) 2007.11.27
Return top