메인 컨텐츠로 가기

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관 보기.

developerWorks에 처음 로그인하면 developerWorks프로파일이 생성됩니다.귀하의 프로파일에서 동의하신 내용이 공개되지만 이 사항은 언제든지 변경 가능합니다. 귀하의 성명(숨김으로 체크되어 있어도 표시됩니다)과 디스플레이 이름은 게시한 컨텐츠나 사이트 엑세스시 표시됩니다.

모든 정보가 안전하게 전송되었습니다.

  • 닫기 [x]

처음 developerWorks에 로그인할 때 프로파일이 작성되므로, 이를 위해 디스플레이 이름을 선택해야 합니다. 선택하신 디스플레이 이름은 developerWorks에 게시한 컨텐츠에 표시됩니다.

3글자 이상 31글자 이하의 길이로 사용 가능합니다. dW커뮤니티 내에서는 보안상 이메일주소를 제외한 다른 이름을 지정하셔야 합니다.

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관 보기.

모든 정보가 안전하게 전송되었습니다.

  • 닫기 [x]

Zend Framework로 SOAP 서비스 구현하기

Zend Framework로 신속하게 SOAP 서비스를 PHP 웹 애플리케이션에 추가하기

Vikram Vaswani, Founder, Melonfire
Vikram Vaswani는 오픈 소스 도구와 기술을 전문으로 다루는 컨설팅 서비스 회사인 Melonfire의 창립자이자 CEO이며 PHP Programming SolutionsPHP: A Beginners Guide의 저자이기도 하다.

요약:  Zend Framework는 강력하고 확장 가능한 PHP 웹 애플리케이션을 빌드하는 데 필요한 MVC 준수 프레임워크입니다. Zend Framework에는 개발자가 신속하고 효율적으로 SOAP 기반 웹 서비스를 애플리케이션에 추가할 수 있게 하는 Zend_Soap 컴포넌트가 포함되어 있습니다. 이 기사에서는 Zend_Soap 컴포넌트에 대해 자세히 살펴보고 SOAP 웹 서비스를 빌드하는 방법에 대해 설명하고 입력 유효성 검증, 결함 생성 및 WSDL 자동 작성과 같은 기능을 조사합니다.

원문 게재일:  2010 년 5 월 11 일 번역 게재일:   2010 년 8 월 20 일
난이도:  중급 영어로:  보기 PDF:  A4 and Letter (342KB | 24 pages)Get Adobe® Reader®
페이지뷰:  4345 회
의견:  


소개

웹 서비스는 요즈음 크게 유행하고 있으며 REST 기반 서비스가 특히 가장 큰 부분을 차지하고 있다. REST는 단순성 및 직관성과 기존 HTTP 메소드에 대해 작업할 수 있는 기능으로 인해 자주 사용된다. 하지만 REST에도 경쟁자가 있다. SOAP(Simple Object Access Protocol)도 웹을 통한 정보 교환의 문제에 대해 더 공식적이고 표준화된 접근 방식을 제공한다.

자주 사용하는 약어

  • API: Application Programming Interface
  • HTTP: Hypertext Transfer Protocol
  • i18n: Internationalization
  • MVC: Model-View-Controller
  • OOP: Object-Oriented Programming
  • REST: Representational State Transfer
  • SQL: Structured Query Language
  • URI: Uniform Resource Identifier
  • URL: Uniform Resource Locator
  • W3C: World Wide Web Consortium
  • WSDL: Web Services Description Language
  • XML: Extensible Markup Language

SOAP 기반 서비스는 일반적으로 복잡하고 구현하는 데 시간이 많이 걸리는 것으로 인식되고 있지만 다수의 기존 도구를 사용하여 프로세스를 상당히 단순화할 수 있다. 이러한 도구 중 하나는 PHP로 확장 가능한 웹 애플리케이션을 빌드하는 데 필요한 완전한 MVC 프레임워크를 제공하는 Zend Framework이다. 다른 많은 유용한 도구—OOP 양식, i18n 지원, 쿼리 및 페이지 캐싱, Dojo 통합 등—뿐만 아니라 Zend Framework도 Zend_Soap 컴포넌트를 통해 SOAP 서비스를 작성하고 전개하는 데 필요한 포괄적인 툴킷을 제공한다.

이 기사에서는 Zend Framework로 단순 SOAP 기반 웹 서비스를 빌드하는 프로세스에 대해 설명한다. 클라이언트 요청을 처리하고 SOAP 준수 응답을 다시 전송하는 방법에 대해 배우는 것 외에도 예외 처리 및 SOAP 결함 생성 프로세스에 대해서도 살펴본다. 또한 마지막으로 Zend_Soap를 사용하여 SOAP 서비스에 대해 설명하는 WSDL 파일을 자동으로 생성하여 클라이언트가 SOAP 서비스 API를 '자동 발견'할 수 있도록 한다.


SOAP 이해하기

먼저 SOAP에 대해 간단하게 설명한다. SOAP는 언어 독립적인 XML을 사용하여 웹을 통해 정보를 교환하는 방법이며 이를 통해 다른 언어로 작성된 애플리케이션이 서로 연결될 수 있다. 이 XML은 HTTP를 전송 프로토콜로 사용하여 클라이언트와 서버 사이에서 전송되며 데이터 무결성을 보장하기 위해 강력한 데이터 입력을 사용한다.

자원조치에 집중하는 REST와는 달리 SOAP는 메소드데이터 유형을 기반으로 한다. REST 서비스는 일반적으로 네 가지 HTTP 메소드인 GET, POST, PUT 및 DELETE에 해당하는 네 가지 조작으로 제한되지만 SOAP 서비스에는 이러한 제한이 없기 때문에 개발자가 정의하려고 하는 수만큼 메소드를 노출할 수 있다. 또한 이러한 메소드는 일반적으로 POST HTTP 메소드를 사용하여 호출되며 이 메소드는 요청 중인 조작의 유형과 아무런 관계가 없다.

SOAP의 작동 방식에 대해 설명하기 위해 단순한 예제를 사용한다. 책갈피 공유 애플리케이션이 있으며 써드파티 개발자가 SOAP를 사용하여 애플리케이션에서 책갈피를 추가하고 검색할 수 있도록 하려고 한다고 가정한다. 일반적으로는 getBookmark()addBookmark()와 같은 메소드를 사용하여 서비스 오브젝트 세트를 구현하고 SOAP 서버를 통해 이러한 서비스 오브젝트를 노출한다. 서버는 SOAP 데이터 유형을 원시 데이터 유형으로 변환하고 SOAP 요청 패킷을 구문 분석하고 해당 서버 메소드를 실행하며 결과와 함께 SOAP 응답 패킷을 생성한다.

Listing 1에는 getBookmark() 프로시저에 대한 SOAP 요청이 어떻게 나타나는지에 대한 예제가 있다.


Listing 1. 예제 SOAP 요청
      
POST /soap HTTP/1.1
Host: localhost
Connection: Keep-Alive
User-Agent: PHP-SOAP/5.3.1
Content-Type: application/soap+xml; charset=utf-8
Content-Length: 471

<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" 
 xmlns:ns1="http://example.localhost/index/soap" 
 xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:enc="http://www.w3.org/2003/05/soap-encoding">
<env:Body>
<ns1:getBookmark env:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
<param0 xsi:type="xsd:int">4682</param0>
</ns1:getBookmark>
</env:Body>
</env:Envelope>

또한 Listing 2에서는 샘플 응답을 보여 준다.


Listing 2. 예제 SOAP 응답
      
HTTP/1.1 200 OK
Date: Wed, 17 Mar 2010 17:13:28 GMT
Server: Apache/2.2.14 (Win32) PHP/5.3.1
X-Powered-By: PHP/5.3.1
Content-Length: 800
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: application/soap+xml; charset=utf-8

<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" 
 xmlns:ns1="http://example.localhost/index/soap" 
 xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
 xmlns:enc="http://www.w3.org/2003/05/soap-encoding" 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<env:Body xmlns:rpc="http://www.w3.org/2003/05/soap-rpc">
<ns1:getBookmarkResponse 
 env:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
<rpc:result>return</rpc:result>
<return enc:itemType="xsd:string" enc:arraySize="3" 
 xsi:type="enc:Array">
<item xsi:type="xsd:string">http://www.google.com</item>
<item xsi:type="xsd:string">http://www.php-programming-solutions.com
</item>
<item xsi:type="xsd:string">http://www.mysql-tcr.com</item>
</return>
</ns1:getBookmarkResponse>
</env:Body>
</env:Envelope>

일반적인 SOAP 트랜잭션에서 서버는 Listing 1에 있는 항목과 같은 XML 인코딩된 요청을 승인하고 XML을 구문 분석하고 해당 서비스 오브젝트 메소드를 실행하며 Listing 2에 있는 항목과 같은 XML 인코딩된 응답을 요청하는 클라이언트에 리턴한다. 클라이언트는 향후 처리를 위해 일반적으로 이 SOAP 응답을 언어별 오브젝트 또는 데이터 구조로 구문 분석하고 변환할 수 있다. 선택적인 WSDL 파일을 사용하여 입력 인수 및 리턴값의 수와 데이터 유형 및 사용 가능한 메소드에 대한 정보를 클라이언트에 제공할 수 있다.

Zend Framework는 SOAP 클라이언트 및 서버와 자동 WSDL 파일 생성을 위한 구현과 함께 제공된다. 서버와 클라이언트 구현은 PHP의 SOAP 확장 주위의 랩퍼이다. 이는 PHP 빌드에 SOAP 확장에 대한 지원이 포함되지 않은 경우 이러한 구현은 작동하지 않음을 의미한다. 즉, 원시 확장을 통해 Zend Framework 라이브러리를 사용하면 개발자는 서비스 API를 구현하는 오브젝트 세트를 정의한 후 서버에 접속시켜 수신 요청을 처리하기만 하면 되기 때문에 작업이 좀 더 단순해진다. 다음 섹션에서는 이 내용에 대해 자세히 다룬다.


예제 애플리케이션 설정하기

SOAP 서비스 구현을 시작하기 전에 몇 가지 참고사항과 가정이 있다. 이 기사 전반에서 필자는 사용자가 Apache, PHP+SOAP 및 MySQL에 대해 작동 중인 개발 환경을 가지고 있고 Zend Framework가 PHP 포함 경로에 설치되어 있으며 사용자가 SQL, XML 및 SOAP의 기본사항에 익숙하다고 가정한다. 또한 필자는 사용자가 Zend Framework를 사용한 애플리케이션 개발의 기본 원칙에 대해 잘 알고 있으며 조치와 제어기 사이의 상호작용을 이해하고 Zend_Db 데이터베이스 추상 계층에 익숙하다고 가정한다. 마지막으로 필자는 사용자의 Apache 웹 서버가 .htaccess 파일을 통한 URL 재작성 및 가상 호스팅을 지원하도록 구성되어 있다고 가정한다. 이러한 주제에 익숙하지 않은 경우에는 이 기사의 참고자료에서 자세한 정보에 대한 링크를 확인한다.

이 기사에서 구현할 예제 SOAP 서비스를 사용하면 써드파티 개발자가 애플리케이션 데이터베이스에서 제품 목록을 추가, 편집, 삭제 및 검색할 수 있다. 해당 서비스는 다음 메소드(모두 표준 SOAP 클라이언트를 사용하여 액세스할 수 있음)를 노출한다.

  • getProducts(): 데이터베이스에서 모든 제품을 리턴함
  • getProduct($id): 데이터베이스에서 특정 제품을 리턴함
  • addProduct($data): 데이터베이스에 새 제품을 추가함
  • deleteProduct($id): 데이터베이스에서 특정 제품을 삭제함
  • updateProduct($id, $data): 새 값으로 데이터베이스의 특정 제품을 업데이트함

1단계: 새 애플리케이션 초기화하기

시작하려면 먼저 이 기사에 표시된 코드에 대한 컨텍스트를 제공하는 표준 Zend Framework 애플리케이션을 설정한다. Zend Framework 도구 스크립트(Windows®의 경우에는 zf.bat, UNIX™의 경우에는 zf.sh)를 사용하여 아래와 같이 새 프로젝트를 초기화한다.

shell> zf.bat create project example

이제 Apache 구성에서 이 애플리케이션의 새 가상 호스트(예: http://example.localhost/)를 정의하고 가상 호스트의 문서 루트가 애플리케이션의 public/ 디렉토리를 가리키도록 할 수 있다. 그런 다음 이 호스트로 이동하면 그림 1과 같이 기본 Zend Framework 시작 페이지가 표시된다.


그림 1. 기본 Zend Framework 시작 페이지
기본 Zend Framework 시작 페이지의 화면 캡처

2단계: 애플리케이션 데이터베이스 및 모델 초기화하기

다음 단계는 애플리케이션 데이터베이스를 초기화하는 것이다. 따라서 아래와 같이 제품 정보를 보유할 새 MySQL 테이블을 작성한다.

mysql> CREATE TABLE IF NOT EXISTS products (
    ->   id int(11) NOT NULL AUTO_INCREMENT, 
    ->   title varchar(200) NOT NULL,
    ->   shortdesc text NOT NULL,
    ->   price float NOT NULL,
    ->   quantity int(11) NOT NULL,
    ->   PRIMARY KEY (id)
    -> ) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

아래와 같이 샘플 레코드로 이 테이블을 채워 시작한다.

mysql> INSERT INTO products (id, title, shortdesc, price, quantity) VALUES(1, 
    ->  'Ride Along Fire Engine', 'This red fire engine is ideal for toddlers who want 
    ->  to travel independently. Comes with flashing lights and beeping horn.', 
    ->  69.99, 11);
Query OK, 1 row affected (0.08 sec)

mysql> INSERT INTO products (id, title, shortdesc, price, quantity) VALUES(2, 
    -> 'Wind-Up Crocodile Bath Toy', 'This wind-up toy is the perfect companion for hours 
    -> of bathtub fun.', 7.99, 67);
Query OK, 1 row affected (0.08 sec)

3단계: 애플리케이션 네임스페이스 구성하기

최종 단계는 Zend Framework 자동 로드 프로그램의 애플리케이션 네임스페이스를 구성하는 것이다. 이 단계는 필요에 따라 애플리케이션 특정 클래스를 애플리케이션으로 자동으로 로드하는 데 도움이 된다. 이 경우 필자는 애플리케이션 네임스페이스가 Example이며 애플리케이션 특정 클래스(예: SOAP 서비스 클래스)가 $PROJECT/library/Example/에 저장된다고 가정한다. 그러므로 $PROJECT/application/configs/application.ini에서 애플리케이션 구성 파일을 업데이트한 후 다음 행을 해당 파일에 추가한다.

autoloaderNamespaces[] = "Example_"

이제 SOAP 서비스 작성을 시작할 준비가 완료되었다.


데이터 검색하기

예제 애플리케이션이므로 필자는 간결함을 유지하고 기본 모듈의 IndexController 자체에서 SOAP 요청을 처리하는 조치를 작성한다. 하지만 실제로는 SOAP 요청을 처리할 별도의 제어기를 유지하려고 할 것이다. Listing 3과 같이 $PROJECT/application/controllers/IndexController.php 파일을 편집하여 새 조치를 해당 파일에 추가한다.


Listing 3. soapAction() 정의
            
<?php
class IndexController extends Zend_Controller_Action
{
    public function soapAction()
    {
      // disable layouts and renderers
      $this->getHelper('viewRenderer')->setNoRender(true);
      
      // initialize server and set URI
      $server = new Zend_Soap_Server(null, 
        array('uri' => 'http://example.localhost/index/soap'));

      // set SOAP service class
      $server->setClass('Example_Manager');

      // handle request
      $server->handle();
    }
}

Listing 3에서는 비WSDL 모드에서 새 Zend_Soap_Server 오브젝트를 초기화하여 널값을 첫 번째 인수로 오브젝트 생성자에 전달한다. 비WSDL 모드에서 서버를 설정하는 경우에는 서버 URI를 지정해야 한다. Listing 3에서는 생성자에 두 번째 인수로 전달된 옵션 배열에서 서버 URI가 지정된다.

다음으로 서버 오브젝트의 setClass() 메소드를 사용하여 서비스 클래스를 서버에 접속한다. 이 클래스는 SOAP 서비스의 사용 가능한 메소드를 구현한다. 서버는 SOAP 요청에 대한 응답으로 이러한 메소드를 자동으로 호출한다. 원하는 경우에는 setClass() 메소드로 클래스를 첨부하는 대신 addFunction()loadFunctions() 메소드를 사용하여 사용자 정의 함수를 서버에 첨부할 수도 있다.

앞서 언급했듯이 Zend_Soap_Server 클래스는 자체 SOAP 서버 구현을 제공하지 않고 단순히 PHP의 내장 SOAP 확장 주위의 랩퍼만 제공한다. 따라서 모든 예비 단계가 처리되면 Listing 3handle() 메소드는 내장 PHP SoapServer 오브젝트를 초기화한 후 이 오브젝트에 요청 오브젝트를 전달하고 해당 오브젝트의 handle() 메소드를 호출하여 SOAP 요청을 처리한다.

모든 사항이 문제 없이 수행되었지만 서비스 클래스가 정의되지 않았기 때문에 작업이 많이 진행되지는 않는다. 다음으로 Listing 4에 있는 코드를 사용한 후 클래스 정의를 $PROJECT/library/Example/Manager.php에 저장하여 서비스 클래스를 작성한다.


Listing 4. get*() 메소드가 정의된 서비스 오브젝트
            
<?php
class Example_Manager {

    /**
     * Returns list of all products in database
     *
     * @return array
     */
    public function getProducts() 
    {
      $db = Zend_Registry::get('Zend_Db');        
      $sql = "SELECT * FROM products";      
      return $db->fetchAll($sql);      
    }

    /**
     * Returns specified product in database
     *
     * @param integer $id
     * @return array|Exception
     */
    public function getProduct($id) 
    {
      if (!Zend_Validate::is($id, 'Int')) {
        throw new Example_Exception('Invalid input');          
      }
      $db = Zend_Registry::get('Zend_Db');        
      $sql = "SELECT * FROM products WHERE id = '$id'";   
      $result = $db->fetchAll($sql);      
      if (count($result) != 1) {        
        throw new Exception('Invalid product ID: ' . $id);  
      } 
      return $result;  
    }
}
?>

Listing 4에서는 두 개의 메소드가 포함된 독립형 서비스 클래스를 설정한다. getProducts() 메소드는 Zend_Db를 사용하여 테이블에서 사용 가능한 모든 제품 레코드를 검색하여 배열로 리턴하지만 getProduct() 메소드는 제품 ID를 승인한 후 지정된 레코드만 리턴한다. 그런 다음 SOAP 서버는 메소드 리턴값을 SOAP 응답 패킷으로 변환한 후 다시 요청하는 클라이언트로 전송한다. Listing 8에 이 응답 패킷의 모양에 대한 예제가 있다.

Zend_Db가 초기화되는 위치를 궁금해 할 경우를 대비해 이 작업은 $PROJECT/application/Bootstrap.php에 있는 애플리케이션 부트스트랩 프로그램에서 수행된다. Bootstrap.php 파일에는 Zend_Db adapter 어댑터를 설정하여 애플리케이션 레지스트리에 등록하는 _initDatabase() 함수가 포함되어 있다. Listing 5에 코드가 있다.


Listing 5. 데이터베이스 어댑터 초기화
            
<?php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
  protected function _initDatabase()
  {
    $db = new Zend_Db_Adapter_Pdo_Mysql(array(
        'host'     => 'localhost',
        'username' => 'user',
        'password' => 'pass',
        'dbname'   => 'example'
    ));
    Zend_Registry::set('Zend_Db', $db); 
  }
}

이 코드가 어떻게 적용되는지 보려면 SOAP 클라이언트(Listing 6)를 작성한 후 이 클라이언트를 사용하여 SOAP 서비스에 연결하여 getProducts() 메소드를 요청한다.


Listing 6. 예제 SOAP 클라이언트
            
<?php
// load Zend libraries
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Soap_Client');

// initialize SOAP client
$options = array(
  'location' => 'http://example.localhost/index/soap',
  'uri'      => 'http://example.localhost/index/soap'
);

try {
  $client = new Zend_Soap_Client(null, $options);  
  $result = $client->getProducts();
  print_r($result);
} catch (SoapFault $s) {
  die('ERROR: [' . $s->faultcode . '] ' . $s->faultstring);
} catch (Exception $e) {
  die('ERROR: ' . $e->getMessage());  
}
?>

SOAP 클라이언트는 요청 패킷을 생성한다(Listing 7).


Listing 7. getProducts() 메소드에 대한 샘플 SOAP 요청
            
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" 
 xmlns:ns1="http://example.localhost/index/soap" 
 xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
 xmlns:enc="http://www.w3.org/2003/05/soap-encoding">
<env:Body>
<ns1:getProducts env:encodingStyle="http://www.w3.org/2003/05/soap-encoding"/>
</env:Body>
</env:Envelope>

서버는 SOAP 인코딩된 응답을 사용하여 여기에 응답한다(Listing 8).


Listing 8. getProducts() 메소드에 대한 샘플 SOAP 응답
            
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" 
 xmlns:ns1="http://example.localhost/index/soap" 
 xmlns:ns2="http://xml.apache.org/xml-soap" 
 xmlns:enc="http://www.w3.org/2003/05/soap-encoding" 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<env:Body xmlns:rpc="http://www.w3.org/2003/05/soap-rpc">
<ns1:getProductsResponse 
 env:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
<rpc:result>return</rpc:result>
<return enc:itemType="ns2:Map" enc:arraySize="2" xsi:type="enc:Array">
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">id</key>
<value xsi:type="xsd:string">1</value>
</item>
<item>
<key xsi:type="xsd:string">title</key>
<value xsi:type="xsd:string">Ride Along Fire Engine</value>
</item>
<item>
<key xsi:type="xsd:string">shortdesc</key>
<value xsi:type="xsd:string">This red fire engine is ideal 
 for toddlers who want to travel independently. 
 Comes with flashing lights and beeping horn.</value>
</item>
<item>
<key xsi:type="xsd:string">price</key>
<value xsi:type="xsd:string">69.99</value>
</item>
<item>
<key xsi:type="xsd:string">quantity</key>
<value xsi:type="xsd:string">11</value>
</item>
</item>
...
</return>
</ns1:getProductsResponse>
</env:Body>
</env:Envelope>

그림 2와 같이 SOAP 클라이언트는 추가로 처리하거나 조사할 수 있는 원시 PHP 배열로 이 응답을 다시 변환한다.


그림 2. 원시 PHP 배열로 변환된 SOAP 요청의 결과
원시 PHP 배열로 변환된 SOAP 요청의 결과

데이터 추가, 삭제 및 업데이트하기

따라서 여기서는 SOAP를 통한 데이터 검색을 처리한다. 이제는 데이터를 추가하고 삭제해 보자.

Example_Manager 클래스에서 addProduct() 메소드를 구현하는 것은 매우 쉽다. Listing 9에서는 이러한 구현을 보여 준다.


Listing 9. addProduct() 메소드가 정의된 SOAP 서비스 오브젝트
            
<?php
class Example_Manager 
{
   /**
     * Adds new product to database
     *
     * @param array $data array of data values with keys -> table fields
     * @return integer id of inserted product
     */
    public function addProduct($data) 
    {      
      $db = Zend_Registry::get('Zend_Db');        
      $db->insert('products', $data);
      return $db->lastInsertId();
    }
}

Listing 9에 있는 addProduct() 메소드는 새 제품 레코드를 키-값 쌍의 배열로 승인한 후 Zend_Db 오브젝트의 insert() 메소드를 사용하여 이 레코드를 데이터베이스 테이블에 쓴다. 이 메소드는 새로 삽입된 레코드의 ID를 리턴한다.

제품 삭제도 마찬가지로 간단하다. 제품 ID를 입력으로 승인한 후 Zend_Db delete() 메소드를 사용하여 데이터베이스에서 레코드를 제거하는 deleteProduct() 메소드를 추가하기만 하면 된다. Listing 10에서는 이 메소드의 모양을 보여 준다.


Listing 10. deleteProduct() 메소드가 정의된 SOAP 서비스 오브젝트
            
<?php
class Example_Manager 
{
    /**
     * Deletes product from database
     *
     * @param integer $id
     * @return integer number of products deleted
     */
    public function deleteProduct($id) 
    {
      $db = Zend_Registry::get('Zend_Db');        
      $count = $db->delete('products', 'id=' . $db->quote($id));
      return $count;
    }
}

Listing 10에서는 delete() 메소드에 전달된 두 번째 인수가 DELETE 조작 수행 시 사용될 제한조건 또는 필터를 지정한다. 이 인수를 포함하는 것이 매우 중요하다. 이 인수가 없으면 Zend_Db는 테이블에 있는 모든 레코드를 삭제한다.

마지막으로 Listing 11에서는 새로운 값으로 제품 레코드를 업데이트하는 데 사용할 수 있는 updateProduct() 메소드를 보여 준다. 이 메소드는 두 가지 입력 인수—제품 ID와 개정된 레코드가 포함된 배열—를 승인한 후 Zend_Db의 update() 메소드를 사용하여 데이터베이스 테이블에서 UPDATE 쿼리를 실행한다.


Listing 11. updateProduct() 메소드가 정의된 SOAP 서비스 오브젝트
    
<?php
class Example_Manager 
{
    /**
     * Updates product in database
     *
     * @param integer $id
     * @param array $data
     * @return integer number of products updated
     */
    public function updateProduct($id, $data) 
    {
      $db = Zend_Registry::get('Zend_Db');        
      $count = $db->update('products', $data, 'id=' . $db->quote($id));
      return $count;        
    }
}

Listing 12에 있는 것과 같은 SOAP 클라이언트를 사용하여 이들 항목을 모두 사용해 볼 수 있다.


Listing 12. 예제 SOAP 클라이언트
    
<?php
// load Zend libraries
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Soap_Client');

// initialize SOAP client
$options = array(
  'location' => 'http://example.localhost/index/soap',
  'uri'      => 'http://example.localhost/index/soap'
);

try {
  // add a new product
  // get and display product ID
  $p = array(
    'title'     => 'Spinning Top',
    'shortdesc' => 'Hours of fun await with this colorful spinning top. 
      Includes flashing colored lights.',
    'price'     => '3.99',
    'quantity'  => 57 
  );
  $client = new Zend_Soap_Client(null, $options);  
  $id = $client->addProduct($p);
  echo 'Added product with ID: ' . $result;

  // update existing product
  $p = array(
    'title'     => 'Box-With-Me Croc',
    'shortdesc' => 'Have fun boxing with this inflatable crocodile, 
      made of tough, washable rubber.',
    'price'     => '12.99',
    'quantity'  => 25 
  );
  $client->updateProduct($id, $p);
  echo 'Updated product with ID: ' . $id;

  // delete existing product
  $client->deleteProduct($id);
  echo 'Deleted product with ID: ' . $id;  
} catch (SoapFault $s) {
  die('ERROR: [' . $s->faultcode . '] ' . $s->faultstring);
} catch (Exception $e) {
  die('ERROR: ' . $e->getMessage());  
}
?>


SOAP 결함 생성하기

이전 섹션에 표시된 모든 메소드의 한 가지 문제점은 입력 유효성 검증 또는 필터를 포함하고 있지 않다는 것이다. 실제로는 이 유형의 유효성 검증을 생략하면 애플리케이션 데이터베이스의 무결성에 심각한 영향을 미치며 잘해야 데이터 손상이 발생하고 최악의 경우에는 전체가 파괴될 수 있다.

다행히도 Zend Framework에는 대부분의 일반적인 시나리오에 대한 내장 유효성 검증기를 제공하는 Zend_Validate 컴포넌트가 포함되어 있다. 이 기능을 Zend_Soap_Server의 registerFaultException() 메소드와 결합하여 클라이언트가 제공하는 요청 데이터를 테스트하고 다양한 오류 시나리오에서의 SOAP 결함을 다시 전송할 수 있다.

이러한 진행 과정을 보려면 Listing 13과 같이 Zend_Exception을 확장하는 사용자 정의 예외 클래스를 작성하는 것부터 시작한다.


Listing 13. 사용자 정의 예외 클래스
    
<?php
class Example_Exception extends Zend_Exception {}    

이 클래스를 $PROJECT/library/Example/Exception.php에 저장한다.

그런 다음 다양한 서비스 클래스 메소드를 업데이트하여 입력 유효성 검증을 포함시키고, 입력 데이터가 올바르지 않거나 누락된 경우에는 사용자 정의 예외를 발생시킨다. Listing 14에서는 개정된 Example_Manager 클래스를 보여 준다.


Listing 14. 입력 유효성 검증 및 예외가 포함된 개정된 SOAP 서비스 오브젝트
    
<?php
class Example_Manager {

    // define filters and validators for input
    private $_filters = array(
      'title'     => array('HtmlEntities', 'StripTags', 'StringTrim'),
      'shortdesc' => array('HtmlEntities', 'StripTags', 'StringTrim'),
      'price'     => array('HtmlEntities', 'StripTags', 'StringTrim'),
      'quantity'  => array('HtmlEntities', 'StripTags', 'StringTrim')
    );

    private $_validators = array(
      'title'     => array(),
      'shortdesc' => array(),
      'price'     => array('Float'),
      'quantity'  => array('Int')
    );

    /**
     * Returns list of all products in database
     *
     * @return array
     */
    public function getProducts() 
    {
      $db = Zend_Registry::get('Zend_Db');
      $sql = "SELECT * FROM products";
      return $db->fetchAll($sql);
    }

    /**
     * Returns specified product in database
     *
     * @param integer $id
     * @return array|Example_Exception
     */
    public function getProduct($id)
    {
      if (!Zend_Validate::is($id, 'Int')) {
        throw new Example_Exception('Invalid input');
      }
      $db = Zend_Registry::get('Zend_Db');
      $sql = "SELECT * FROM products WHERE id = '$id'";
      $result = $db->fetchAll($sql);
      if (count($result) != 1) {
        throw new Example_Exception('Invalid product ID: ' . $id); 
      } 
      return $result;
    }

    /**
     * Adds new product to database
     *
     * @param array $data array of data values with keys -> table fields
     * @return integer id of inserted product
     */
    public function addProduct($data) 
    {
      $input = new Zend_Filter_Input($this->_filters,
        $this->_validators, $data);
      if (!$input->isValid()) {
        throw new Example_Exception('Invalid input');
      }
      $values = $input->getEscaped();
      $db = Zend_Registry::get('Zend_Db');
      $db->insert('products', $values);
      return $db->lastInsertId();
    }

    /**
     * Deletes product from database
     *
     * @param integer $id
     * @return integer number of products deleted
     */
    public function deleteProduct($id) 
    {
      if (!Zend_Validate::is($id, 'Int')) {
        throw new Example_Exception('Invalid input');
      }
      $db = Zend_Registry::get('Zend_Db');
      $count = $db->delete('products', 'id=' . $db->quote($id));
      return $count;
    }

    /**
     * Updates product in database
     *
     * @param integer $id
     * @param array $data
     * @return integer number of products updated
     */
    public function updateProduct($id, $data) 
    {
      $input = new Zend_Filter_Input($this->_filters, 
        $this->_validators, $data);
      if (!Zend_Validate::is($id, 'Int') || !$input->isValid()) {
        throw new Example_Exception('Invalid input');
      } 
      $values = $input->getEscaped();
      $db = Zend_Registry::get('Zend_Db');
      $count = $db->update('products', $values, 'id=' . $db->quote($id));
      return $count;
    }    

}

Listing 14에서 서비스 API는 모든 입력 매개변수에 대한 유효성 검증을 포함하도록 강화되었다. 대부분의 API 메소드의 경우 Zend_Validate::is() 정적 메소드가 입력 인수를 테스트하는 편리한 방법을 제공한다. 일부 경우에는 입력을 유효성 검증하고 필터링하는 데 추가적인 Zend_Filter_Input 필터 체인이 사용된다. 입력 유효성 검증 프로세스를 통해 발생하는 오류는 Example_Exception 클래스의 인스턴스로 발생한다.

최종 단계는 발생한 Example_Exception을 SOAP 결함으로 자동으로 변환하도록 SOAP 서버에 지시하는 것이다. 이는 Listing 15의 개정된 IndexController::soapAction과 같이 registerFaultException() 메소드를 사용하여 예외 클래스를 SOAP 서버에 등록하여 수행된다.


Listing 15. 사용자 정의 예외가 결함으로 발생하는 개정된 soapAction() 정의
    
<?php
class IndexController extends Zend_Controller_Action
{

    public function soapAction()
    {
      // disable layouts and renderers
      $this->getHelper('viewRenderer')->setNoRender(true);
      
      // initialize server and set URI
      $server = new Zend_Soap_Server(null, 
        array('uri' => 'http://example.localhost/index/soap'));

      // set SOAP service class      
      $server->setClass('Example_Manager');
      
      // register exceptions that generate SOAP faults
      $server->registerFaultException(array('Example_Exception'));
      
      // handle request
      $server->handle();
    }
}

어떻게 작동되는지 확인하려면 getProduct() 메소드에 대한 SOAP 요청을 전송한 후 해당 요청에 올바르지 않은 ID를 전달한다. Listing 16에는 이러한 SOAP 요청에 대한 예제가 있다.


Listing 16. 올바르지 않은 입력 인수가 포함된 SOAP 요청
    
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" 
 xmlns:ns1="http://example.localhost/index/soap" 
 xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:enc="http://www.w3.org/2003/05/soap-encoding">
<env:Body>
<ns1:getProduct env:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
<param0 xsi:type="xsd:string">nosuchproduct</param0>
</ns1:getProduct>
</env:Body>
</env:Envelope>

서버는 입력을 유효성 검증한 후 올바르지 않은 것으로 판별되면 Example_Exception(SOAP 결함으로 변환된 후 다시 클라이언트에 전송됨)을 발생시킨다. Listing 17에서는 응답 패킷의 모양을 보여 준다.


Listing 17. 생성되는 SOAP 결함
    
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope 
 xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/>
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>Receiver</faultcode>
<faultstring>Invalid input</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

SOAP 클라이언트의 관점에서는 위에 있는 것과 같은 SOAP 결함을 단계적으로 발견하여 처리할 수 있도록 try-catch 블록 내에서 SOAP 호출을 랩핑하는 것이 좋다. Listing 12에 있는 예제 SOAP 클라이언트를 다시 살펴보면 이러한 진행 과정에 대한 예제를 확인할 수 있다.


WSDL 지원 추가하기

PHP에서 원시 SOAP 확장의 한 가지 단점은 SOAP 서비스에 대한 WSDL 파일의 자동 생성 지원이 포함되어 있지 않다는 것이다. WSDL 파일은 사용 가능한 SOAP API 메소드에 대한 정보를 포함하고 있고 연결하는 클라이언트가 SOAP API를 "자동 발견"하는 데 사용할 수 있기 때문에 유용하다.

하지만 Zend Framework는 SOAP 서비스에 대한 WSDL 파일을 자동으로 생성하는 데 사용할 수 있는 Zend_Soap_AutoDiscover 컴포넌트를 포함하고 있다. Zend Framework는 SOAP 서비스 클래스에 있는 PHPDoc 주석을 읽은 후 이러한 주석을 WSDL 문서로 변환하여 이를 수행한다. 이 기사의 이전 Listing을 살펴보면 각 메소드에는 PHPDoc 주석이 함께 제공되는 것을 알게 된다. 이는 WSDL 자동 생성을 더 단순하게 만들기 위해 의도적으로 수행된다.

Listing 18에서는 Zend_Soap_AutoDiscover 컴포넌트를 사용하여 WSDL 자동 생성을 설정하는 방법을 보여 준다.


Listing 18. wsdlAction() 정의
        
<?php
class IndexController extends Zend_Controller_Action
{
    public function soapAction()
    {
      // disable layouts and renderers
      $this->getHelper('viewRenderer')->setNoRender(true);

      // initialize server and set WSDL file location
      $server = new Zend_Soap_Server('http://example.localhost/index/wsdl');
      // set SOAP service class      
      $server->setClass('Example_Manager');

      // register exceptions that generate SOAP faults
      $server->registerFaultException(array('Example_Exception'));

      // handle request
      $server->handle();
    }

    public function wsdlAction()
    {
      // disable layouts and renderers
      $this->getHelper('viewRenderer')->setNoRender(true);

      // set up WSDL auto-discovery
      $wsdl = new Zend_Soap_AutoDiscover();

      // attach SOAP service class
      $wsdl->setClass('Example_Manager');

      // set SOAP action URI
      $wsdl->setUri('http://example.localhost/index/soap');

      // handle request
      $wsdl->handle();
    }
}

Listing 18에서는 Zend_Soap_AutoDiscover 컴포넌트의 인스턴스를 초기화한 후 이 인스턴스가 Example_Manager 클래스를 가리키도록 하는 새 wsdlAction()를 정의한다. 이 인스턴스의 handle() 메소드를 호출하면 이 메소드가 지정된 클래스를 읽고 내부에 있는 PHPDoc 주석을 구문 분석하고 서비스 오브젝트에 대해 자세히 설명하는 표준 준수 WSDL 문서를 생성한다.

결과를 확인하려면 브라우저가 http://example.localhost/index/wsdl을 가리키도록 한다. 그러면 그림 3과 같은 내용이 표시된다.


그림 3. 동적으로 생성된 WSDL 파일
동적으로 생성된 WSDL 파일의 화면 캡처

이제 urilocation 매개변수를 수동으로 지정하는 대신 SOAP 서버와 클라이언트가 이 WSDL 파일을 사용할 수 있다. Listing 18에서도 WSDL 모드에서 시작하도록 Zend_Soap_Server 생성자에 WSDL URL을 전달하도록 soapAction()를 개정하여 이를 보여 준다. 또한 SOAP 클라이언트를 연결하면 이 WSDL URL을 사용하여 SOAP 서비스 API를 자동 발견할 수 있다.


결론

Zend Framework는 SOAP API를 웹 애플리케이션에 신속하고 효율적으로 추가하는 데 필요한 완전한 툴킷을 제공한다. 이 툴킷을 사용하면 잘 알려진 SOAP 표준을 사용하여 경제적이고 효율적으로 웹 애플리케이션 사이에서 정보를 교환할 수 있다. Zend Framework의 SOAP 클라이언트 및 서버와 WSDL 자동 생성에 대한 내장 지원으로 인해 Zend Framework는 신속한 SOAP 서비스 구현 및 전개를 위한 적절한 선택이 된다. 마지막으로 Zend Framework는 MVC 준수 프레임워크이기 때문에 기존 코드베이스에 최소한의 영향을 미쳐 걱정할 회귀가 적은 상태로 기존 Zend Framework 애플리케이션에 SOAP API를 매우 쉽게 접목할 수도 있다.

제품의 추가, 편집, 삭제 및 검색에 사용할 수 있는 단순 SOAP 클라이언트와 함께 이 기사에서 구현된 모든 코드의 링크는 다운로드를 참조한다. 이 기사에서 사용된 도구는 참고자료를 참조한다. 코드를 확보하여 이 코드에 새로운 항목을 추가해 보기를 권장한다. 이렇게 추가해도 문제는 절대 발생하지 않으며 학습에 확실히 도움이 된다. 한번 해 보자.



다운로드 하십시오

설명이름크기다운로드 방식
The example application discussed in this articleexample-app-soap.zip8KBHTTP

다운로드 방식에 대한 정보


참고자료

교육

제품 및 기술 얻기

  • Zend Framework: 최신 Zend Framework 릴리스를 다운로드하자.

  • MySQL 데이터베이스 서버: 이 기사에 사용된 오픈 소스 트랜잭션 데이터베이스를 다운로드하자.

  • DB2 Express-C: 소규모 및 중간 규모 비즈니스를 위한 애플리케이션 개발의 탁월한 기초가 되는 IBM DB2 데이터베이스 서버의 무료 버전을 다운로드하자.

  • IBM 제품 평가판을 다운로드하거나 IBM SOA Sandbox의 온라인 시험판을 살펴보고 DB2®, Lotus®, Rational®, Tivoli® 및 WebSphere®의 애플리케이션 개발 도구 및 미들웨어 제품을 사용해 볼 수 있다.

토론

필자소개

Vikram Vaswani는 오픈 소스 도구와 기술을 전문으로 다루는 컨설팅 서비스 회사인 Melonfire의 창립자이자 CEO이며 PHP Programming SolutionsPHP: A Beginners Guide의 저자이기도 하다.

잘못된 도움말 신고

부정사용 신고

감사합니다. 이 항목은 운영자가 관심을 표시했습니다.


잘못된 도움말 신고

부정사용 신고

제출실패 신고. 나중에 다시 실행해주세요.


디벨로퍼웍스 로그인


IBM ID가 필요하세요?
IBM ID를 잊으셨습니까?


비밀번호를 잊으셨습니까?
비밀번호 변경

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관.

 


developerWorks에 처음 로그인하면 developerWorks프로파일이 생성됩니다.귀하의 프로파일에서 동의하신 내용이 공개되지만 이 사항은 언제든지 변경 가능합니다. 귀하의 성명(숨김으로 체크되어 있어도 표시됩니다)과 디스플레이 이름은 게시한 컨텐츠나 사이트 엑세스시 표시됩니다.

화면상에 보여지는 닉네임을 정하세요.

처음 developerWorks에 로그인할 때 프로파일이 작성되므로, 이를 위해 디스플레이 이름을 선택해야 합니다. 선택하신 디스플레이 이름은 developerWorks에 게시한 컨텐츠에 표시됩니다.

3글자 이상 31글자 이하의 길이로 사용 가능합니다. dW커뮤니티 내에서는 보안상 이메일주소를 제외한 다른 이름을 지정하셔야 합니다.

3개의 &이나 대쉬를 포함해주시고 31글자내로 제한해주세요.


developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관.

 


아티클 순위

의견

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=20
Zone=XML, 오픈 소스, SOA와 웹서비스
ArticleID=512439
ArticleTitle=Zend Framework로 SOAP 서비스 구현하기
publish-date=05112010
author1-email=vikram.melonfire@gmail.com
author1-email-cc=

태그

Help
검색 필드를 사용하여 My developerWorks 내에서 해당 태그가 사용된 모든 종류의 컨텐츠를 검색하십시오.

태그를 더 많이 보거나 적게 보기 위해 슬라이더 막대를 사용하십시오.

인기 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 최고 인기 태그를 보여줍니다.

내 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 귀하의 태그를 보여줍니다.

검색 필드를 사용하여 My developerWorks 내에서 해당 태그가 사용된 모든 종류의 컨텐츠를 검색하십시오. 인기 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 최고 인기 태그를 보여줍니다. 내 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 귀하의 태그를 보여줍니다.