PHP를 이용한 개발 기간의 단축
By John Neil


Myers Internet의 사례를 통해, PHP/Oracle 개발 모델이 제공하는 애플리케이션 라이프사이클 단축 효과를 확인해 봅니다.

미수금 계정 관리를 위한 비즈니스 모델에서는 주문의 입력, 추적, 청구 작업이 핵심 프로세스가 됩니다. 기업은 이러한 프로세스를 성공적으로 구현함으로써 기업을 효과적으로 확장하고 수익을 개선할 수 있습니다. 주문 처리 프로세스의 일관성과 단순성이 떨어지고 에러가 많은 경우, 직접적인 비용이 증가하고 생산성이 저하되는 부작용이 발생합니다.


필자가 근무하는 Myers Internet은, 클라이언트를 위해 핵심 비즈니스 프로세스를 개발하는 서비스를 제공하고 있습니다. Myers Internet은 주문 입력/처리 사이클의 관리를 위해 다양한 시스템을 사용해 왔습니다. 하지만 이러한 시스템이 서로 간에 통합되지 않았을 뿐더러, 각각의 주문과 빌링 결과를 확인할 수 있는 메커니즘이 제공되지 않는다는 문제가 있었습니다.


Myers Order Tracking System (MOTS)

다른 기업 조직과 마찬가지로, Myers 역시 소규모 기업에서 중간 규모 기업으로 성장하는 과정을 거쳤습니다. 하지만 이 과정에서 프로세스와 시스템은 변하지 않았습니다. 이메일, 종이 문서, 고객 미팅 등을 통해 수작업 기반으로 수행되던 프로세스가 여전히 그대로 사용되고 있었습니다. 지금으로부터 5~6년 전, Myer에 근무하던 한 엔지니어가 Cold Fusion과 Microsoft SQL Server를 이용하여 MOTS (Myers Order Tracking System)라는 이름의 주문처리 시스템을 개발하였습니다. MOTS에서 영업/고객관리 팀이 주문을 입력하면 기술지원, 엔지니어링, 설계, 정보 시스템, 회계 팀이 입력된 주문을 처리하게 됩니다. 이 시스템으로 업무 환경을 상당 수준 개선할 수 있었지만, 여전히 많은 수작업 요소가 존재하였고 다른 시스템과의 통합이 이루어지지 않았다는 문제가 남아 있었습니다.


또 비슷한 시기에 Myer 웹사이트 상품의 온라인 주문을 위한 환경이 구현되었습니다. 고객은 이 시스템을 이용하여 새로운 웹 사이트를 생성하고, 생성된 웹 사이트 패키지에 대한 요금을 자동으로 계산합니다. 이 작업이 완료되면 Myers의 담당자에게 메일이 전달되며, 담당자는 MOTS에 주문 내역을 입력하고 청구서를 생성합니다.

아키텍처의 문제

이 아키텍처는 여러 가지 면에서 문제가 많았습니다. 주문 처리를 위해 수작업으로 데이타가 입력되는 과정에서 입력자의 실수가 발생할 가능성이 있었습니다. 또, 주문 입력, 주문 추적, 빌링 시스템이 각각 분리되어 있으므로 주문 입력 누락, 정보 손실, 사용자 실수 등의 문제가 발생할 수 있었습니다.


MOTS 시스템의 내부적인 문제도 있었습니다. 설계 상의 문제 때문에, MOTS에 입력된 주문이 담당 직원에게 할당되지 않는 상황이 빈번하게 발생했습니다. 이러한 경우 입력된 주문은 시스템 내에서 “고아”가 되어 버리며, 빌링 업무의 처리 속도와 정확성에 큰 차질이 빚어지기도 합니다.


비즈니스의 규모가 성장하고 클라이언트와 주문의 수가 증가하면서, 아키텍처 상의 문제가 점점 심각한 사안으로 떠오르기 시작했습니다. 누락되거나 잘못 입력된 주문 때문에 발생하는 매출 손실도 무시할 수 없는 수준에 이르렀습니다. 또 수작업 방식의 데이타 입력으로 인해 업무가 지연되고 프로세스의 효율성이 저하되었습니다.

이러한 문제를 해결하기 위해, 전체 환경을 통합하고 효율성을 개선하고 에러율을 낮추기 위한 대체 시스템의 구현이 필요하다는 사실이 명백해졌습니다. 아래 그림은 기존 시스템 구성도를 보여주고 있습니다.



 

그림 1: 기존 시스템 아키텍처 위 다이어그램에는 수작업이 수반되는 영역이 표시되고 있습니다. 각각의 시스템이 서로 통합되어 있지 않았기 때문에, 데이타가 손실되거나 왜곡될 가능성이 높았습니다. 기존 시스템을 대체할 새로운 시스템을 위한 요구사항이 다음과 같습니다.


  • 주문 시스템(order system)은 주문 추적 시스템(fulfillment tracking system)과 직접적으로 통합되어야 함.

  • 입력된 주문이 처리되지 않고 누락되는 경우를 방지할 수 있어야 함.

  • 빌링 및 주문처리 과정에서 정확성이 유지되어야 함.

  • 시스템 구현에 수반되는 내부적인 비용을 최소화할 수 있어야 함. 따라서 완전한 기능을 갖춘 시스템을 최단시간 내에 구현해야 함.


주문 입력/추적 시스템을 이용하여 비용을 절감할 수는 있어도, 시스템 자체가 비용적인 부담으로 작용해서는 안 된다는 것이 우리의 판단이었습니다.


핵심으로의 접근

스키마 설계 작업을 수행하기 전에, 몇 가지 기본적인 아키텍처 이슈를 검토해야 했습니다. 먼저, 추가적인 코딩 작업을 거치지 않고도 시스템의 설정 변경이 가능해야 했습니다. 따라서 워크플로우를 렌더링/프로세싱 코드 레벨에서 하드 코딩 방식으로 구현하는 대신 데이타베이스 레벨에서 구현하는 것으로 결정되었습니다. 두 번째로, 주문 입력 및 처리를 위한 핵심 인터페이스를 데이타베이스 내부에 구현함으로써, 필요한 경우 쉽게 수정이 가능하도록 하였습니다.


위에 언급된 문제들을 해결하기 위해서 시스템에 두 가지 컴포넌트(주문 입력 및 주문 추적)를 설계하고, 컴포넌트간의 연계 기능을 명확하게 정의하였습니다. 주문 입력 시스템에서는 제품 코드, 할인율, 가격 조건 등의 정보가 정확하게 주문 양식에 렌더링되어야 합니다. 주문 추적 시스템에서는 다양한 태스크 및 관련 작업, 그리고 주문 처리 담당자등을 정확하게 추적할 수 있어야 합니다. 마지막으로 입력된 주문을 주문 추적 시스템으로 전달하는 과정 역시 정확하고 예측가능해야 합니다. 아래 그림은 새로 구현된 시스템의 구조를 보여 주고 있습니다.


 

그림 2: 새로운 시스템 아키텍처 위 그림은 새로운 주문 입력/추적 시스템의 정보 흐름을 보여 주고 있습니다. 이러한 흐름은 백오피스 포탈 관리 사이트를 통해 구현됩니다. 모든 데이타는 단 한 차례만 입력되며, 각 담당자는 프로세스 단계별로 정보를 검증하는 역할만을 담당합니다. 주문 시스템에서 회계 시스템으로의 데이타 전달 과정 또한 자동화되었습니다.
PHP의 활용

프로젝트를 위한 기술 검토 과정에서, 우리는 개발 언어로 PHP를, 데이타베이스로는 오라클을 사용하기로 결정하였습니다. 이처럼 결정한 이유는 여러 가지가 있습니다. 먼저, Myers의 기존 백오피스 포탈이 오라클 데이타베이스를 기반으로 하는 PHP 코드로 구현되어 있었습니다. 따라서 기존 소스와의 호환성을 보장할 수 이씅ㄹ 뿐 아니라, 조직 내의 PHP 전문 인력을 활용할 수 있다는 장점이 있었습니다.


두 번째로, 과거의 경험을 통해 PHP가 다른 프로그래밍 언어에 비해 상대적으로 뛰어난 성능을 제공함을 확인할 수 있었습니다. PHP는 Apache 서버 내에 다이내믹하게 로드된 라이브러리로써 상주하기 때문에, 시스템에 연결을 설정하는 과정에서 추가적인 스타트업 시간을 필요로 하지 않습니다. 그 뿐 아니라 Zend 프로젝트를 통해 한층 최적화된 PHP 환경을 활용함으로써, 코드의 성능을 일정 수준 이상으로 보장하는 것이 가능했습니다. 또 PHP에서 사용하는 OCI 인터페이스 모듈은 C 코드로 컴파일되어 있기 때문에, 오라클 데이타베이스의 액세스 과정에서 뛰어난 성능을 보여줍니다.


세 번째로, PHP가 HTML에 임베드된 형태로 구현되므로, 사용자 인터페이스 간의 보다 자연스러운 연계가 가능하다는 장점이 있습니다. 다른 서버-사이드 스크립트 언어도 모두 이러한 장점을 가지고 있지만, 특히 PHP는 개발자와 설계 담당자 간의 업무 충돌을 방지하는데 효과적이었습니다. 또 PHP의 문법과 코드 라이브러리를 이용하면 어떠한 형태의 로직도 쉽게 구현할 수 있었습니다.


마지막으로, 모든 코드를 HTML 코드에 임베드함으로써, 표준 텍스트 파일을 이용한 소스 코드의 관리가 가능해졌다는 점을 들 수 있습니다. 우리는 버전 관리 시스템(revision control system)으로 CVS를 사용하고 있습니다. PHP 코드는 컴파일되지 않으므로, 데이타베이스에 저장된 텍스트 소스 파일을 가져와 웹 서버에 복사하는 작업만으로도 시스템 “빌드”를 구현할 수 있습니다. 따라서 복잡한 빌드 시스템을 생성하지 않고도 CVS의 컨트롤 메커니즘을 이용하여 버그 픽스를 순차적으로 적용하는 것이 가능합니다.


설정 변경이 용이하도록 스키마를 설계

아래 두 그림은 주문 시스템의 기본적인 스키마 다이어그램을 보여주고 있습니다. 두 스키마 모두 “prototyping” 테이블과 “transactional” 테이블로 구분되어 있습니다. prototyping 테이블은, 비즈니스 요구사항이 변경되는 경우 다시 코딩 작업을 수행하지 않고 시스템의 설정을 변경하는데 활용됩니다. transactional 테이블은 클라이언트가 실제로 입력한 주문에 관련한 상세한 정보를 저장하고 있습니다.





그림 3: 기본 스키마 다이어그램



 

그림 4: 기본 스키마 다이어그램 위 의 스키마 다이어그램은 다소 복잡해 보이는 것이 사실입니다. 하지만, (“_def” 접미사를 갖는) prototyping 테이블만을 따로 분리해서 본다면 아키텍처의 기본 구조를 보다 쉽게 이해할 수 있을 것입니다. 주문(order)은 order line 또는 detail을 포함하는 line group들로 구성됩니다. Order line을 이용하여 job을 생성할 수 있습니다. 각각의 job은 상세한 정보를 포함하는 일련의 task로 구성되며, task를 수행하기 위해서는 상세 정보의 입력이 필요합니다. Task는 큐에 저장되며, 각 큐는 특정 부서의 특정 담당자에 의해 접근 가능합니다.
우리는 시스템의 검증을 위해, 주문 시스템의 각 단계별로 프로토타입을 생성하기로 했습니다. 가장 먼저, order prototype 테이블로부터 정확한 주문을 생성하는 것이 가능한지 검증하기 위한 작업이 수행되었습니다. 1차적인 스키마 정의가 완료된 후, 가장 먼저 order form generator를 위한 프로토타입 시스템이 구현되었습니다.


이 시스템을 구축하기 위해 3명의 개발자와 (시스템을 실제로 사용하게 될) 다양한 부서의 관리자들로 구성된 팀이 구성되었습니다. 3명의 개발자들은 각각 설정(configuration), 디스플레이(display), 트랜잭션(transaction) 부분을 담당하는 것으로 결정되었습니다. 관련 부서의 관리자들은 초기 빌드 사이클에 참여하여. 사용자의 데이타 입력 및 처리 과정을 위한 인터페이스 구현에 필요한 피드백을 제공하였습니다.


PHP를 이용한 사용자 인터페이스의 렌더링

가장 먼저 기본적인 웹 사이트 order form의 프로토타입이 구현되었습니다. (webwiz.myersinternet.com/에 서 확인하실 수 있습니다.) 이 양식은 한 명의 개발자가 PHP를 사용하여 3일 만에 작성한 것입니다. PHP 코드만을 이용하여 주문 입력 과정을 명확하게 정의한 프로토타입을 구현하기 위해서는, 데이타베이스 설계에 약간의 수정을 가할 수 밖에 없었습니다. 따라서 order line group은 다음과 같은 두 가지 기능을 제공하는 것으로 정의되었습니다: (1) 입력 양식의 시각적인 구분을 통해, 유사한 아이템을 그룹화하여 렌더링함. (2) 기능적으로 유사한 아이템들을 그룹화하여 처리함.


우리는 PHP를 이용하여 스키마의 변경 작업과 form generator 구현 작업을 동시에 진행함으로써, 프로토타입 구현 작업은 매우 신속하게 완료할 수 있었습니다. 또 사용자 인터페이스를 고려하여 스키마를 설계하였기 때문에, 프로토타이핑 과정에서 다른 요구사항이 돌출하는 경우에도 변경 내역을 쉽게 반영할 수 있었습니다. 이렇게 해서 완성된 양식이 아래와 같습니다:




그림 5: Order Form의 생성 실제 시스템의 구현 프로토타입이 완성된 이후에는, 완전한 기능을 갖춘 order form의 구현 작업이 진행되었습니다. 먼저, order form에 입력되는 주문 내역을 시스템에 저장할 수 있어야 했습니다. 두 번째로, 주문을 입력하는 담당자가 기존에 저장된 데이타를 활용하여 폼의 입력 작업을 수행할 수 있어야 했습니다. 세 번째로, 폼을 제출하는 과정에서 요금을 지불해야 하는 경우를 위해 지불 프로세스가 별도로 구현되어야 했습니다. 마지막으로, 폼이 제출된 이후 필요한 후속 작업이 진행되어야 했습니다


PHP 개발 환경이 제공하는 높은 업무효율성 덕분에, 우리는 시각적/기능적인 측면에서 전체 주문입력 과정을 완벽하게 구현한 프로토타입을 짧은 기간 안에 구현할 수 있었습니다. 또 그 덕분에 시스템 아키텍처에 대한 피드백 정보를 충분히 얻을 수 있었습니다. 프로젝트 기간이 단축된다는 것은, 프로젝트에 관여하는 이들의 관심이 아직 식지 않은 동안에 작업을 완료할 수 있음을 의미합니다. 또 UI와 백엔드(미들티어 서버) 사이에 제3의 계층을 별도로 구현할 필요가 없었기 때문에, 적은 수의 개발자로 프로토타입 구현 과정을 신속하게 완료하는 것이 가능했습니다.


주문에 관련된 트랜잭션을 위한 job을 생성한 뒤에는, 주문 처리 사이클에 관련한 다양한 작업이 생성되었습니다. 주문 처리 프로세스에는 기본적으로 Task Assignment(태스크 할당), Task Processing(태스크 처리)를 위한 두 가지 UI 화면이 사용됩니다. Task Assignment 페이지는 아직 할당되지 않은 태스크를 저장한 큐의 정보와, 이미 사용자에게 할당된 태스크에 대한 정보를 동시에 표시하게 됩니다.


우리는 개발 과정에서, 자주 사용되는 기능이 무엇인지 확인하고, 이 기능들을 코드 라이브러리의 형태로 추상화하는 작업에 역점을 두었습니다. 특정 함수 또는 데이타 포인트가 여러 페이지에 걸쳐 사용되는 경우가 많았습니다. 여러 페이지에 중복적인 기능이 사용되는 경우에는, 이러한 기능을 PHP 코드 라이브러리의 형태로 추상화하는 것이 매우 효과적입니다.


Task Processing 페이지는 현재 처리해야 하는 주문에 대한 상세 정보, 그리고 이전에 현 담당자 또는 다른 담당자에 의해 처리되었거나 처리되어야 하는 주문에 대한 정보를 표시합니다. 이 페이지는 초기 프로토타입 과정에서 1차적으로 구현된 이후로 여러 차례의 수정 과정을 거쳤습니다.


Task Processing 페이지의 구현 과정에서 가장 어려운 요구사항의 하나로 다음과 같은 문제가 있었습니다:

  1. 특정 태스크가 종료되는 경우, 해당 태스크에 종속된 다른 태스크가 시작되어야 합니다.

  2. 특정 Job에 관련한 모든 태스크가 종료된 경우, Job 역시 함께 종료되어야 합니다.

  3. 특정 주문에 관련한 모든 Job이 종료된 경우, 해당 주문 역시 종료되어야 합니다.


물론 이 로직을 PHP 코드로 작성하는 것도 가능합니다. 하지만 일부 태스크 처리 과정에서 Perl 스크립트가 함께 사용되고 있었기 때문에, 표준적인 포맷을 이용한 작업의 구현이 필요했습니다. 태스크 종료에 관련된 모든 프로시저가 데이타베이스를 기반으로 구현되므로, PL/SQL로 작성하는 것이 가장 합리적이라는 결론이 내려졌습니다. 이와 같이 함으로써, 어떤 프로그래밍 언어를 사용하더라도 동일한 프로시저를 활용하여 데이타베이스 작업을 수행할 수 있는 환경이 마련되었습니다.


자동화 태스크 정의 테이블(automated task definition table)과 자동화 태스크 실행 테이블(automated task run table)을 이용하여 자동화 태스크(automated task)를 정의하였습니다. 태스크의 자동화 과정에서 기존에 작성된 CGI 스크립트를 불러올 필요가 있었기 때문에, Perl을 이용하여 코드를 작성하는 것으로 결정되었습니다. Perl 모듈이 제공하는 방대한 라이브러리를 이용하여 다양한 유형의 작업을 구현하는 것이 가능했습니다. 시스템에 상주하는 서버 프로세스는 1분 간격으로 실행되어 현재 대기 중인 자동화 태스크가 있는지 확인합니다. 자동화 태스크가 대기 중인 경우, 관련 상세 정보와 매개변수를 이용하여 태스크가 실행됩니다. 자동화 태스크의 실행 성공 여부는 로그에 저장됩니다. 자동화 태스크의 실행이 성공한 경우, PL/SQL 프로시저가 실행되고 태스크는 정상적으로 종료됩니다.


프로토타입의 통합

모든 주요 기능 영역에 대한 프로토타입을 구현한 뒤, Myers의 비즈니스 업무 사이클을 반영하는 전체 프로세스가 구현되었습니다. 주문 프로세스를 위한 프로토타입은 “new-client order”, “existing client order”, “special order”의 3개 모듈로 구분되어 구현되었습니다. “new-client order”에서는 영업담당자가 새로운 고객의 정보를 입력하고, 최초 셋업을 위한 요금을 설정하는 작업이 수행됩니다. “existing client order”에서는 웹 사이트 업그레이드, 전체/부분 서비스의 취소, 서비스 및 빌링의 변경과 같은 작업이 수행됩니다. “special order”에서는 고위 임직원에 의해서만 주문 가능한 특수한 아이템이 취급됩니다.


시스템이 점점 모양을 갖추어감에 따라, PHP의 효과가 드러나기 시작했습니다. 우리는 개발 작업을 진행하는 과정에서 태스크 프로세싱 페이지에 주문에 관련한 작업의 상세 정보를 표시해야 한다는 사실을 뒤늦게 깨달았지만, 불과 며칠 만에 개발 및 테스팅 작업을 완료할 수 있었습니다. 작업을 특정 시점까지 Sleep 모드로 유지하는 기능을 구현하기 위해 새로운 자동화 태스크를 추가하는 작업도 일 주일 만에 완료되었습니다.

또, 태스크의 종속 관계를 설정하는 과정에서도 기존 부서간 비즈니스 프로세스를 매우 쉽게 확인하고 수정하는 것이 가능했습니다. 데이타베이스 종속성 정보와 AT&T Research Graphviz가 제공하는 “dot” 패키지 ( www.graphviz.org 참고)를 이용하여 라이브 데이타를 그래픽 화면을 통해 확인할 수 있었습니다. 아래 그림은 웹 사이트 패키지의 셋업을 위한 작업 사이클을 보여주고 있습니다.



 

그림 6: 작업 사이클의 예 트 랜잭션 페이지 이외에도 다양한 시스템 메트릭을 활용하여, 매우 수준 높은 비즈니스 프로세스 리포트를 생성할 수 있었습니다. 비즈니스 요구사항에 따라 매출 리포트와 작업 현황 리포트를 생성하거나 수정하는 것이 가능했습니다. 이 리포트는 PHP를 통해 생성된 표준 HTML, 또는 Perl을 통해 생성된 Excel 스프레드시트의 형태로 제공되었습니다. 또 오라클의 분석 및 그룹핑 기능을 이용하여 요약 뷰를 구현함으로써 리포트의 일관성을 확보할 수 있었습니다.
프로젝트 일정

시스템 개발 작업은 2003년 5월에 시작되었습니다. 2003년 6월에는 핵심 시스템 컴포넌트의 구현이 모두 완료되었으며, 시스템의 최종적인 설정 작업이 시작되었습니다. 2003년 7월 말, 시스템의 구성 작업이 모두 완료되었고 내부 교육이 진행되었습니다. 시스템은 2003년 8월에 운영을 시작하였으며, 약 2,000 명의 클라이언트를 위해 4,000여 개의 주문, 6,000여 개의 job, 20,000여 개의 task를 수행하였습니다. 새로운 시스템이 도입된 이후, 사용자들은 개발자들의 도움을 받지 않고도 새로운 주문 및 작업을 위한 프로토타입을 생성할 수 있게 되었습니다. 또 별도의 코딩 과정을 거치지 않고도 클라이언트 트러블티켓 시스템을 구성하는 것이 가능했습니다.


프로젝트가 성공적으로 마무리될 수 있었던 것은 유연한 아키텍처와 짧은 개발 기간의 덕이 큽니다. 물론 시스템을 전혀 다른 방법으로 구현할 수도 있었겠지만, 우리가 선택한 방법은 유연성의 극대화와 개발 비용의 절감을 가능하게 했습니다.


아래 표는 각 컴포넌트에 소요된 개발 기간 및 개발 인력(man-hour)을 보여주고 있습니다. 또 비교를 위해, 기존 시스템을 구현하는데 소요된 전체 시간을 함께 표시하였습니다.



컴포넌트 새로운 시스템 개발 기간 기존 시스템
개발 기간
Web site sales order form 32 developer hours
7 business configuration hours
200 developer hours
Upgrade order forms 35 business configuration hours NA
Job assignment forms 12 developer hours 40 developer hours
Job processing forms 72 developer hours
15 business configuration hours
240 developer hours
Job conversation log forms 12 developer hours NA
Hourly billing tracking forms 8 developer hours 24 developer hours
Productivity reports 48 developer hours NA
Commission reports 6 developer hours NA
Revenue reports 40 developer hours NA
Marketing reports 24 developer hours NA





프로젝트의 교훈

아키텍처 및 시스템의 구현 과정에서, 우리는 각 기능별 프로토타입을 구현하는 기간을 단축하고, UI 설정 및 UI 코드 간의 긴밀한 통합을 이루고, 별도의 코딩 없이도 시스템의 다양한 설정을 변경할 수 있도록 하는 것이 중요하다는 사실을 깨달을 수 있었습니다. Oracle/PHP 개발 모델의 단순성과 유연성을 활용하여, 초기 구상, 기능별 프로토타입 구현, 운영 시스템의 구현에 이르는 전체 개발과정을 최단시간 내에 완료할 수 있었습니다.


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

PHP를 이용한 다중 연결 소켓 통신  (0) 2008.02.13
PHP 함수  (0) 2006.08.18
[PHPSchool] 오늘 본 상품 출력  (0) 2006.08.03
쇼핑몰에 들어가는 오늘 본 상품  (0) 2006.08.03
PHP를 이용한 개발 기간의 단축  (0) 2006.06.04
[PHPSchool] 일본어 송신 관련  (0) 2006.06.03
5년전에 잠시 일본에서 무역회사를 하던 때의 메일 주소를 아직도 쓰고
있는데 지금도 자주 한국에서 광고 메일이 들어오고 일본어 메일을
받아 보면 깨어지는 메일이 많이 있습니다. 특히 웹메일을 사용하여
다량으로 메일을 보내는 업체들에서 이러한 경우가 많이 발생합니다.
이런 깨진 메일 보내면 광고 효과는 커녕 역효과만 내리라고 생각합니다.
그리고 몇주 전에는 일본의 다이얼패드에서 보내온 일본어 메일을
받았는데 이 메일 역시 깨져 있었습니다. 어느 정도 이름이 있는 회사가
보낸 메일이 깨어 진다면 회사의 신용 문제와도 연결되는 것이라고
생각해 여기에 오시는 PHPER분들이 제작하는 일본어 메일 프로그램만큼은
절대로 깨어지지 않도록 하는 팁과 소스를 올려보고자 합니다.

의외로 한국의 큰 소프트 개발회사에서 개발한 일본어 소프트에서도
위와같은 사례를 많이 보아왔습니다. 아래의 내용은 PHP뿐만아니라 C,
PERL, JAVA로 개발을 하더라도 해당이 되는 부분입니다.

<철칙>
일본어 메일은 반드시 JIS코드로 송신해야합니다.

메일 해더의 캐릭터셋은 ISO-2022-JP로 합니다.

일본어가 들어간 송신자이름, 수신자이름, 제목, 파일명은 반드시
ISO-2022-JP코드로 64B인코딩해야합니다.
(예,Subject: =?ISO-2022-JP?B?GyRCI1cjRSNCRVBPPyROJCpDTiRpJDsbKEI=?=)

RFC규정에 Quoted Printable 인코딩 방법이 있는데 이 인코딩 방법은
일본어 메일 헤더내에 절대로 사용하지 않아야 합니다. 의외로 이 인코딩
방법을 쓰는 메일들을 많이 봅니다. 많은 클라이언트 메일러에서
이 인코딩 방법을 지원하지 않습니다. 그리고 I-MODE등의 휴대전화로
메일을 보냈을때 여지 없이 깨어집니다. 많은 비지네스맨들이
메일을 휴대전화로 전송하여 확인하고 있다는 사실을 염두해야 합니다....

Shift_JIS코드로 아무 처리없이 그냥 메일을 보내지 말아야 합니다.
일부 메일러에서는 처리해주는 경우가 있으나 아직도 미비합니다.
이렇게 보내지는 메일도 많이 보아 왔습니다.휴대전화에서는 깨짐.

가급적이면 Content-type: text/html; 을 사용하지 않는다. 대부분의
PC에서는 지원이 되나 휴대전화에서 지원하지 않아 많은 사람들이
짜증을 냅니다. 이 콘텐트 타이프를 사용하고자 할때는
Content-Type: multipart/alternative; 를 사용합니다.
이 때에는 처음 텍스트 파트는
------=_NextPart
Content-Type: text/plain;
charset="iso-2022-jp"
Content-Transfer-Encoding: 7bit
와 같은 방법으로 그냥 JIS코드로 본문을 작성합니다.
JIS코드는 ISO에 등록된 7bit코드라서 위와같이 지정합니다.
다음 HTML파트는
------=_NextPart
Content-Type: text/html;
charset="iso-2022-jp"
Content-Transfer-Encoding: quoted-printable
와 같은 방법으로 여기에서는 quoted-printable를 사용합니다.
일반적으로 64인코딩을 하는 경우도 있는데 일부 메일러와
휴대전화에서 지원하지 않습니다. 위와 같이 처리하면
휴대전화에서도 HTML로 송신된 메일을 받아볼 수 있습니다.

무작정 전부다 64인코딩이나 Q인코딩하지 말아야 합니다.
일부 텍스트 메일과 위의 multipart/alternative타입의 경우
메일 본문을 모두 64인코딩이나 Q인코딩하는 사례 역시 많이
발견합니다만, 휴대전화에서는 지원하지 않고 일본어 메일러에서
지원하지 않는 경우가 있습니다.

Content-Type: multipart/mixed; 의 경우도 위와 같은 방법으로
헤더와 메일 본문을 작성합니다.(아래 소스 참조)

(다 알고 있는 부분이겠지만...)
korea.internet.com의 경우 Content-Type: multipart/alternative; 를
사용하여 메일을 송신하고 있는데 위의 텍스트 부분은 64인코딩을하고
HTML부분은 Q인코딩하여 송신을 합니다. 이것은 한글의 완성형
euc-kr이나 ks_c_5601-1987은 8bit 코드 라서 텍스트 부분을 RFC규정에
준하여 64인코딩을 해주는 겁니다. 물론 이렇게 안해줘도 한국에서는
메일이 잘 전달이 됩니다. 요즈음은 거의 안 쓰이지만 이전의 7bit
ISO 한글코드를 사용하면 64인코딩을 하지 않아도 된다는 이야기 입니다.
이렇게 인코딩을 하는 것은 일부 메일 서버에서 아직도 8bit메일을 통과
시키지 못하는 경우가 가끔 있기 때문입니다.

철칙 끝

위의 철칙만 지키면 절대로 일본어 메일은 깨어지지 않습니다.

그 다음은 현재 PHP소스중 일본어 문자 코드를 변환하는 소스가
일본에도 거의 없습니다. 일반적으로 일본의 HTML에서 Shift_JIS와 EUC가
많이 사용이 되는데 웹에서 메일을 보내려면 Shift_JIS로 입력받은
데이터와 EUC로 입력 받은 데이터를 JIS로 변환하지 않으면 안됩니다.
일본의 경우 일본의 PHP유저 그룹에서 일본어 코드를 변환하는 함수를
C소스로 추가해서 국제판 PHP라는 이름으로 배포를 하여 일본에서는
이 버전을 많이 사용하고 있습니다. --- 나쁜놈들.. 무슨 국제판이야....
그러나 요즈음은 버전업이 자주되고 유저그룹에서 지원해 주지 못해
php.net에서 다운 받은 오리지날 버전 사용자가 늘어나고 있습니다...Good
그래서 이 오리지날 버전 PHP에서도 사용할 수 있도록 일본어 코드를
변환하는 소스를 이전 PERL로 작성한 것이 있어 PHP로 고쳐서 썼는데
속도가 따라주지 않아 일본의 유닉스 시스템의 NKF라는 한자 코드 변환
프로그램 C소스를 참고하여 PHP소스로 수정하였습니다. 아래의 소스를
참고하시기 바랍니다. 그리고 일부 고수분들이 pack함수를 쓰지 chr함수를
사용하였느냐는 질문이 있을것 같아서 설명을 드립니다.

chr함수가 실행속도 면에서 휠씬 빠릅니다.그리고 IEEE부동소수점을
채택하고 있는 메모리(IEEE사양의 메모리에서는 바이트 오더까지
규정하고 있지 않기 때문에)에서 메모리 상에서 계산을 할때 수치를
double에서 float로 변환하고 다시 double로 재 변환 하기 때문에
정밀도가 떨어져 어떤 기종의 컴퓨터에서 pack한 데이터를 다른
기종에서는 unpack할 수 없는 가능성이 있기 때문입니다.

아래는 Sendmail을 사용하여 일본어 메일과 첨부 파일을 송신하는
소스 샘플입니다. SMTP나 mail함수를 사용하여 보낼때는 아래 내용을
참고하여 적절히 헤더와 본문의 데이터를 수정하시면 됩니다.



function jpmail ($from_mail, $from_name, $to, $subject, $body, $attach, $filename)
{

$boundary = "____nogada" . uniqid("b");
$from_name = '=?ISO-2022-JP?B?'.base64_encode(euc2jis($from_name).'?=';

###입력값이 Shift_JIS 코드일때
//$from_name = '=?ISO-2022-JP?B?'.base64_encode(sjis2jis($from_name).'?=';

$from = $from_name." <".$from_mail.">";

### 제목을 jis(iso-2022-jp)코드로 변환해서 MIME 인코딩한다.
$subject = '=?ISO-2022-JP?B?'.base64_encode(euc2jis($subject)).'?=';

###입력값이 Shift_JIS 코드일때
//$subject = '=?ISO-2022-JP?B?'.base64_encode(sjis2jis($subject)).'?=';

### 본문을 jis코드로 변환한다.
$body = euc2jis($body);

###입력값이 Shift_JIS 코드일때
//$body = sjis2jis($body);

$body = str_replace(" ", " ", $body);
$body = str_replace(" ", " ", $body);

### 첨부할 파일 데이터를 base64 인코딩 한다.
$attach = base64_encode($attach);

### 파일명을 jis( iso-2022-jp)코드로해서 MIME 인코딩한다.
$filename = '=?ISO-2022-JP?B?'.base64_encode(euc2jis($filename).'?=';

###입력값이 Shift_JIS 코드일때
//$filename = '=?ISO-2022-JP?B?'.base64_encode(sjis2jis($filename).'?=';

### 메일 송신

$sendmail = '/usr/sbin/sendmail';

$MAIL = popen("$sendmail -f $from_mail $to", "w");

###송신자의 메일어드레스가 없어도 송신이 가능한 경우
//$MAIL = popen("$sendmail -t -f $to", "w");

########################## 메일 데이타 작성

### 본문 헤더

fputs($MAIL, "From: $from ");
fputs($MAIL, "To: $to ");
fputs($MAIL, "Subject: $subject ");
fputs($MAIL, "MIME-Version: 1.0 ");
fputs($MAIL, "Content-Type: Multipart/Mixed; ");
fputs($MAIL, " ? ?boundary="$boundary" ");
fputs($MAIL, " ");

### 메일 본문 파트
fputs($MAIL, "This is a multi-part message in MIME format. ");
fputs($MAIL, " ");
fputs($MAIL, "--$boundary ");
fputs($MAIL, "Content-Type: text/plain; ");
fputs($MAIL, " ? ?charset="ISO-2022-JP" ");
fputs($MAIL, "Content-Transfer-Encoding: 7bit ");
fputs($MAIL, " ");
fputs($MAIL, "$body ");

### 첨부파일 파트
fputs($MAIL, "--$boundary ");
fputs($MAIL, "Content-Type: application/octet-stream; ");
fputs($MAIL, " ? ?name="$filename" ");
fputs($MAIL, "Content-Transfer-Encoding: base64 ");
fputs($MAIL, "Content-Disposition: attachment; ");
fputs($MAIL, " ? ?filename="$filename" ");
fputs($MAIL, " ");
fputs($MAIL, "$attach ");
fputs($MAIL, " ");

### 멀티 파트 끝
fputs($MAIL, "--$boundary" . "-- ");
pclose($MAIL);
}

###Shift_JIS코드를 JIS코드로 변환
function sjis2jis(&$sjis_string)
{
$jis_code = ';
$c = 0;
$b = unpack("C*", $sjis_string);
$n = count($b);
$esc = array(chr(0x1B).chr(0x28).chr(0x42),
chr(0x1B).chr(0x24).chr(0x42),
chr(0x1B).chr(0x28).chr(0x49));

for ($i = 1; $i <= $n; $i++) {
$b1 = $b[$i];
if (0xA1 <= $b1 && $b1 <= 0xDF) {
if ($c != 2) {
$c = 2;
$jis_code .= $esc[$c];
}
$jis_code .= chr($b1 - 0x80);
} elseif ($b1 >= 0x80) {
if ($c != 1) {
$c = 1;
$jis_code .= $esc[$c];
}
$b2 = $b[$i+1];
$b1 <<= 1;
if ($b2 < 0x9F) {
if ($b1 < 0x13F) $b1 -= 0xE1; else $b1 -= 0x61;
if ($b2 > 0x7E) $b2 -= 0x20; else $b2 -= 0x1F;
} else {
if ($b1 < 0x13F) $b1 -= 0xE0; else $b1 -= 0x60;
$b2 -= 0x7E;
}
$jis_code .= chr($b1).chr($b2);
$i++;
} else {
if ($c != 0) {
$c = 0;
$jis_code .= $esc[$c];
}
$jis_code .= chr($b1);
}
}
if ($c != 0) $jis_code .= $esc[0];

return $jis_code;
}

###일본어 EUC코드를 JIS코드로 변환
function euc2jis(&$euc_string)
{
$jis_code = ';
$c = 0;
$b = unpack("C*", $euc_string);
$n = count($b);
$esc = array(chr(0x1B).chr(0x28).chr(0x42),
chr(0x1B).chr(0x24).chr(0x42),
chr(0x1B).chr(0x28).chr(0x49));

for ($i = 1; $i <= $n; $i++) {
$b1 = $b[$i];
if ($b1 == 0x8E) {
if ($c != 2) {
$c = 2;
$jis_code .= $esc[$c];
}
$jis_code .= chr($b[$i+1] - 0x80);
$i++;
} elseif ($b1 > 0x8E) {
if ($c != 1) {
$c = 1;
$jis_code .= $esc[$c];
}
$jis_code .= chr($b1 - 0x80).chr($b[$i+1] - 0x80);
$i++;
} else {
if ($c != 0) {
$c = 0;
$jis_code .= $esc[$c];
}
$jis_code .= chr($b1);
}
}
if ($c != 0) $jis_code .= $esc[0];

return $jis_code;
}

?>

###메일 송신 테스트 샘플

$from_name = 坂本
$from_mail = "my@sendmail.com";
$to = "your@testmail.com";
$subject = "日本語題名";
$body = "これは日本語の本文です.";
$attach_data = "これは添付ファイルです. ";
$attach_data .= "添付ファイルはバイナリデ-タの場合もあります. ";
$filename = "日本語ファイル.txt";

jpmail( $from_mail, $from_name, $to, $subject, $body, $attach_data, $filename);

メ-ル送信完了

?>

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

PHP를 이용한 다중 연결 소켓 통신  (0) 2008.02.13
PHP 함수  (0) 2006.08.18
[PHPSchool] 오늘 본 상품 출력  (0) 2006.08.03
쇼핑몰에 들어가는 오늘 본 상품  (0) 2006.08.03
PHP를 이용한 개발 기간의 단축  (0) 2006.06.04
[PHPSchool] 일본어 송신 관련  (0) 2006.06.03

+ Recent posts